Initial commit
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
internal static class Constants
|
||||
{
|
||||
internal const ushort IMAGE_FILE_MACHINE_I386 = 0x014c;
|
||||
internal const ushort IMAGE_FILE_MACHINE_IA64 = 0x0200;
|
||||
internal const ushort IMAGE_FILE_MACHINE_AMD64 = 0x8664;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_data_directory
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct IMAGE_DATA_DIRECTORY
|
||||
{
|
||||
public uint VirtualAddress; // DWORD VirtualAddress
|
||||
public uint Size; // DWORD Size
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct IMAGE_DOS_HEADER
|
||||
{
|
||||
public ushort MagicNumber; // e_magic - Magic number (The value “MZ” are the initials of the PE designer Mark Zbikowski)
|
||||
public ushort BytesOnLastPageOfFile; // e_cblp - Bytes on last page of file
|
||||
public ushort PagesInFile; // e_cp - Pages in file
|
||||
public ushort Relocations; // e_crlc - Relocations
|
||||
public ushort SizeOfHeaderInParagraphs; // e_cparhdr - Size of header in paragraphs
|
||||
public ushort MinimumExtraParagraphs; // e_minalloc - Minimum extra paragraphs needed
|
||||
public ushort MaximumExtraParagraphs; // e_maxalloc - Maximum extra paragraphs needed
|
||||
public ushort InitialSS; // e_ss - Initial (relative) SS value
|
||||
public ushort InitialSP; // e_sp - Initial SP value
|
||||
public ushort Checksum; // e_csum - Checksum
|
||||
public ushort InitialIP; // e_ip - Initial IP value
|
||||
public ushort InitialCS; // e_cs - Initial (relative) CS value
|
||||
public ushort AddressOfRelocationTable; // e_lfarlc - File address of relocation table
|
||||
public ushort OverlayNumber; // e_ovno - Overlay number
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public ushort[] ReservedWords1; // e_res - Reserved words
|
||||
|
||||
public ushort OEMIdentifier; // e_oemid - OEM identifier (for e_oeminfo)
|
||||
public ushort OEMInformation; // e_oeminfo - OEM information; e_oemid specific
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
|
||||
public ushort[] ReservedWords2; // e_res2 - Reserved words
|
||||
|
||||
public int FileAddressOfNewExeHeader; // e_lfanew - File address of new exe header
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_file_header
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct IMAGE_FILE_HEADER
|
||||
{
|
||||
/// <summary>
|
||||
/// The architecture type of the computer.
|
||||
/// An image file can only be run on the specified computer or a system that emulates the specified computer.
|
||||
/// </summary>
|
||||
public ushort Machine;
|
||||
|
||||
/// <summary>
|
||||
/// The number of sections.
|
||||
/// This indicates the size of the section table, which immediately follows the headers.
|
||||
/// Note that the Windows loader limits the number of sections to 96.
|
||||
/// </summary>
|
||||
public ushort NumberOfSections;
|
||||
|
||||
/// <summary>
|
||||
/// The low 32 bits of the time stamp of the image.
|
||||
/// This represents the date and time the image was created by the linker.
|
||||
/// The value is represented in the number of seconds elapsed since midnight (00:00:00), January 1, 1970, Universal Coordinated Time, according to the system clock.
|
||||
/// </summary>
|
||||
public uint TimeDateStamp;
|
||||
|
||||
public uint PointerToSymbolTable;
|
||||
|
||||
public uint NumberOfSymbols;
|
||||
|
||||
public ushort SizeOfOptionalHeader;
|
||||
|
||||
public ushort Characteristics;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_nt_headers32
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct IMAGE_NT_HEADERS32
|
||||
{
|
||||
public uint Signature; // DWORD Signature
|
||||
public IMAGE_FILE_HEADER FileHeader; // IMAGE_FILE_HEADER FileHeader
|
||||
public IMAGE_OPTIONAL_HEADER32 OptionalHeader; // IMAGE_OPTIONAL_HEADER32 OptionalHeader
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_nt_headers64
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct IMAGE_NT_HEADERS64
|
||||
{
|
||||
public uint Signature; // DWORD Signature
|
||||
public IMAGE_FILE_HEADER FileHeader; // IMAGE_FILE_HEADER FileHeader
|
||||
public IMAGE_OPTIONAL_HEADER64 OptionalHeader; // IMAGE_OPTIONAL_HEADER32 OptionalHeader
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct IMAGE_OPTIONAL_HEADER32
|
||||
{
|
||||
public ushort Magic;
|
||||
public byte MajorLinkerVersion;
|
||||
public byte MinorLinkerVersion;
|
||||
public uint SizeOfCode;
|
||||
public uint SizeOfInitializedData;
|
||||
public uint SizeOfUninitializedData;
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the entry point function, relative to the image base address.
|
||||
/// For executable files, this is the starting address.
|
||||
/// For device drivers, this is the address of the initialization function.
|
||||
/// The entry point function is optional for DLLs.
|
||||
/// When no entry point is present, this member is zero.
|
||||
/// </summary>
|
||||
public uint AddressOfEntryPoint;
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the beginning of the code section, relative to the image base.
|
||||
/// </summary>
|
||||
public uint BaseOfCode;
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the beginning of the data section, relative to the image base.
|
||||
/// </summary>
|
||||
public uint BaseOfData;
|
||||
|
||||
/// <summary>
|
||||
/// The preferred address of the first byte of the image when it is loaded in memory.
|
||||
/// This value is a multiple of 64K bytes.
|
||||
/// The default value for DLLs is 0x10000000.
|
||||
/// The default value for applications is 0x00400000, except on Windows CE where it is 0x00010000.
|
||||
/// </summary>
|
||||
public uint ImageBase;
|
||||
|
||||
public uint SectionAlignment;
|
||||
|
||||
public uint FileAlignment;
|
||||
|
||||
public ushort MajorOperatingSystemVersion;
|
||||
public ushort MinorOperatingSystemVersion;
|
||||
public ushort MajorImageVersion;
|
||||
public ushort MinorImageVersion;
|
||||
public ushort MajorSubsystemVersion;
|
||||
public ushort MinorSubsystemVersion;
|
||||
public uint Win32VersionValue;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the image, in bytes, including all headers. Must be a multiple of SectionAlignment.
|
||||
/// </summary>
|
||||
public uint SizeOfImage;
|
||||
|
||||
/// <summary>
|
||||
/// The combined size of the following items, rounded to a multiple of the value specified in the FileAlignment member.
|
||||
/// - e_lfanew member of IMAGE_DOS_HEADER
|
||||
/// - 4 byte signature
|
||||
/// - size of IMAGE_FILE_HEADER
|
||||
/// - size of optional header
|
||||
/// - size of all section headers
|
||||
/// </summary>
|
||||
public uint SizeOfHeaders;
|
||||
public uint CheckSum;
|
||||
public ushort Subsystem;
|
||||
public ushort DllCharacteristics;
|
||||
public uint SizeOfStackReserve;
|
||||
public uint SizeOfStackCommit;
|
||||
public uint SizeOfHeapReserve;
|
||||
public uint SizeOfHeapCommit;
|
||||
public uint LoaderFlags;
|
||||
|
||||
/// <summary>
|
||||
/// The number of directory entries in the remainder of the optional header. Each entry describes a location and size.
|
||||
/// </summary>
|
||||
public uint NumberOfRvaAndSizes;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)]
|
||||
public IMAGE_DATA_DIRECTORY[] DataDirectory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct IMAGE_OPTIONAL_HEADER64
|
||||
{
|
||||
public ushort Magic;
|
||||
public byte MajorLinkerVersion;
|
||||
public byte MinorLinkerVersion;
|
||||
public uint SizeOfCode;
|
||||
public uint SizeOfInitializedData;
|
||||
public uint SizeOfUninitializedData;
|
||||
public uint AddressOfEntryPoint;
|
||||
public uint BaseOfCode;
|
||||
public ulong ImageBase;
|
||||
public uint SectionAlignment;
|
||||
public uint FileAlignment;
|
||||
public ushort MajorOperatingSystemVersion;
|
||||
public ushort MinorOperatingSystemVersion;
|
||||
public ushort MajorImageVersion;
|
||||
public ushort MinorImageVersion;
|
||||
public ushort MajorSubsystemVersion;
|
||||
public ushort MinorSubsystemVersion;
|
||||
public uint Win32VersionValue;
|
||||
public uint SizeOfImage;
|
||||
public uint SizeOfHeaders;
|
||||
public uint CheckSum;
|
||||
public ushort Subsystem;
|
||||
public ushort DllCharacteristics;
|
||||
public ulong SizeOfStackReserve;
|
||||
public ulong SizeOfStackCommit;
|
||||
public ulong SizeOfHeapReserve;
|
||||
public ulong SizeOfHeapCommit;
|
||||
public uint LoaderFlags;
|
||||
public uint NumberOfRvaAndSizes;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)]
|
||||
public IMAGE_DATA_DIRECTORY[] DataDirectory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct IMAGE_SECTION_HEADER
|
||||
{
|
||||
/// <summary>
|
||||
/// An 8-byte, null-padded UTF-8 string.
|
||||
/// There is no terminating null character if the string is exactly eight characters long.
|
||||
/// For longer names, this member contains a forward slash (/) followed by an ASCII representation of a double number that is an offset into the string table.
|
||||
/// Executable images do not use a string table and do not support section names longer than eight characters.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
public byte[] Name;
|
||||
|
||||
public UnionType Misc;
|
||||
|
||||
/// <summary>
|
||||
/// The address of the first byte of the section when loaded into memory, relative to the image base.
|
||||
/// For object files, this is the address of the first byte before relocation is applied.
|
||||
/// </summary>
|
||||
public uint VirtualAddress;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the initialized data on disk, in bytes.
|
||||
/// This value must be a multiple of the FileAlignment member of the IMAGE_OPTIONAL_HEADER structure.
|
||||
/// If this value is less than the VirtualSize member, the remainder of the section is filled with zeroes.
|
||||
/// If the section contains only uninitialized data, the member is zero.
|
||||
/// </summary>
|
||||
public uint SizeOfRawData;
|
||||
|
||||
/// <summary>
|
||||
/// A file pointer to the first page within the COFF file.
|
||||
/// This value must be a multiple of the FileAlignment member of the IMAGE_OPTIONAL_HEADER structure.
|
||||
/// If a section contains only uninitialized data, set this member is zero.
|
||||
/// </summary>
|
||||
public uint PointerToRawData;
|
||||
|
||||
public uint PointerToRelocations;
|
||||
|
||||
public uint PointerToLinenumbers;
|
||||
|
||||
public ushort NumberOfRelocations;
|
||||
|
||||
public ushort NumberOfLinenumbers;
|
||||
|
||||
public uint Characteristics;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct UnionType
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public uint PhysicalAddress;
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the section when loaded into memory, in bytes. If this value is greater than the SizeOfRawData member, the section is filled with zeroes.
|
||||
/// This field is valid only for executable images and should be set to 0 for object files.
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public uint VirtualSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
internal static class StreamExtensions
|
||||
{
|
||||
internal static void WriteStruct<T>(this Stream stream, T structData) where T : struct
|
||||
{
|
||||
var bytes = StructToBytes(structData);
|
||||
stream.Write(bytes);
|
||||
}
|
||||
|
||||
private static byte[] StructToBytes<T>(T structData) where T : struct
|
||||
{
|
||||
int size = Marshal.SizeOf(structData);
|
||||
byte[] byteArray = new byte[size];
|
||||
nint ptr = Marshal.AllocHGlobal(size);
|
||||
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(structData, ptr, false);
|
||||
Marshal.Copy(ptr, byteArray, 0, size);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
internal class WasmWebcilUnwrapper : IAsyncDisposable
|
||||
{
|
||||
private readonly Stream _wasmStream;
|
||||
private MemoryStream? _cachedStream;
|
||||
|
||||
public WasmWebcilUnwrapper(Stream wasmStream)
|
||||
{
|
||||
_wasmStream = wasmStream;
|
||||
}
|
||||
|
||||
public async Task WriteUnwrappedAsync(Stream outputStream)
|
||||
{
|
||||
// Cache the stream content to MemoryStream for synchronous BinaryReader operations
|
||||
if (_cachedStream == null)
|
||||
{
|
||||
_cachedStream = new MemoryStream();
|
||||
await _wasmStream.CopyToAsync(_cachedStream);
|
||||
_cachedStream.Position = 0; // Reset to beginning for validation
|
||||
}
|
||||
|
||||
// Validate prefix from cached stream
|
||||
ValidateWasmPrefix(_cachedStream);
|
||||
|
||||
// Skip prefix and read data section
|
||||
var prefix = WasmWebcilWrapper.GetPrefix();
|
||||
_cachedStream.Position = prefix.Length;
|
||||
|
||||
using var reader = new BinaryReader(_cachedStream, System.Text.Encoding.UTF8, leaveOpen: true);
|
||||
var bytes = ReadDataSection(reader);
|
||||
await outputStream.WriteAsync(bytes);
|
||||
}
|
||||
|
||||
private void ValidateWasmPrefix(Stream stream)
|
||||
{
|
||||
var originalPosition = stream.Position;
|
||||
try
|
||||
{
|
||||
// Create a byte array matching the length of the prefix.
|
||||
var prefix = WasmWebcilWrapper.GetPrefix();
|
||||
var buffer = new byte[prefix.Length];
|
||||
stream.Position = 0;
|
||||
int bytesRead = stream.Read(buffer, 0, buffer.Length);
|
||||
if (bytesRead < buffer.Length)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to read Wasm prefix.");
|
||||
}
|
||||
|
||||
// Compare the read prefix with the expected one.
|
||||
if (!buffer.SequenceEqual(prefix))
|
||||
{
|
||||
throw new InvalidOperationException("Invalid Wasm prefix.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = originalPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SkipSection(BinaryReader reader)
|
||||
{
|
||||
var size = ULEB128Decode(reader);
|
||||
reader.BaseStream.Seek(size, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
private static byte[] ReadDataSection(BinaryReader reader)
|
||||
{
|
||||
// Skip until we find the data section, which contains the Webcil payload.
|
||||
byte[] buffer = new byte[1];
|
||||
while (true)
|
||||
{
|
||||
// Read the Data section
|
||||
var dataRead = reader.Read(buffer, 0, 1);
|
||||
if (dataRead == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to read Data Section.");
|
||||
}
|
||||
|
||||
// Check the Data section (ID = 11)
|
||||
if (buffer[0] == 11)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip other sections by reading and ignoring their content.
|
||||
SkipSection(reader);
|
||||
}
|
||||
|
||||
// Read and ignore the size of the data section.
|
||||
ULEB128Decode(reader);
|
||||
|
||||
// Read the number of segments.
|
||||
int segmentsCount = (int)ULEB128Decode(reader);
|
||||
int lastSegment = segmentsCount - 1;
|
||||
for (int segmentIndex = 0; segmentIndex < segmentsCount; segmentIndex++)
|
||||
{
|
||||
// Ignore segmentType (1 = passive segment)
|
||||
var segmentType = reader.Read(buffer, 0, 1);
|
||||
if (segmentType != 1)
|
||||
{
|
||||
throw new InvalidOperationException($"Unexpected segment code for segment {segmentIndex}.");
|
||||
}
|
||||
|
||||
// Read the segment size.
|
||||
var segmentSize = ULEB128Decode(reader);
|
||||
|
||||
// The actual Webcil payload is expected to be in the last segment.
|
||||
if (segmentIndex == lastSegment)
|
||||
{
|
||||
return reader.ReadBytes((int)segmentSize);
|
||||
}
|
||||
|
||||
// Skip other segments.
|
||||
reader.BaseStream.Seek(segmentSize, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
throw new Exception("Unable to read DataSection.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a variable-length quantity (VLQ) encoded as unsigned LEB128.
|
||||
/// LEB128 (Little Endian Base 128) is used to encode integers in a variable number of bytes.
|
||||
/// The method reads bytes from the provided binary reader and decodes them into an unsigned integer.
|
||||
/// </summary>
|
||||
/// <param name="reader">The binary reader from which to read the ULEB128 encoded data.</param>
|
||||
/// <returns>The decoded unsigned integer from the ULEB128 encoded data.</returns>
|
||||
private static uint ULEB128Decode(BinaryReader reader)
|
||||
{
|
||||
uint result = 0;
|
||||
int shift = 0;
|
||||
byte byteValue;
|
||||
|
||||
do
|
||||
{
|
||||
byteValue = reader.ReadByte();
|
||||
uint byteAsUInt = byteValue & 0x7Fu;
|
||||
result |= byteAsUInt << shift;
|
||||
shift += 7;
|
||||
} while ((byteValue & 0x80) != 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_cachedStream != null)
|
||||
{
|
||||
await _cachedStream.DisposeAsync();
|
||||
}
|
||||
await _wasmStream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
public class WasmWebcilWrapper
|
||||
{
|
||||
private static readonly FieldInfo FieldInfoPrefix = typeof(WebcilWasmWrapper).GetField("s_wasmWrapperPrefix", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetField)!;
|
||||
|
||||
public static byte[] GetPrefix()
|
||||
{
|
||||
#if NET7_0_OR_GREATER
|
||||
return GetPrefixValue<ReadOnlyMemory<byte>>().ToArray();
|
||||
#else
|
||||
return GetPrefixValue<byte[]>();
|
||||
#endif
|
||||
}
|
||||
|
||||
private static T GetPrefixValue<T>()
|
||||
{
|
||||
return (T)FieldInfoPrefix.GetValue(null)!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
public class Webcil
|
||||
{
|
||||
/// <summary>
|
||||
/// The header of a WebCIL file.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// The header is a subset of the PE, COFF and CLI headers that are needed by the mono runtime to load managed assemblies.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public unsafe struct WebcilHeader
|
||||
{
|
||||
public fixed byte id[4]; // 'W' 'b' 'I' 'L'
|
||||
// 4 bytes
|
||||
public ushort version_major; // 0
|
||||
public ushort version_minor; // 0
|
||||
// 8 bytes
|
||||
|
||||
public ushort coff_sections;
|
||||
public ushort reserved0; // 0
|
||||
// 12 bytes
|
||||
public uint pe_cli_header_rva;
|
||||
public uint pe_cli_header_size;
|
||||
// 20 bytes
|
||||
public uint pe_debug_rva;
|
||||
public uint pe_debug_size;
|
||||
// 28 bytes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the Webcil analog of System.Reflection.PortableExecutable.SectionHeader, but with fewer fields
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct WebcilSectionHeader
|
||||
{
|
||||
public readonly int VirtualSize;
|
||||
public readonly int VirtualAddress;
|
||||
public readonly int SizeOfRawData;
|
||||
public readonly int PointerToRawData;
|
||||
|
||||
public WebcilSectionHeader(int virtualSize, int virtualAddress, int sizeOfRawData, int pointerToRawData)
|
||||
{
|
||||
VirtualSize = virtualSize;
|
||||
VirtualAddress = virtualAddress;
|
||||
SizeOfRawData = sizeOfRawData;
|
||||
PointerToRawData = pointerToRawData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
internal static unsafe class WebcilConstants
|
||||
{
|
||||
public const int WC_VERSION_MAJOR = 0;
|
||||
public const int WC_VERSION_MINOR = 0;
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection.PortableExecutable;
|
||||
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
/// <summary>
|
||||
/// Reads a .NET assembly in a normal PE COFF file and writes it out as a Webcil file
|
||||
/// </summary>
|
||||
public class WebcilConverter
|
||||
{
|
||||
|
||||
// Interesting stuff we've learned about the input PE file
|
||||
public record PEFileInfo(
|
||||
// The sections in the PE file
|
||||
ImmutableArray<SectionHeader> SectionHeaders,
|
||||
// The location of the debug directory entries
|
||||
DirectoryEntry DebugTableDirectory,
|
||||
// The file offset of the sections, following the section directory
|
||||
FilePosition SectionStart,
|
||||
// The debug directory entries
|
||||
ImmutableArray<DebugDirectoryEntry> DebugDirectoryEntries
|
||||
);
|
||||
|
||||
// Intersting stuff we know about the webcil file we're writing
|
||||
public record WCFileInfo(
|
||||
// The header of the webcil file
|
||||
Webcil.WebcilHeader Header,
|
||||
// The section directory of the webcil file
|
||||
ImmutableArray<Webcil.WebcilSectionHeader> SectionHeaders,
|
||||
// The file offset of the sections, following the section directory
|
||||
FilePosition SectionStart
|
||||
);
|
||||
|
||||
private readonly string _inputPath;
|
||||
private readonly string _outputPath;
|
||||
|
||||
private string InputPath => _inputPath;
|
||||
|
||||
public bool WrapInWebAssembly { get; set; } = true;
|
||||
|
||||
private WebcilConverter(string inputPath, string outputPath)
|
||||
{
|
||||
_inputPath = inputPath;
|
||||
_outputPath = outputPath;
|
||||
}
|
||||
|
||||
public static WebcilConverter FromPortableExecutable(string inputPath, string outputPath)
|
||||
=> new WebcilConverter(inputPath, outputPath);
|
||||
|
||||
public void ConvertToWebcil()
|
||||
{
|
||||
using var inputStream = File.Open(_inputPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
PEFileInfo peInfo;
|
||||
WCFileInfo wcInfo;
|
||||
using (var peReader = new PEReader(inputStream, PEStreamOptions.LeaveOpen))
|
||||
{
|
||||
GatherInfo(peReader, out wcInfo, out peInfo);
|
||||
}
|
||||
|
||||
using var outputStream = File.Open(_outputPath, FileMode.Create, FileAccess.Write);
|
||||
if (!WrapInWebAssembly)
|
||||
{
|
||||
WriteConversionTo(outputStream, inputStream, peInfo, wcInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if wrapping in WASM, write the webcil payload to memory because we need to discover the length
|
||||
|
||||
// webcil is about the same size as the PE file
|
||||
using var memoryStream = new MemoryStream(checked((int)inputStream.Length));
|
||||
WriteConversionTo(memoryStream, inputStream, peInfo, wcInfo);
|
||||
memoryStream.Flush();
|
||||
var wrapper = new WebcilWasmWrapper(memoryStream);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
wrapper.WriteWasmWrappedWebcil(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteConversionTo(Stream outputStream, FileStream inputStream, PEFileInfo peInfo, WCFileInfo wcInfo)
|
||||
{
|
||||
WriteHeader(outputStream, wcInfo.Header);
|
||||
WriteSectionHeaders(outputStream, wcInfo.SectionHeaders);
|
||||
CopySections(outputStream, inputStream, peInfo.SectionHeaders);
|
||||
if (wcInfo.Header.pe_debug_size != 0 && wcInfo.Header.pe_debug_rva != 0)
|
||||
{
|
||||
var wcDebugDirectoryEntries = FixupDebugDirectoryEntries(peInfo, wcInfo);
|
||||
OverwriteDebugDirectoryEntries(outputStream, wcInfo, wcDebugDirectoryEntries);
|
||||
}
|
||||
}
|
||||
|
||||
public record struct FilePosition(int Position)
|
||||
{
|
||||
public static implicit operator FilePosition(int position) => new(position);
|
||||
|
||||
public static FilePosition operator +(FilePosition left, int right) => new(left.Position + right);
|
||||
}
|
||||
|
||||
private static unsafe int SizeOfHeader()
|
||||
{
|
||||
return sizeof(Webcil.WebcilHeader);
|
||||
}
|
||||
|
||||
public unsafe void GatherInfo(PEReader peReader, out WCFileInfo wcInfo, out PEFileInfo peInfo)
|
||||
{
|
||||
var headers = peReader.PEHeaders;
|
||||
var peHeader = headers.PEHeader!;
|
||||
var coffHeader = headers.CoffHeader!;
|
||||
var sections = headers.SectionHeaders;
|
||||
Webcil.WebcilHeader header;
|
||||
header.id[0] = (byte)'W';
|
||||
header.id[1] = (byte)'b';
|
||||
header.id[2] = (byte)'I';
|
||||
header.id[3] = (byte)'L';
|
||||
header.version_major = WebcilConstants.WC_VERSION_MAJOR;
|
||||
header.version_minor = WebcilConstants.WC_VERSION_MINOR;
|
||||
header.coff_sections = (ushort)coffHeader.NumberOfSections;
|
||||
header.reserved0 = 0;
|
||||
header.pe_cli_header_rva = (uint)peHeader.CorHeaderTableDirectory.RelativeVirtualAddress;
|
||||
header.pe_cli_header_size = (uint)peHeader.CorHeaderTableDirectory.Size;
|
||||
header.pe_debug_rva = (uint)peHeader.DebugTableDirectory.RelativeVirtualAddress;
|
||||
header.pe_debug_size = (uint)peHeader.DebugTableDirectory.Size;
|
||||
|
||||
// current logical position in the output file
|
||||
FilePosition pos = SizeOfHeader();
|
||||
// position of the current section in the output file
|
||||
// initially it's after all the section headers
|
||||
FilePosition curSectionPos = pos + sizeof(Webcil.WebcilSectionHeader) * coffHeader.NumberOfSections;
|
||||
// The first WC section is immediately after the section directory
|
||||
FilePosition firstWCSection = curSectionPos;
|
||||
|
||||
FilePosition firstPESection = 0;
|
||||
|
||||
ImmutableArray<Webcil.WebcilSectionHeader>.Builder headerBuilder = ImmutableArray.CreateBuilder<Webcil.WebcilSectionHeader>(coffHeader.NumberOfSections);
|
||||
foreach (var sectionHeader in sections)
|
||||
{
|
||||
// The first section is the one with the lowest file offset
|
||||
if (firstPESection.Position == 0)
|
||||
{
|
||||
firstPESection = sectionHeader.PointerToRawData;
|
||||
}
|
||||
else
|
||||
{
|
||||
firstPESection = Math.Min(firstPESection.Position, sectionHeader.PointerToRawData);
|
||||
}
|
||||
|
||||
var newHeader = new Webcil.WebcilSectionHeader
|
||||
(
|
||||
virtualSize: sectionHeader.VirtualSize,
|
||||
virtualAddress: sectionHeader.VirtualAddress,
|
||||
sizeOfRawData: sectionHeader.SizeOfRawData,
|
||||
pointerToRawData: curSectionPos.Position
|
||||
);
|
||||
|
||||
pos += sizeof(Webcil.WebcilSectionHeader);
|
||||
curSectionPos += sectionHeader.SizeOfRawData;
|
||||
headerBuilder.Add(newHeader);
|
||||
}
|
||||
|
||||
ImmutableArray<DebugDirectoryEntry> debugDirectoryEntries = peReader.ReadDebugDirectory();
|
||||
|
||||
peInfo = new PEFileInfo(SectionHeaders: sections,
|
||||
DebugTableDirectory: peHeader.DebugTableDirectory,
|
||||
SectionStart: firstPESection,
|
||||
DebugDirectoryEntries: debugDirectoryEntries);
|
||||
|
||||
wcInfo = new WCFileInfo(Header: header,
|
||||
SectionHeaders: headerBuilder.MoveToImmutable(),
|
||||
SectionStart: firstWCSection);
|
||||
}
|
||||
|
||||
private static void WriteHeader(Stream s, Webcil.WebcilHeader webcilHeader)
|
||||
{
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
webcilHeader.version_major = BinaryPrimitives.ReverseEndianness(webcilHeader.version_major);
|
||||
webcilHeader.version_minor = BinaryPrimitives.ReverseEndianness(webcilHeader.version_minor);
|
||||
webcilHeader.coff_sections = BinaryPrimitives.ReverseEndianness(webcilHeader.coff_sections);
|
||||
webcilHeader.pe_cli_header_rva = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_cli_header_rva);
|
||||
webcilHeader.pe_cli_header_size = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_cli_header_size);
|
||||
webcilHeader.pe_debug_rva = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_debug_rva);
|
||||
webcilHeader.pe_debug_size = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_debug_size);
|
||||
}
|
||||
WriteStructure(s, webcilHeader);
|
||||
}
|
||||
|
||||
private static void WriteSectionHeaders(Stream s, ImmutableArray<Webcil.WebcilSectionHeader> sectionsHeaders)
|
||||
{
|
||||
foreach (var sectionHeader in sectionsHeaders)
|
||||
{
|
||||
WriteSectionHeader(s, sectionHeader);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteSectionHeader(Stream s, Webcil.WebcilSectionHeader sectionHeader)
|
||||
{
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
sectionHeader = new Webcil.WebcilSectionHeader
|
||||
(
|
||||
virtualSize: BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualSize),
|
||||
virtualAddress: BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualAddress),
|
||||
sizeOfRawData: BinaryPrimitives.ReverseEndianness(sectionHeader.SizeOfRawData),
|
||||
pointerToRawData: BinaryPrimitives.ReverseEndianness(sectionHeader.PointerToRawData)
|
||||
);
|
||||
}
|
||||
WriteStructure(s, sectionHeader);
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
private static void WriteStructure<T>(Stream s, T structure)
|
||||
where T : unmanaged
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
byte* p = (byte*)&structure;
|
||||
s.Write(new ReadOnlySpan<byte>(p, sizeof(T)));
|
||||
}
|
||||
}
|
||||
#else
|
||||
private static void WriteStructure<T>(Stream s, T structure)
|
||||
where T : unmanaged
|
||||
{
|
||||
int size = Marshal.SizeOf<T>();
|
||||
byte[] buffer = new byte[size];
|
||||
IntPtr ptr = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
ptr = Marshal.AllocHGlobal(size);
|
||||
Marshal.StructureToPtr(structure, ptr, false);
|
||||
Marshal.Copy(ptr, buffer, 0, size);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
s.Write(buffer, 0, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
private static void CopySections(Stream outStream, FileStream inputStream, ImmutableArray<SectionHeader> peSections)
|
||||
{
|
||||
// endianness: ok, we're just copying from one stream to another
|
||||
foreach (var peHeader in peSections)
|
||||
{
|
||||
var buffer = new byte[peHeader.SizeOfRawData];
|
||||
inputStream.Seek(peHeader.PointerToRawData, SeekOrigin.Begin);
|
||||
ReadExactly(inputStream, buffer);
|
||||
outStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER && !NET6_0
|
||||
private static void ReadExactly(FileStream s, Span<byte> buffer)
|
||||
{
|
||||
s.ReadExactly(buffer);
|
||||
}
|
||||
#else
|
||||
private static void ReadExactly(FileStream s, byte[] buffer)
|
||||
{
|
||||
int offset = 0;
|
||||
while (offset < buffer.Length)
|
||||
{
|
||||
int read = s.Read(buffer, offset, buffer.Length - offset);
|
||||
if (read == 0)
|
||||
throw new EndOfStreamException();
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static FilePosition GetPositionOfRelativeVirtualAddress(ImmutableArray<Webcil.WebcilSectionHeader> wcSections, uint relativeVirtualAddress)
|
||||
{
|
||||
foreach (var section in wcSections)
|
||||
{
|
||||
if (relativeVirtualAddress >= section.VirtualAddress && relativeVirtualAddress < section.VirtualAddress + section.VirtualSize)
|
||||
{
|
||||
FilePosition pos = section.PointerToRawData + ((int)relativeVirtualAddress - section.VirtualAddress);
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("relative virtual address not in any section");
|
||||
}
|
||||
|
||||
// Given a physical file offset, return the section and the offset within the section.
|
||||
private (Webcil.WebcilSectionHeader section, int offset) GetSectionFromFileOffset(ImmutableArray<Webcil.WebcilSectionHeader> peSections, FilePosition fileOffset)
|
||||
{
|
||||
foreach (var section in peSections)
|
||||
{
|
||||
if (fileOffset.Position >= section.PointerToRawData && fileOffset.Position < section.PointerToRawData + section.SizeOfRawData)
|
||||
{
|
||||
return (section, fileOffset.Position - section.PointerToRawData);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"file offset not in any section (Webcil) for {InputPath}");
|
||||
}
|
||||
|
||||
private void GetSectionFromFileOffset(ImmutableArray<SectionHeader> sections, FilePosition fileOffset)
|
||||
{
|
||||
foreach (var section in sections)
|
||||
{
|
||||
if (fileOffset.Position >= section.PointerToRawData && fileOffset.Position < section.PointerToRawData + section.SizeOfRawData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"file offset {fileOffset.Position} not in any section (PE) for {InputPath}");
|
||||
}
|
||||
|
||||
// Make a new set of debug directory entries that
|
||||
// have their data pointers adjusted to be relative to the start of the webcil file.
|
||||
// This is necessary because the debug directory entires in the PE file are relative to the start of the PE file,
|
||||
// and a PE header is bigger than a webcil header.
|
||||
private ImmutableArray<DebugDirectoryEntry> FixupDebugDirectoryEntries(PEFileInfo peInfo, WCFileInfo wcInfo)
|
||||
{
|
||||
int dataPointerAdjustment = peInfo.SectionStart.Position - wcInfo.SectionStart.Position;
|
||||
ImmutableArray<DebugDirectoryEntry> entries = peInfo.DebugDirectoryEntries;
|
||||
ImmutableArray<DebugDirectoryEntry>.Builder newEntries = ImmutableArray.CreateBuilder<DebugDirectoryEntry>(entries.Length);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
DebugDirectoryEntry newEntry;
|
||||
if (entry.Type == DebugDirectoryEntryType.Reproducible || entry.DataPointer == 0 || entry.DataSize == 0)
|
||||
{
|
||||
// this entry doesn't have an associated data pointer, so just copy it
|
||||
newEntry = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the "DataPointer" field is a file offset in the PE file, adjust the entry wit the corresponding offset in the Webcil file
|
||||
var newDataPointer = entry.DataPointer - dataPointerAdjustment;
|
||||
newEntry = new DebugDirectoryEntry(entry.Stamp, entry.MajorVersion, entry.MinorVersion, entry.Type, entry.DataSize, entry.DataRelativeVirtualAddress, newDataPointer);
|
||||
GetSectionFromFileOffset(peInfo.SectionHeaders, entry.DataPointer);
|
||||
// validate that the new entry is in some section
|
||||
GetSectionFromFileOffset(wcInfo.SectionHeaders, newDataPointer);
|
||||
}
|
||||
newEntries.Add(newEntry);
|
||||
}
|
||||
return newEntries.MoveToImmutable();
|
||||
}
|
||||
|
||||
private static void OverwriteDebugDirectoryEntries(Stream s, WCFileInfo wcInfo, ImmutableArray<DebugDirectoryEntry> entries)
|
||||
{
|
||||
FilePosition debugDirectoryPos = GetPositionOfRelativeVirtualAddress(wcInfo.SectionHeaders, wcInfo.Header.pe_debug_rva);
|
||||
using var writer = new BinaryWriter(s, System.Text.Encoding.UTF8, leaveOpen: true);
|
||||
writer.Seek(debugDirectoryPos.Position, SeekOrigin.Begin);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
WriteDebugDirectoryEntry(writer, entry);
|
||||
}
|
||||
// TODO check that we overwrite with the same size as the original
|
||||
|
||||
// restore the stream position
|
||||
writer.Seek(0, SeekOrigin.End);
|
||||
}
|
||||
|
||||
private static void WriteDebugDirectoryEntry(BinaryWriter writer, DebugDirectoryEntry entry)
|
||||
{
|
||||
writer.Write((uint)0); // Characteristics
|
||||
writer.Write(entry.Stamp);
|
||||
writer.Write(entry.MajorVersion);
|
||||
writer.Write(entry.MinorVersion);
|
||||
writer.Write((uint)entry.Type);
|
||||
writer.Write(entry.DataSize);
|
||||
writer.Write(entry.DataRelativeVirtualAddress);
|
||||
writer.Write(entry.DataPointer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
internal static class WebcilConverterUtil
|
||||
{
|
||||
private static readonly byte[] SectionHeaderText = { 0x2E, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00 }; // .text
|
||||
private static readonly byte[] SectionHeaderRsRc = { 0x2E, 0x72, 0x73, 0x72, 0x63, 0x00, 0x00, 0x00 }; // .rsrc
|
||||
private static readonly byte[] SectionHeaderReloc = { 0x2E, 0x72, 0x65, 0x6C, 0x6F, 0x63, 0x00, 0x00 }; // .reloc
|
||||
private static readonly byte[] MSDOS =
|
||||
{
|
||||
0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x54, 0x68,
|
||||
0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E, 0x6E, 0x6F,
|
||||
0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20,
|
||||
0x6D, 0x6F, 0x64, 0x65, 0x2E, 0x0D, 0x0D, 0x0A, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
private static readonly ushort[] DOSReservedWords1 = { 0, 0, 0, 0 };
|
||||
private static readonly ushort[] DOSReservedWords2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
private static readonly DateTime Epoch = new(1970, 1, 1);
|
||||
private static readonly int SizeofDOSHeader = Marshal.SizeOf<IMAGE_DOS_HEADER>(); // 64
|
||||
private static readonly int SizeofFileHeader = Marshal.SizeOf<IMAGE_FILE_HEADER>();
|
||||
private static readonly int SizeofMSDOS = MSDOS.Length; // 64
|
||||
private static readonly int SizeofNTHeaders = Marshal.SizeOf<IMAGE_NT_HEADERS32>(); // 248
|
||||
private static readonly int SizeofOptionalHeader = Marshal.SizeOf<IMAGE_OPTIONAL_HEADER32>();
|
||||
private static readonly int SizeofSectionHeader = Marshal.SizeOf<IMAGE_SECTION_HEADER>(); // 40
|
||||
|
||||
private const uint FileAlignment = 0x0200;
|
||||
private const uint SectionAlignment = 0x2000;
|
||||
|
||||
/// <summary>
|
||||
/// Convert a Portable Executable file into a Webcil file.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The input path for the PE file.</param>
|
||||
/// <param name="outputPath">The output path for the Webcil file.</param>
|
||||
/// <param name="wrapInWebAssembly">The Webcil should be wrapped in Wasm [default value is <c>true</c>].</param>
|
||||
public static void ConvertToWebcil(string inputPath, string outputPath, bool wrapInWebAssembly = true)
|
||||
{
|
||||
var webcilConverter = WebcilConverter.FromPortableExecutable(inputPath, outputPath);
|
||||
webcilConverter.WrapInWebAssembly = wrapInWebAssembly;
|
||||
|
||||
webcilConverter.ConvertToWebcil();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a Webcil stream into a Portable Executable which can be used to create a valid <see cref="MetadataReference"/>.
|
||||
/// </summary>
|
||||
/// <param name="inputStream">The input sStream.</param>
|
||||
/// <param name="wrappedInWebAssembly">The Webcil is wrapped in Wasm [default value is <c>true</c>].</param>
|
||||
/// <returns>A byte[] Portable Executable</returns>
|
||||
public static async Task<byte[]> ConvertFromWebcilAsync(Stream inputStream, bool wrappedInWebAssembly = true)
|
||||
{
|
||||
Stream webcilStream;
|
||||
if (wrappedInWebAssembly)
|
||||
{
|
||||
await using var unwrapper = new WasmWebcilUnwrapper(inputStream);
|
||||
webcilStream = new MemoryStream();
|
||||
await unwrapper.WriteUnwrappedAsync(webcilStream);
|
||||
|
||||
webcilStream.Flush();
|
||||
webcilStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
else
|
||||
{
|
||||
webcilStream = inputStream;
|
||||
}
|
||||
|
||||
// These are Webcil variables
|
||||
var webcilHeader = ReadHeader(webcilStream);
|
||||
var webcilSectionHeaders = ReadSectionHeaders(webcilStream, webcilHeader.coff_sections);
|
||||
var webcilSectionHeadersCount = webcilSectionHeaders.Length;
|
||||
var webcilSectionHeadersSizeOfRawData = (uint)webcilSectionHeaders.Sum(x => x.SizeOfRawData);
|
||||
|
||||
// These are PE (Portable Executable) variables
|
||||
int sectionStart = SizeofDOSHeader + SizeofMSDOS + SizeofNTHeaders + webcilSectionHeadersCount * SizeofSectionHeader; // 496
|
||||
int sectionStartRounded = sectionStart.RoundToNearest();
|
||||
var extraBytesAfterSections = new byte[sectionStartRounded - sectionStart];
|
||||
var pointerToRawDataFirstSectionHeader = webcilSectionHeaders[0].PointerToRawData;
|
||||
var pointerToRawDataOffsetBetweenWebcilAndPE = sectionStartRounded - pointerToRawDataFirstSectionHeader;
|
||||
|
||||
using var peStream = new MemoryStream();
|
||||
|
||||
var DOSHeader = new IMAGE_DOS_HEADER
|
||||
{
|
||||
MagicNumber = 0x5A4D,
|
||||
BytesOnLastPageOfFile = 0x90,
|
||||
PagesInFile = 3,
|
||||
Relocations = 0,
|
||||
SizeOfHeaderInParagraphs = 4,
|
||||
MinimumExtraParagraphs = 0,
|
||||
MaximumExtraParagraphs = 0xFFFF,
|
||||
InitialSS = 0,
|
||||
InitialSP = 0xB8,
|
||||
Checksum = 0,
|
||||
InitialIP = 0,
|
||||
InitialCS = 0,
|
||||
AddressOfRelocationTable = 0x40,
|
||||
OverlayNumber = 0,
|
||||
ReservedWords1 = DOSReservedWords1,
|
||||
OEMIdentifier = 0,
|
||||
OEMInformation = 0,
|
||||
ReservedWords2 = DOSReservedWords2,
|
||||
FileAddressOfNewExeHeader = 0x80
|
||||
};
|
||||
peStream.WriteStruct(DOSHeader);
|
||||
|
||||
peStream.Write(MSDOS);
|
||||
|
||||
var IMAGE_NT_HEADERS32 = new IMAGE_NT_HEADERS32
|
||||
{
|
||||
Signature = 0x4550, // 'PE'
|
||||
FileHeader = new IMAGE_FILE_HEADER
|
||||
{
|
||||
Machine = Constants.IMAGE_FILE_MACHINE_I386,
|
||||
NumberOfSections = 3,
|
||||
TimeDateStamp = GetImageTimestamp(),
|
||||
PointerToSymbolTable = 0,
|
||||
NumberOfSymbols = 0,
|
||||
SizeOfOptionalHeader = 0x00E0,
|
||||
Characteristics = 0x0022
|
||||
},
|
||||
OptionalHeader = new IMAGE_OPTIONAL_HEADER32
|
||||
{
|
||||
Magic = 0x010B, // Signature/Magic - Represents PE32 for 32-bit (0x10b) and PE32+ for 64-bit (0x20B)
|
||||
MajorLinkerVersion = 0x30,
|
||||
MinorLinkerVersion = 0,
|
||||
SizeOfCode = (uint)webcilSectionHeaders[0].SizeOfRawData,
|
||||
SizeOfInitializedData = (uint)(webcilSectionHeaders[1].SizeOfRawData + webcilSectionHeaders[2].SizeOfRawData),
|
||||
SizeOfUninitializedData = 0,
|
||||
AddressOfEntryPoint = 0, // This can be set to 0
|
||||
BaseOfCode = 0x2000,
|
||||
BaseOfData = 0xA000,
|
||||
ImageBase = 0x400000, // The default value for applications is 0x00400000
|
||||
SectionAlignment = SectionAlignment,
|
||||
FileAlignment = FileAlignment,
|
||||
MajorOperatingSystemVersion = 4,
|
||||
MinorOperatingSystemVersion = 0,
|
||||
MajorImageVersion = 0,
|
||||
MinorImageVersion = 0,
|
||||
MajorSubsystemVersion = 4,
|
||||
MinorSubsystemVersion = 0,
|
||||
Win32VersionValue = 0,
|
||||
SizeOfImage = webcilSectionHeadersSizeOfRawData.RoundToNearest(SectionAlignment),
|
||||
SizeOfHeaders = GetSizeOfHeaders(DOSHeader, webcilSectionHeadersCount),
|
||||
CheckSum = 0,
|
||||
Subsystem = 3, // IMAGE_SUBSYSTEM_WINDOWS_CUI
|
||||
DllCharacteristics = 0x8560,
|
||||
SizeOfStackReserve = 0x100000,
|
||||
SizeOfStackCommit = 0x1000,
|
||||
SizeOfHeapReserve = 0x100000,
|
||||
SizeOfHeapCommit = 0x1000,
|
||||
LoaderFlags = 0,
|
||||
NumberOfRvaAndSizes = 0x10,
|
||||
DataDirectory = new IMAGE_DATA_DIRECTORY[]
|
||||
{
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_EXPORT
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_IMPORT (can be 0)
|
||||
new() { Size = (uint) webcilSectionHeaders[1].VirtualSize, VirtualAddress = (uint) webcilSectionHeaders[1].VirtualAddress }, // IMAGE_DIRECTORY_ENTRY_RESOURCE
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_EXCEPTION
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_SECURITY
|
||||
new() { Size = (uint) webcilSectionHeaders[2].VirtualSize, VirtualAddress = (uint) webcilSectionHeaders[2].VirtualAddress }, // IMAGE_DIRECTORY_ENTRY_BASERELOC
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_DEBUG (can be 0)
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_ARCHITECTURE
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_GLOBALPTR
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_TLS
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
|
||||
new() { Size = 0x0008, VirtualAddress = (uint) webcilSectionHeaders[0].VirtualAddress }, // IMAGE_DIRECTORY_ENTRY_IAT
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 }, // IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
|
||||
new() { Size = 0x0048, VirtualAddress = (uint) webcilSectionHeaders[0].VirtualAddress + 8 }, // TODO ??? IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
|
||||
new() { Size = 0x0000, VirtualAddress = 0x0000 } // ?
|
||||
}
|
||||
}
|
||||
};
|
||||
peStream.WriteStruct(IMAGE_NT_HEADERS32);
|
||||
|
||||
var textSectionHeader = new IMAGE_SECTION_HEADER
|
||||
{
|
||||
Name = SectionHeaderText,
|
||||
Misc = new IMAGE_SECTION_HEADER.UnionType { VirtualSize = (uint)webcilSectionHeaders[0].VirtualSize },
|
||||
VirtualAddress = (uint)webcilSectionHeaders[0].VirtualAddress,
|
||||
SizeOfRawData = (uint)webcilSectionHeaders[0].SizeOfRawData,
|
||||
PointerToRawData = webcilSectionHeaders[0].GetCorrectedPointerToRawData(pointerToRawDataOffsetBetweenWebcilAndPE),
|
||||
Characteristics = 0x60000020
|
||||
};
|
||||
peStream.WriteStruct(textSectionHeader);
|
||||
|
||||
var rsrcSectionHeader = new IMAGE_SECTION_HEADER
|
||||
{
|
||||
Name = SectionHeaderRsRc,
|
||||
Misc = new IMAGE_SECTION_HEADER.UnionType { VirtualSize = (uint)webcilSectionHeaders[1].VirtualSize },
|
||||
VirtualAddress = (uint)webcilSectionHeaders[1].VirtualAddress,
|
||||
SizeOfRawData = (uint)webcilSectionHeaders[1].SizeOfRawData,
|
||||
PointerToRawData = webcilSectionHeaders[1].GetCorrectedPointerToRawData(pointerToRawDataOffsetBetweenWebcilAndPE),
|
||||
Characteristics = 0x40000040
|
||||
};
|
||||
peStream.WriteStruct(rsrcSectionHeader);
|
||||
|
||||
var relocSectionHeader = new IMAGE_SECTION_HEADER
|
||||
{
|
||||
Name = SectionHeaderReloc,
|
||||
Misc = new IMAGE_SECTION_HEADER.UnionType { VirtualSize = (uint)webcilSectionHeaders[2].VirtualSize },
|
||||
VirtualAddress = (uint)webcilSectionHeaders[2].VirtualAddress,
|
||||
SizeOfRawData = (uint)webcilSectionHeaders[2].SizeOfRawData,
|
||||
PointerToRawData = webcilSectionHeaders[2].GetCorrectedPointerToRawData(pointerToRawDataOffsetBetweenWebcilAndPE),
|
||||
Characteristics = 0x42000040
|
||||
};
|
||||
peStream.WriteStruct(relocSectionHeader);
|
||||
|
||||
if (extraBytesAfterSections.Length > 0)
|
||||
{
|
||||
peStream.Write(extraBytesAfterSections);
|
||||
}
|
||||
|
||||
// Just copy all data
|
||||
foreach (var webcilSectionHeader in webcilSectionHeaders)
|
||||
{
|
||||
var buffer = new byte[webcilSectionHeader.SizeOfRawData];
|
||||
webcilStream.Seek(webcilSectionHeader.PointerToRawData, SeekOrigin.Begin);
|
||||
ReadExactly(webcilStream, buffer);
|
||||
|
||||
peStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
peStream.Flush();
|
||||
peStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
return peStream.ToArray();
|
||||
}
|
||||
|
||||
private static Webcil.WebcilHeader ReadHeader(Stream webcilStream)
|
||||
{
|
||||
var webcilHeader = ReadStructure<Webcil.WebcilHeader>(webcilStream);
|
||||
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
webcilHeader.version_major = BinaryPrimitives.ReverseEndianness(webcilHeader.version_major);
|
||||
webcilHeader.version_minor = BinaryPrimitives.ReverseEndianness(webcilHeader.version_minor);
|
||||
webcilHeader.coff_sections = BinaryPrimitives.ReverseEndianness(webcilHeader.coff_sections);
|
||||
webcilHeader.pe_cli_header_rva = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_cli_header_rva);
|
||||
webcilHeader.pe_cli_header_size = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_cli_header_size);
|
||||
webcilHeader.pe_debug_rva = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_debug_rva);
|
||||
webcilHeader.pe_debug_size = BinaryPrimitives.ReverseEndianness(webcilHeader.pe_debug_size);
|
||||
}
|
||||
|
||||
return webcilHeader;
|
||||
}
|
||||
|
||||
private static ImmutableArray<Webcil.WebcilSectionHeader> ReadSectionHeaders(Stream webcilStream, int sectionsHeaders)
|
||||
{
|
||||
var result = new List<Webcil.WebcilSectionHeader>();
|
||||
for (int i = 0; i < sectionsHeaders; i++)
|
||||
{
|
||||
result.Add(ReadSectionHeader(webcilStream));
|
||||
}
|
||||
|
||||
return ImmutableArray.Create(result.ToArray());
|
||||
}
|
||||
|
||||
private static Webcil.WebcilSectionHeader ReadSectionHeader(Stream webcilStream)
|
||||
{
|
||||
var sectionHeader = ReadStructure<Webcil.WebcilSectionHeader>(webcilStream);
|
||||
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
sectionHeader = new Webcil.WebcilSectionHeader
|
||||
(
|
||||
virtualSize: BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualSize),
|
||||
virtualAddress: BinaryPrimitives.ReverseEndianness(sectionHeader.VirtualAddress),
|
||||
sizeOfRawData: BinaryPrimitives.ReverseEndianness(sectionHeader.SizeOfRawData),
|
||||
pointerToRawData: BinaryPrimitives.ReverseEndianness(sectionHeader.PointerToRawData)
|
||||
);
|
||||
}
|
||||
|
||||
return sectionHeader;
|
||||
}
|
||||
|
||||
private static uint GetSizeOfHeaders(IMAGE_DOS_HEADER IMAGE_DOS_HEADER, int numSectionHeaders)
|
||||
{
|
||||
var soh = IMAGE_DOS_HEADER.FileAddressOfNewExeHeader + // e_lfanew member of IMAGE_DOS_HEADER
|
||||
sizeof(uint) + // 4 byte signature
|
||||
SizeofFileHeader +
|
||||
SizeofOptionalHeader + // size of optional header
|
||||
numSectionHeaders * SizeofSectionHeader // size of all section headers
|
||||
;
|
||||
|
||||
return (uint)soh.RoundToNearest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The low 32 bits of the time stamp of the image.
|
||||
/// This represents the date and time the image was created by the linker.
|
||||
/// The value is represented in the number of seconds elapsed since midnight (00:00:00), January 1, 1970, Universal Coordinated Time, according to the system clock.
|
||||
/// </summary>
|
||||
private static uint GetImageTimestamp()
|
||||
{
|
||||
// Calculate the total seconds since Unix epoch
|
||||
var totalSeconds = (DateTime.UtcNow - Epoch).Ticks / TimeSpan.TicksPerSecond;
|
||||
|
||||
// Convert to uint (low 32 bits)
|
||||
return (uint)totalSeconds;
|
||||
}
|
||||
|
||||
internal static int RoundToNearest(this int number, int nearest = 512)
|
||||
{
|
||||
int remainder = number % nearest;
|
||||
int halfNearest = nearest / 2;
|
||||
return remainder >= halfNearest ? number + nearest - remainder : number - remainder;
|
||||
}
|
||||
|
||||
internal static uint RoundToNearest(this uint number, uint nearest = 512)
|
||||
{
|
||||
uint remainder = number % nearest;
|
||||
uint halfNearest = nearest / 2;
|
||||
return remainder >= halfNearest ? number + nearest - remainder : number - remainder;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
private static T ReadStructure<T>(Stream s) where T : unmanaged
|
||||
{
|
||||
T structure = default;
|
||||
unsafe
|
||||
{
|
||||
byte* p = (byte*)&structure;
|
||||
Span<byte> buffer = new Span<byte>(p, sizeof(T));
|
||||
int read = s.Read(buffer);
|
||||
if (read != sizeof(T))
|
||||
{
|
||||
throw new InvalidOperationException("Couldn't read the full structure from the stream.");
|
||||
}
|
||||
}
|
||||
|
||||
return structure;
|
||||
}
|
||||
#else
|
||||
private static T ReadStructure<T>(Stream s) where T : unmanaged
|
||||
{
|
||||
int size = Marshal.SizeOf<T>();
|
||||
byte[] buffer = new byte[size];
|
||||
s.Read(buffer, 0, size);
|
||||
|
||||
IntPtr ptr = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
ptr = Marshal.AllocHGlobal(size);
|
||||
Marshal.Copy(buffer, 0, ptr, size);
|
||||
return Marshal.PtrToStructure<T>(ptr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER && !NET6_0
|
||||
private static void ReadExactly(Stream s, Span<byte> buffer)
|
||||
{
|
||||
s.ReadExactly(buffer);
|
||||
}
|
||||
#else
|
||||
private static void ReadExactly(Stream s, byte[] buffer)
|
||||
{
|
||||
int offset = 0;
|
||||
while (offset < buffer.Length)
|
||||
{
|
||||
int read = s.Read(buffer, offset, buffer.Length - offset);
|
||||
if (read == 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
internal static class WebcilSectionHeaderExtensions
|
||||
{
|
||||
internal static uint GetCorrectedPointerToRawData(this Webcil.WebcilSectionHeader webcilSectionHeader, int offset)
|
||||
{
|
||||
return (uint) (webcilSectionHeader.PointerToRawData + offset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Emits a simple WebAssembly wrapper module around a given webcil payload.
|
||||
//
|
||||
// The entire wasm module is going to be unchanging, except for the data section which has 2 passive
|
||||
// segments. segment 0 is 4 bytes and contains the length of the webcil payload. segment 1 is of a
|
||||
// variable size and contains the webcil payload.
|
||||
//
|
||||
// The unchanging parts are stored as a "prefix" and "suffix" which contain the bytes for the following
|
||||
// WAT module, split into the parts that come before the data section, and the bytes that come after:
|
||||
//
|
||||
// (module
|
||||
// (data "\0\00\00\00") ;; data segment 0: payload size as a 4 byte LE uint32
|
||||
// (data "webcil Payload\cc") ;; data segment 1: webcil payload
|
||||
// (memory (import "webcil" "memory") 1)
|
||||
// (global (export "webcilVersion") i32 (i32.const 0))
|
||||
// (func (export "getWebcilSize") (param $destPtr i32) (result)
|
||||
// local.get $destPtr
|
||||
// i32.const 0
|
||||
// i32.const 4
|
||||
// memory.init 0)
|
||||
// (func (export "getWebcilPayload") (param $d i32) (param $n i32) (result)
|
||||
// local.get $d
|
||||
// i32.const 0
|
||||
// local.get $n
|
||||
// memory.init 1))
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
public class WebcilWasmWrapper
|
||||
{
|
||||
private readonly Stream _webcilPayloadStream;
|
||||
private readonly uint _webcilPayloadSize;
|
||||
|
||||
public WebcilWasmWrapper(Stream webcilPayloadStream)
|
||||
{
|
||||
_webcilPayloadStream = webcilPayloadStream;
|
||||
long len = webcilPayloadStream.Length;
|
||||
if (len > (long)uint.MaxValue)
|
||||
throw new InvalidOperationException("webcil payload too large");
|
||||
_webcilPayloadSize = (uint)len;
|
||||
}
|
||||
|
||||
public void WriteWasmWrappedWebcil(Stream outputStream)
|
||||
{
|
||||
WriteWasmHeader(outputStream);
|
||||
using (var writer = new BinaryWriter(outputStream, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||
{
|
||||
WriteDataSection(writer);
|
||||
}
|
||||
WriteWasmSuffix(outputStream);
|
||||
}
|
||||
|
||||
//
|
||||
// Everything from the above wat module before the data section
|
||||
//
|
||||
// extracted by wasm-reader -s wrapper.wasm
|
||||
private static
|
||||
#if NET7_0_OR_GREATER
|
||||
ReadOnlyMemory<byte>
|
||||
#else
|
||||
byte[]
|
||||
#endif
|
||||
s_wasmWrapperPrefix = new byte[] {
|
||||
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x02, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x02, 0x7f, 0x7f, 0x00, 0x02, 0x12, 0x01, 0x06, 0x77, 0x65, 0x62, 0x63, 0x69, 0x6c, 0x06, 0x6d,
|
||||
0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x01, 0x03, 0x03, 0x02, 0x00, 0x01, 0x06, 0x0b, 0x02, 0x7f, 0x00, 0x41, 0x00, 0x0b, 0x7f, 0x00, 0x41, 0x00, 0x0b, 0x07, 0x41, 0x04, 0x0d, 0x77, 0x65,
|
||||
0x62, 0x63, 0x69, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x03, 0x00, 0x0a, 0x77, 0x65, 0x62, 0x63, 0x69, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x03, 0x01, 0x0d, 0x67, 0x65, 0x74, 0x57, 0x65,
|
||||
0x62, 0x63, 0x69, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x00, 0x00, 0x10, 0x67, 0x65, 0x74, 0x57, 0x65, 0x62, 0x63, 0x69, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x00, 0x01, 0x0c, 0x01, 0x02,
|
||||
0x0a, 0x1b, 0x02, 0x0c, 0x00, 0x20, 0x00, 0x41, 0x00, 0x41, 0x04, 0xfc, 0x08, 0x00, 0x00, 0x0b, 0x0c, 0x00, 0x20, 0x00, 0x41, 0x00, 0x20, 0x01, 0xfc, 0x08, 0x01, 0x00, 0x0b,
|
||||
};
|
||||
//
|
||||
// Everything from the above wat module after the data section
|
||||
//
|
||||
// extracted by wasm-reader -s wrapper.wasm
|
||||
private static
|
||||
#if NET7_0_OR_GREATER
|
||||
ReadOnlyMemory<byte>
|
||||
#else
|
||||
byte[]
|
||||
#endif
|
||||
s_wasmWrapperSuffix = new byte[] {
|
||||
0x00, 0x1b, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x02, 0x14, 0x02, 0x00, 0x01, 0x00, 0x07, 0x64, 0x65, 0x73, 0x74, 0x50, 0x74, 0x72, 0x01, 0x02, 0x00, 0x01, 0x64, 0x01, 0x01, 0x6e,
|
||||
};
|
||||
|
||||
private static void WriteWasmHeader(Stream outputStream)
|
||||
{
|
||||
#if NET7_0_OR_GREATER
|
||||
outputStream.Write(s_wasmWrapperPrefix.Span);
|
||||
#else
|
||||
outputStream.Write(s_wasmWrapperPrefix, 0, s_wasmWrapperPrefix.Length);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void WriteWasmSuffix(Stream outputStream)
|
||||
{
|
||||
#if NET7_0_OR_GREATER
|
||||
outputStream.Write(s_wasmWrapperSuffix.Span);
|
||||
#else
|
||||
outputStream.Write(s_wasmWrapperSuffix, 0, s_wasmWrapperSuffix.Length);
|
||||
#endif
|
||||
}
|
||||
|
||||
// 1 byte to encode "passive" data segment
|
||||
private const uint SegmentCodeSize = 1;
|
||||
|
||||
// Align the payload start to a 4-byte boundary within the wrapper. If the runtime reads the
|
||||
// payload directly, instead of by instantiatng the wasm module, we don't want the WebAssembly
|
||||
// prefix to push some of the values inside the image to odd byte offsets as the runtime assumes
|
||||
// the image will be aligned.
|
||||
//
|
||||
// There are requirements in ECMA-335 (Section II.25.4) that fat method headers and method data
|
||||
// sections be 4-byte aligned.
|
||||
private const uint WebcilPayloadInternalAlignment = 4;
|
||||
|
||||
private void WriteDataSection(BinaryWriter writer)
|
||||
{
|
||||
|
||||
uint dataSectionSize = 0;
|
||||
// uleb128 encoding of number of segments
|
||||
dataSectionSize += 1; // there's always 2 segments which encodes to 1 byte
|
||||
// compute the segment 0 size:
|
||||
// segment 0 has 1 byte segment code, 1 byte of size and at least 4 bytes of payload
|
||||
uint segment0MinimumSize = SegmentCodeSize + 1 + 4;
|
||||
dataSectionSize += segment0MinimumSize;
|
||||
|
||||
// encode webcil size as a uleb128
|
||||
byte[] ulebWebcilPayloadSize = ULEB128Encode(_webcilPayloadSize);
|
||||
|
||||
// compute the segment 1 size:
|
||||
// segment 1 has 1 byte segment code, a uleb128 encoding of the webcilPayloadSize, and the payload
|
||||
// don't count the size of the payload yet
|
||||
checked
|
||||
{
|
||||
dataSectionSize += SegmentCodeSize + (uint)ulebWebcilPayloadSize.Length;
|
||||
}
|
||||
|
||||
// at this point the data section size includes everything except the data section code, the data section size and the webcil payload itself
|
||||
// and any extra padding that we may want to add to segment 0.
|
||||
// So we can compute the offset of the payload within the wasm module.
|
||||
byte[] putativeULEBDataSectionSize = ULEB128Encode(dataSectionSize + _webcilPayloadSize);
|
||||
uint payloadOffset = (uint)s_wasmWrapperPrefix.Length + 1 + (uint)putativeULEBDataSectionSize.Length + dataSectionSize ;
|
||||
|
||||
uint paddingSize = PadTo(payloadOffset, WebcilPayloadInternalAlignment);
|
||||
|
||||
if (paddingSize > 0)
|
||||
{
|
||||
checked
|
||||
{
|
||||
dataSectionSize += paddingSize;
|
||||
}
|
||||
}
|
||||
|
||||
checked
|
||||
{
|
||||
dataSectionSize += _webcilPayloadSize;
|
||||
}
|
||||
|
||||
byte[] ulebSectionSize = ULEB128Encode(dataSectionSize);
|
||||
|
||||
if (putativeULEBDataSectionSize.Length != ulebSectionSize.Length)
|
||||
throw new InvalidOperationException ("adding padding would cause data section's encoded length to chane"); // TODO: fixme: there's upto one extra byte to encode the section length - take away a padding byte.
|
||||
writer.Write((byte)11); // section Data
|
||||
writer.Write(ulebSectionSize, 0, ulebSectionSize.Length);
|
||||
|
||||
writer.Write((byte)2); // number of segments
|
||||
|
||||
// write segment 0
|
||||
writer.Write((byte)1); // passive segment
|
||||
if (paddingSize + 4 > 127) {
|
||||
throw new InvalidOperationException ("padding would cause segment 0 to need a multi-byte ULEB128 size encoding");
|
||||
}
|
||||
writer.Write((byte)(4 + paddingSize)); // segment size: 4 plus any padding
|
||||
writer.Write((uint)_webcilPayloadSize); // payload is an unsigned 32 bit number
|
||||
for (int i = 0; i < paddingSize; i++)
|
||||
writer.Write((byte)0);
|
||||
|
||||
// write segment 1
|
||||
writer.Write((byte)1); // passive segment
|
||||
writer.Write(ulebWebcilPayloadSize, 0, ulebWebcilPayloadSize.Length); // segment size: _webcilPayloadSize
|
||||
if (writer.BaseStream.Position % WebcilPayloadInternalAlignment != 0) {
|
||||
throw new Exception ($"predited offset {payloadOffset}, actual position {writer.BaseStream.Position}");
|
||||
}
|
||||
_webcilPayloadStream.CopyTo(writer.BaseStream); // payload is the entire webcil content
|
||||
}
|
||||
|
||||
private static byte[] ULEB128Encode(uint value)
|
||||
{
|
||||
uint n = value;
|
||||
int len = 0;
|
||||
do
|
||||
{
|
||||
n >>= 7;
|
||||
len++;
|
||||
} while (n != 0);
|
||||
byte[] arr = new byte[len];
|
||||
int i = 0;
|
||||
n = value;
|
||||
do
|
||||
{
|
||||
byte b = (byte)(n & 0x7f);
|
||||
n >>= 7;
|
||||
if (n != 0)
|
||||
b |= 0x80;
|
||||
arr[i++] = b;
|
||||
} while (n != 0);
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static uint PadTo (uint value, uint align)
|
||||
{
|
||||
uint newValue = AlignTo(value, align);
|
||||
return newValue - value;
|
||||
}
|
||||
|
||||
private static uint AlignTo (uint value, uint align)
|
||||
{
|
||||
return (value + (align - 1)) & ~(align - 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user