154 lines
5.1 KiB
C#
154 lines
5.1 KiB
C#
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();
|
|
}
|
|
} |