Skip to content

Instantly share code, notes, and snippets.

@parzivail
Created August 1, 2024 23:34
Show Gist options
  • Save parzivail/47ad3b922d2efbd669a496ced704687f to your computer and use it in GitHub Desktop.
Save parzivail/47ad3b922d2efbd669a496ced704687f to your computer and use it in GitHub Desktop.
Myst (Windows) WDIB to BMP
using System.Collections;
using System.Net;
namespace MystKit;
class Program
{
static void Main(string[] args)
{
foreach (var path in Directory.GetFiles(@"D:\myst\MYST_DAT", "*WDIB*.bin"))
{
Console.WriteLine(path);
using var fs = File.OpenRead(path);
var rle = new RunLengthCompressionStream(fs);
using var fsOut = File.Create(Path.Combine(@"D:\myst\MYST_BMP", Path.GetFileNameWithoutExtension(path) + ".bmp"));
rle.CopyTo(fsOut);
}
}
}
public class RunLengthCompressionStream : Stream
{
private readonly uint _length;
private readonly Stream _source;
private readonly RingBuffer _ringBuffer;
private long _position;
private byte _commandByte;
private int _commandBit = 8;
private int _runLength;
private int _runRingOffset;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => _length;
public override long Position
{
get => _position;
set => throw new NotSupportedException();
}
public RunLengthCompressionStream(Stream source, int ringBufferSize = 0x400)
{
_source = source;
_ringBuffer = new RingBuffer(ringBufferSize);
Span<byte> lengthBytes = stackalloc byte[4];
source.ReadExactly(lengthBytes);
_length = BitConverter.ToUInt32(lengthBytes);
}
public override void Flush()
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
Span<byte> runDataBuffer = stackalloc byte[2];
var startOffset = offset;
while (offset < startOffset + count)
{
if (_runLength > 0)
{
// If we have a non-zero run length, continue reading the run
_runLength--;
buffer[offset++] = _ringBuffer.Pop(_runRingOffset);
_runRingOffset++;
}
else
{
if (_commandBit == 8)
{
// The current command byte has been exhausted, read a new one
var commandByte = _source.ReadByte();
if (commandByte == -1)
break;
_commandByte = (byte)commandByte;
_commandBit = 0;
}
// Execute the next command
var command = _commandByte & (1 << _commandBit);
_commandBit++;
if (command == 0)
{
// read new run
if (_source.Read(runDataBuffer) != runDataBuffer.Length)
break;
_runLength = ((runDataBuffer[0] & 0b11111100) >> 2) + 3;
_runRingOffset = ((runDataBuffer[0] & 0b11) << 8) | runDataBuffer[1];
_runRingOffset += 0x42;
}
else
{
// read absolute byte
var rawByte = _source.ReadByte();
if (rawByte == -1)
break;
_ringBuffer.Push((byte)rawByte);
buffer[offset++] = (byte)rawByte;
}
}
}
var bytesRead = offset - startOffset;
_position += bytesRead;
return bytesRead;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
public class RingBuffer
{
private readonly byte[] _data;
private int _cursor;
public byte this[int offset]
{
get => _data[offset % _data.Length];
set => _data[offset % _data.Length] = value;
}
public RingBuffer(int size)
{
_data = new byte[size];
}
public void Push(byte value)
{
this[_cursor++] = value;
_cursor %= _data.Length;
}
public byte Pop(int index)
{
var value = this[index];
Push(value);
return value;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment