From b17f8dac7ad0b8d1e0666190c4855c5f39f01678 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 2 Jan 2023 10:17:27 -0800 Subject: [PATCH] Start fixing Quantum with safeguards, hook up --- .../Quantum/Decompressor.cs | 123 ++++++++++++++---- .../Compression/Quantum/Model.cs | 2 +- BurnOutSharp.Wrappers/MicrosoftCabinet.cs | 10 +- 3 files changed, 104 insertions(+), 31 deletions(-) diff --git a/BurnOutSharp.Compression/Quantum/Decompressor.cs b/BurnOutSharp.Compression/Quantum/Decompressor.cs index 5de1b25e..e5b99d8c 100644 --- a/BurnOutSharp.Compression/Quantum/Decompressor.cs +++ b/BurnOutSharp.Compression/Quantum/Decompressor.cs @@ -1,14 +1,54 @@ using System; using System.Linq; using BurnOutSharp.Models.Compression.Quantum; +using BurnOutSharp.Models.MicrosoftCabinet; namespace BurnOutSharp.Compression.Quantum { /// /// /// - public class Decompressor + /// + public static class Decompressor { + /// + /// Decompress a data block using a given state + /// + public static byte[] Decompress(CFFOLDER folder, CFDATA dataBlock) + { + // If we have an invalid folder + if (folder == null) + return null; + + // If we have an invalid data block + if (dataBlock?.CompressedData == null) + { + // Corrupt blocks will show as size 0 + int compressedSize = dataBlock?.CompressedSize ?? 0; + if (compressedSize == 0) + compressedSize = 32768; + + return new byte[compressedSize]; + } + + // Setup the decompression state + State state = new State(); + if (!InitState(state, folder)) + return new byte[dataBlock.UncompressedSize]; + + // Setup the decompression variables + int inlen = dataBlock.CompressedSize; + byte[] inbuf = dataBlock.CompressedData; + int outlen = dataBlock.UncompressedSize; + byte[] outbuf = new byte[outlen]; + + // Perform the decompression, if possible + if (Decompress(state, inlen, inbuf, outlen, outbuf)) + return outbuf; + else + return new byte[outlen]; + } + /// /// Decompress a byte array using a given State /// @@ -39,24 +79,35 @@ namespace BurnOutSharp.Compression.Quantum while (togo > 0) { + // If we have more requested bytes than we have data + if (inpos >= inbuf.Length - 1) + break; + selector = (byte)GET_SYMBOL(state.Model7, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf); switch (selector) { + // Selector 0 = literal model, 64 entries, 0x00-0x3F case 0: sym = (byte)GET_SYMBOL(state.Model7Submodel00, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf); state.Window[window + window_posn++] = sym; togo--; break; + + // Selector 1 = literal model, 64 entries, 0x40-0x7F case 1: sym = (byte)GET_SYMBOL(state.Model7Submodel40, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf); state.Window[window + window_posn++] = sym; togo--; break; + + // Selector 2 = literal model, 64 entries, 0x80-0xBF case 2: sym = (byte)GET_SYMBOL(state.Model7Submodel80, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf); state.Window[window + window_posn++] = sym; togo--; break; + + // Selector 3 = literal model, 64 entries, 0xC0-0xFF case 3: sym = (byte)GET_SYMBOL(state.Model7SubmodelC0, ref H, ref L, ref C, inbuf, ref inpos, ref bitsleft, ref bitbuf); state.Window[window + window_posn++] = sym; @@ -93,7 +144,7 @@ namespace BurnOutSharp.Compression.Quantum return false; } - // If this is a match */ + // If this is a match if (selector >= 4) { rundest = (int)(window + window_posn); @@ -144,7 +195,17 @@ namespace BurnOutSharp.Compression.Quantum /// /// Initialize a Quantum decompressor state /// - public static bool InitState(int window, int level, State state) + public static bool InitState(State state, CFFOLDER folder) + { + int window = ((ushort)folder.CompressionType >> 8) & 0x1f; + int level = ((ushort)folder.CompressionType >> 4) & 0xF; + return InitState(state, window, level); + } + + /// + /// Initialize a Quantum decompressor state + /// + public static bool InitState(State state, int window, int level) { uint windowSize = (uint)(1 << window); int msz = window * 2, i; @@ -184,22 +245,22 @@ namespace BurnOutSharp.Compression.Quantum } // Initialize arithmetic coding models - InitModel(state.Model7, state.Model7Symbols, 7, 0); + state.Model7 = CreateModel(state.Model7Symbols, 7, 0); - InitModel(state.Model7Submodel00, state.Model7Submodel00Symbols, 0x40, 0x00); - InitModel(state.Model7Submodel40, state.Model7Submodel40Symbols, 0x40, 0x40); - InitModel(state.Model7Submodel80, state.Model7Submodel80Symbols, 0x40, 0x80); - InitModel(state.Model7SubmodelC0, state.Model7SubmodelC0Symbols, 0x40, 0xC0); + state.Model7Submodel00 = CreateModel(state.Model7Submodel00Symbols, 0x40, 0x00); + state.Model7Submodel40 = CreateModel(state.Model7Submodel40Symbols, 0x40, 0x40); + state.Model7Submodel80 = CreateModel(state.Model7Submodel80Symbols, 0x40, 0x80); + state.Model7SubmodelC0 = CreateModel(state.Model7SubmodelC0Symbols, 0x40, 0xC0); // Model 4 depends on table size, ranges from 20 to 24 - InitModel(state.Model4, state.Model4Symbols, (msz < 24) ? msz : 24, 0); + state.Model4 = CreateModel(state.Model4Symbols, (msz < 24) ? msz : 24, 0); // Model 5 depends on table size, ranges from 20 to 36 - InitModel(state.Model5, symbols: state.Model5Symbols, (msz < 36) ? msz : 36, 0); + state.Model5 = CreateModel(symbols: state.Model5Symbols, (msz < 36) ? msz : 36, 0); // Model 6 Position depends on table size, ranges from 20 to 42 - InitModel(state.Model6Position, state.Model6PositionSymbols, msz, 0); - InitModel(state.Model6Length, state.Model6LengthSymbols, 27, 0); + state.Model6Position = CreateModel(state.Model6PositionSymbols, msz, 0); + state.Model6Length = CreateModel(state.Model6LengthSymbols, 27, 0); return true; } @@ -208,12 +269,15 @@ namespace BurnOutSharp.Compression.Quantum /// Initialize a Quantum model that decodes symbols from s to (s + n - 1) /// /// - public static void InitModel(Model model, ModelSymbol[] symbols, int entryCount, int initialSymbol) + private static Model CreateModel(ModelSymbol[] symbols, int entryCount, int initialSymbol) { // Set the basic values - model.ShiftsLeft = 4; - model.Entries = entryCount; - model.Symbols = symbols; + Model model = new Model + { + TimeToReorder = 4, + Entries = entryCount, + Symbols = symbols, + }; // Clear out the look-up table model.LookupTable = Enumerable.Repeat(0xFF, model.LookupTable.Length).ToArray(); @@ -224,22 +288,24 @@ namespace BurnOutSharp.Compression.Quantum // Set up a look-up entry for symbol model.LookupTable[i + initialSymbol] = i; - // Actual symbol - model.Symbols[i].Symbol = (ushort)(i + initialSymbol); - - // Current frequency of that symbol - model.Symbols[i].CumulativeFrequency = (ushort)(entryCount - i); + // Create the symbol in the table + model.Symbols[i] = new ModelSymbol + { + Symbol = (ushort)(i + initialSymbol), + CumulativeFrequency = (ushort)(entryCount - i), + }; } // Set the last symbol frequency to 0 - model.Symbols[entryCount].CumulativeFrequency = 0; + model.Symbols[entryCount] = new ModelSymbol { CumulativeFrequency = 0 }; + return model; } /// /// Update the quantum model for a particular symbol /// /// - public static void UpdateModel(Model model, int symbol) + private static void UpdateModel(Model model, int symbol) { // Update the cumulative frequency for all symbols less than the provided for (int i = 0; i < symbol; i++) @@ -252,7 +318,7 @@ namespace BurnOutSharp.Compression.Quantum return; // If we have more than 1 shift left in the model - if (--model.ShiftsLeft != 0) + if (--model.TimeToReorder != 0) { // Loop through the entries from highest to lowest, // performing the shift on the cumulative frequencies @@ -269,7 +335,7 @@ namespace BurnOutSharp.Compression.Quantum else { // Reset the shifts left value to 50 - model.ShiftsLeft = 50; + model.TimeToReorder = 50; // Loop through the entries setting the cumulative frequencies for (int i = 0; i < model.Entries; i++) @@ -512,12 +578,17 @@ namespace BurnOutSharp.Compression.Quantum } L <<= 1; H = (ushort)((H << 1) | 1); + + // If we have more requested bytes than we have data + if (inpos >= inbuf.Length - 1) + break; + Q_FILL_BUFFER(inbuf, ref inpos, ref bitsleft, ref bitbuf); C = (ushort)((C << 1) | Q_PEEK_BITS(1, bitbuf)); Q_REMOVE_BITS(1, ref bitsleft, ref bitbuf); } - Decompressor.UpdateModel(model, i); + UpdateModel(model, i); return v; } diff --git a/BurnOutSharp.Models/Compression/Quantum/Model.cs b/BurnOutSharp.Models/Compression/Quantum/Model.cs index de9471c4..c8e10b81 100644 --- a/BurnOutSharp.Models/Compression/Quantum/Model.cs +++ b/BurnOutSharp.Models/Compression/Quantum/Model.cs @@ -4,7 +4,7 @@ namespace BurnOutSharp.Models.Compression.Quantum /// public sealed class Model { - public int ShiftsLeft; + public int TimeToReorder; public int Entries; diff --git a/BurnOutSharp.Wrappers/MicrosoftCabinet.cs b/BurnOutSharp.Wrappers/MicrosoftCabinet.cs index b9e0934a..b6178bf5 100644 --- a/BurnOutSharp.Wrappers/MicrosoftCabinet.cs +++ b/BurnOutSharp.Wrappers/MicrosoftCabinet.cs @@ -232,9 +232,7 @@ namespace BurnOutSharp.Wrappers decompressed = mszip.DecompressMSZIPData(dataBlock.CompressedData, hasLastBlock); break; case Models.MicrosoftCabinet.CompressionType.TYPE_QUANTUM: - // TODO: UNIMPLEMENTED - //decompressed = dataBlock.CompressedData; - decompressed = null; + decompressed = Compression.Quantum.Decompressor.Decompress(folder, dataBlock); break; case Models.MicrosoftCabinet.CompressionType.TYPE_LZX: // TODO: UNIMPLEMENTED @@ -379,8 +377,12 @@ namespace BurnOutSharp.Wrappers if (folderData == null || folderData.Length == 0) return null; - // Get the segment that represents this file + // Create the output file data byte[] fileData = new byte[file.FileSize]; + if (fileData.Length < file.FolderStartOffset + file.FileSize) + return null; + + // Get the segment that represents this file Array.Copy(folderData, file.FolderStartOffset, fileData, 0, file.FileSize); return fileData; }