Skip to content

Commit

Permalink
Enable simple KWAJ handling
Browse files Browse the repository at this point in the history
mnadareski committed Dec 11, 2024
1 parent 80375b6 commit 871d635
Showing 2 changed files with 153 additions and 16 deletions.
126 changes: 110 additions & 16 deletions SabreTools.Compression/SZDD/Decompressor.cs
Original file line number Diff line number Diff line change
@@ -19,29 +19,64 @@ public class Decompressor
private readonly BufferedStream _source;

/// <summary>
/// Offset within the window
/// SZDD format being decompressed
/// </summary>
private int _offset;
private Format _format;

#region Constructors

/// <summary>
/// Create a SZDD decompressor
/// </summary>
private Decompressor(Stream source, int offset)
private Decompressor(Stream source)
{
// Validate the inputs
if (source.Length == 0)
throw new ArgumentOutOfRangeException(nameof(source));
if (!source.CanRead)
throw new InvalidOperationException(nameof(source));
if (offset < 0 || offset > 4096)
throw new ArgumentOutOfRangeException(nameof(offset));

// Initialize the window with space characters
_window = Array.ConvertAll(_window, b => (byte)0x20);
_source = new BufferedStream(source);
_offset = 4096 - offset;
}

/// <summary>
/// Create a KWAJ decompressor
/// </summary>
public static Decompressor CreateKWAJ(byte[] source)
=> CreateKWAJ(new MemoryStream(source));

/// <summary>
/// Create a KWAJ decompressor
/// </summary>
/// TODO: Replace validation when Models is updated
public static Decompressor CreateKWAJ(Stream source)
{
// Create the decompressor
var decompressor = new Decompressor(source);

// Validate the header
byte[] magic = source.ReadBytes(8);
if (Encoding.ASCII.GetString(magic) != Encoding.ASCII.GetString([0x4B, 0x57, 0x41, 0x4A, 0x88, 0xF0, 0x27, 0xD1]))
throw new InvalidDataException(nameof(source));
ushort compressionType = source.ReadUInt16();
decompressor._format = compressionType switch
{
0 => Format.KWAJNoCompression,
1 => Format.KWAJXor,
2 => Format.KWAJQBasic,
3 => Format.KWAJLZH,
4 => Format.KWAJMSZIP,
_ => throw new IndexOutOfRangeException(nameof(source)),
};

// Skip the rest of the header
_ = source.ReadUInt16(); // DataOffset
_ = source.ReadUInt16(); // HeaderFlags

// Return the decompressor
return decompressor;
}

/// <summary>
@@ -57,7 +92,7 @@ public static Decompressor CreateQBasic(byte[] source)
public static Decompressor CreateQBasic(Stream source)
{
// Create the decompressor
var decompressor = new Decompressor(source, 18);
var decompressor = new Decompressor(source);

// Validate the header
byte[] magic = source.ReadBytes(8);
@@ -66,6 +101,9 @@ public static Decompressor CreateQBasic(Stream source)

// Skip the rest of the header
_ = source.ReadUInt32(); // RealLength

// Set the format and return
decompressor._format = Format.QBasic;
return decompressor;
}

@@ -82,7 +120,7 @@ public static Decompressor CreateSZDD(byte[] source)
public static Decompressor CreateSZDD(Stream source)
{
// Create the decompressor
var decompressor = new Decompressor(source, 16);
var decompressor = new Decompressor(source);

// Validate the header
byte[] magic = source.ReadBytes(8);
@@ -95,6 +133,9 @@ public static Decompressor CreateSZDD(Stream source)
// Skip the rest of the header
_ = source.ReadByteValue(); // LastChar
_ = source.ReadUInt32(); // RealLength

// Set the format and return
decompressor._format = Format.SZDD;
return decompressor;
}

@@ -104,6 +145,29 @@ public static Decompressor CreateSZDD(Stream source)
/// Decompress source data to an output stream
/// </summary>
public bool CopyTo(Stream dest)
{
// Ignore unwritable streams
if (!dest.CanWrite)
return false;

// Handle based on the format
return _format switch
{
Format.SZDD => DecompressSZDD(dest, 4096 - 16),
Format.QBasic => DecompressSZDD(dest, 4096 - 18),
Format.KWAJNoCompression => CopyKWAJ(dest, xor: false),
Format.KWAJXor => CopyKWAJ(dest, xor: true),
Format.KWAJQBasic => DecompressSZDD(dest, 4096 - 18),
Format.KWAJLZH => false,
Format.KWAJMSZIP => false,
_ => false,
};
}

/// <summary>
/// Decompress using SZDD
/// </summary>
private bool DecompressSZDD(Stream dest, int offset)
{
// Ignore unwritable streams
if (!dest.CanWrite)
@@ -128,12 +192,12 @@ public bool CopyTo(Stream dest)
break;

// Store the data in the window and write
_window[_offset] = literal.Value;
dest.WriteByte(_window[_offset]);
_window[offset] = literal.Value;
dest.WriteByte(_window[offset]);

// Set the next offset value
_offset++;
_offset &= 4095;
offset++;
offset &= 4095;
continue;
}

@@ -155,12 +219,12 @@ public bool CopyTo(Stream dest)
while (matchlen-- > 0)
{
// Copy the window value and write
_window[_offset] = _window[matchpos.Value];
dest.WriteByte(_window[_offset]);
_window[offset] = _window[matchpos.Value];
dest.WriteByte(_window[offset]);

// Set the next offset value
_offset++; matchpos++;
_offset &= 4095; matchpos &= 4095;
offset++; matchpos++;
offset &= 4095; matchpos &= 4095;
}
}
}
@@ -170,6 +234,36 @@ public bool CopyTo(Stream dest)
return true;
}

/// <summary>
/// Copy KWAJ data, optionally using XOR
/// </summary>
private bool CopyKWAJ(Stream dest, bool xor)
{
// Ignore unwritable streams
if (!dest.CanWrite)
return false;

// Loop and copy
while (true)
{
// Read the next byte
byte? next = _source.ReadNextByte();
if (next == null)
break;

// XOR with 0xFF if required
if (xor)
next = (byte)(next ^ 0xFF);

// Write the byte
dest.WriteByte(next.Value);
}

// Flush and return
dest.Flush();
return true;
}

/// <summary>
/// Buffered stream that reads in blocks
/// </summary>
43 changes: 43 additions & 0 deletions SabreTools.Compression/SZDD/Enums.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace SabreTools.Compression.SZDD
{
/// <summary>
/// Represents the SZDD format being decompressed
/// </summary>
internal enum Format
{
/// <summary>
/// Standard SZDD implementation
/// </summary>
SZDD,

/// <summary>
/// QBasic 4.5 installer variant
/// </summary>
QBasic,

/// <summary>
/// KWAJ variant, no compression
/// </summary>
KWAJNoCompression,

/// <summary>
/// KWAJ variant, XORed with 0xFF
/// </summary>
KWAJXor,

/// <summary>
/// KWAJ variant, QBasic variant compression
/// </summary>
KWAJQBasic,

/// <summary>
/// KWAJ variant, LZ + Huffman compression
/// </summary>
KWAJLZH,

/// <summary>
/// KWAJ variant, MS-ZIP compression
/// </summary>
KWAJMSZIP,
}
}

0 comments on commit 871d635

Please sign in to comment.