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