diff --git a/BurnOutSharp.Compression/Quantum/Decompressor.cs b/BurnOutSharp.Compression/Quantum/Decompressor.cs
index f9976180..4c8d2909 100644
--- a/BurnOutSharp.Compression/Quantum/Decompressor.cs
+++ b/BurnOutSharp.Compression/Quantum/Decompressor.cs
@@ -14,9 +14,9 @@ namespace BurnOutSharp.Compression.Quantum
///
/// Decompress a byte array using a given State
///
- public static bool Decompress(State state, int inlen, byte[] inbuf, int outlen, byte[] outbuf)
+ public static int Decompress(State state, int inlen, byte[] inbuf, int outlen, byte[] outbuf)
{
- int inpos = 0; // inbuf[0]
+ int inpos = 0, outpos = 0; // inbuf[0], outbuf[0]
int window = 0; // state.Window[0]
int runsrc, rundest;
uint windowPosition = state.WindowPosition;
@@ -26,81 +26,80 @@ namespace BurnOutSharp.Compression.Quantum
byte selector, sym;
uint matchOffset = 0;
+ // Make local copies of state variables
+ uint bitBuffer = state.BitBuffer;
+ int bitsLeft = state.BitsLeft;
+
ushort H = 0xFFFF, L = 0;
// Read initial value of C
- Q_INIT_BITSTREAM(out int bitsleft, out uint bitbuf);
- ushort C = (ushort)Q_READ_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ ushort C = (ushort)Q_READ_BITS(16, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
// Apply 2^x-1 mask
windowPosition &= windowSize - 1;
- // Runs can't straddle the window wraparound
- if ((windowPosition + togo) > windowSize)
- return false;
-
while (togo > 0)
{
- selector = (byte)GET_SYMBOL(state.SelectorModel, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ selector = (byte)GET_SYMBOL(state.SelectorModel, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
switch (selector)
{
// Selector 0 = literal model, 64 entries, 0x00-0x3F
case 0:
- sym = (byte)GET_SYMBOL(state.Model0, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ sym = (byte)GET_SYMBOL(state.Model0, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
state.Window[window + windowPosition++] = sym;
togo--;
break;
// Selector 1 = literal model, 64 entries, 0x40-0x7F
case 1:
- sym = (byte)GET_SYMBOL(state.Model1, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ sym = (byte)GET_SYMBOL(state.Model1, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
state.Window[window + windowPosition++] = sym;
togo--;
break;
// Selector 2 = literal model, 64 entries, 0x80-0xBF
case 2:
- sym = (byte)GET_SYMBOL(state.Model2, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ sym = (byte)GET_SYMBOL(state.Model2, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
state.Window[window + windowPosition++] = sym;
togo--;
break;
// Selector 3 = literal model, 64 entries, 0xC0-0xFF
case 3:
- sym = (byte)GET_SYMBOL(state.Model3, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ sym = (byte)GET_SYMBOL(state.Model3, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
state.Window[window + windowPosition++] = sym;
togo--;
break;
// Selector 4 = fixed length of 3
case 4:
- sym = (byte)GET_SYMBOL(state.Model4, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf);
- extra = (int)Q_READ_BITS(state.ExtraBits[sym], inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ sym = (byte)GET_SYMBOL(state.Model4, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
+ extra = (int)Q_READ_BITS(state.ExtraBits[sym], inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
matchOffset = (uint)(state.PositionSlotBases[sym] + extra + 1);
matchLength = 3;
break;
// Selector 5 = fixed length of 4
case 5:
- sym = (byte)GET_SYMBOL(state.Model5, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf);
- extra = (int)Q_READ_BITS(state.ExtraBits[sym], inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ sym = (byte)GET_SYMBOL(state.Model5, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
+ extra = (int)Q_READ_BITS(state.ExtraBits[sym], inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
matchOffset = (uint)(state.PositionSlotBases[sym] + extra + 1);
matchLength = 4;
break;
// Selector 6 = variable length
case 6:
- sym = (byte)GET_SYMBOL(state.Model6Length, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf);
- extra = (int)Q_READ_BITS(state.LengthExtraBits[sym], inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ sym = (byte)GET_SYMBOL(state.Model6Length, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
+ extra = (int)Q_READ_BITS(state.LengthExtraBits[sym], inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
matchLength = state.LengthBases[sym] + extra + 5;
- sym = (byte)GET_SYMBOL(state.Model6Position, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf);
- extra = (int)Q_READ_BITS(state.ExtraBits[sym], inbuf, ref inpos, ref bitsleft, ref bitbuf);
+ sym = (byte)GET_SYMBOL(state.Model6Position, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
+ extra = (int)Q_READ_BITS(state.ExtraBits[sym], inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
matchOffset = (uint)(state.PositionSlotBases[sym] + extra + 1);
break;
default:
- return false;
+ return inpos;
}
// If this is a match
@@ -138,17 +137,40 @@ namespace BurnOutSharp.Compression.Quantum
while (matchLength-- > 0)
{
state.Window[rundest++] = state.Window[runsrc++];
+
+ // Handle wraparounds that aren't supposed to happen
+ if (rundest >= state.Window.Length)
+ rundest = 0;
+ if (runsrc >= state.Window.Length)
+ runsrc = 0;
}
}
+
+ // If we hit the end of the window, copy to the output and wrap
+ if (windowPosition >= state.Window.Length)
+ {
+ Array.Copy(state.Window, 0, outbuf, outpos, Math.Min(windowSize, outlen));
+ outpos += (int)Math.Min(windowSize, outlen);
+ outlen -= (int)Math.Min(windowSize, outlen);
+ windowPosition = 0;
+ }
}
if (togo > 0)
- return false;
+ return inpos;
- Array.Copy(state.Window, (windowPosition == 0 ? windowSize : windowPosition) - outlen, outbuf, 0, outlen);
+ if (outlen > 0)
+ {
+ int sourceIndex = (int)((windowPosition == 0 ? windowSize : windowPosition) - outlen);
+ Array.Copy(state.Window, sourceIndex, outbuf, outpos, outlen);
+ }
+ // Cache the decompression state variables
+ state.BitBuffer = bitBuffer;
+ state.BitsLeft = bitsLeft;
state.WindowPosition = windowPosition;
- return true;
+
+ return inpos;
}
///
@@ -353,7 +375,7 @@ namespace BurnOutSharp.Compression.Quantum
///
private static void Q_FILL_BUFFER(byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
{
- if (bitsleft > 16)
+ if (bitsleft > 8)
return;
byte b0 = inpos + 0 < inbuf.Length ? inbuf[inpos + 0] : (byte)0;
diff --git a/BurnOutSharp.Compression/Quantum/State.cs b/BurnOutSharp.Compression/Quantum/State.cs
index efd621b4..55e3a1ce 100644
--- a/BurnOutSharp.Compression/Quantum/State.cs
+++ b/BurnOutSharp.Compression/Quantum/State.cs
@@ -175,5 +175,19 @@ namespace BurnOutSharp.Compression.Quantum
};
#endregion
+
+ #region Decompression State
+
+ ///
+ /// Bit buffer to persist between runs
+ ///
+ public uint BitBuffer = 0;
+
+ ///
+ /// Bits remaining to persist between runs
+ ///
+ public int BitsLeft = 0;
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/BurnOutSharp.Wrappers/Quantum.cs b/BurnOutSharp.Wrappers/Quantum.cs
index de19580f..ff637c71 100644
--- a/BurnOutSharp.Wrappers/Quantum.cs
+++ b/BurnOutSharp.Wrappers/Quantum.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using System.Linq;
+using BurnOutSharp.Compression.Quantum;
namespace BurnOutSharp.Wrappers
{
@@ -29,7 +31,7 @@ namespace BurnOutSharp.Wrappers
#endregion
- #region Files
+ #region File List
///
public Models.Quantum.FileDescriptor[] FileList => _archive.FileList;
@@ -144,10 +146,74 @@ namespace BurnOutSharp.Wrappers
return false;
// Get the file information
- var file = FileList[index];
+ var fileDescriptor = FileList[index];
- // TODO: Is it even possible to extract a single file?
+ // Read the entire compressed data
+ int compressedDataOffset = (int)CompressedDataOffset;
+ int compressedDataLength = GetEndOfFile() - compressedDataOffset;
+ byte[] compressedData = ReadFromDataSource(compressedDataOffset, compressedDataLength);
+
+ // TODO: Figure out decompression
+ // - Single-file archives seem to work
+ // - Single-file archives with files that span a window boundary seem to work
+ // - The first files in each archive seem to work
return false;
+
+ // // Setup the decompression state
+ // State state = new State();
+ // Decompressor.InitState(state, TableSize, CompressionFlags);
+
+ // // Decompress the entire array
+ // int decompressedDataLength = (int)FileList.Sum(fd => fd.ExpandedFileSize);
+ // byte[] decompressedData = new byte[decompressedDataLength];
+ // Decompressor.Decompress(state, compressedData.Length, compressedData, decompressedData.Length, decompressedData);
+
+ // // Read the data
+ // int offset = (int)FileList.Take(index).Sum(fd => fd.ExpandedFileSize);
+ // byte[] data = new byte[fileDescriptor.ExpandedFileSize];
+ // Array.Copy(decompressedData, offset, data, 0, data.Length);
+
+ // // Loop through all files before the current
+ // for (int i = 0; i < index; i++)
+ // {
+ // // Decompress the next block of data
+ // byte[] tempData = new byte[FileList[i].ExpandedFileSize];
+ // int lastRead = Decompressor.Decompress(state, compressedData.Length, compressedData, tempData.Length, tempData);
+ // compressedData = new ReadOnlySpan(compressedData, (lastRead), compressedData.Length - (lastRead)).ToArray();
+ // }
+
+ // // Read the data
+ // byte[] data = new byte[fileDescriptor.ExpandedFileSize];
+ // _ = Decompressor.Decompress(state, compressedData.Length, compressedData, data.Length, data);
+
+ // // Create the filename
+ // string filename = fileDescriptor.FileName;
+
+ // // If we have an invalid output directory
+ // if (string.IsNullOrWhiteSpace(outputDirectory))
+ // return false;
+
+ // // Create the full output path
+ // filename = Path.Combine(outputDirectory, filename);
+
+ // // Ensure the output directory is created
+ // Directory.CreateDirectory(Path.GetDirectoryName(filename));
+
+ // // Try to write the data
+ // try
+ // {
+ // // Open the output file for writing
+ // using (Stream fs = File.OpenWrite(filename))
+ // {
+ // fs.Write(data, 0, data.Length);
+ // }
+ // }
+ // catch
+ // {
+ // return false;
+ // }
+
+ return true;
}
#endregion