diff --git a/BurnOutSharp.Compression/LZ.cs b/BurnOutSharp.Compression/LZ.cs index 788fd23a..1a235c71 100644 --- a/BurnOutSharp.Compression/LZ.cs +++ b/BurnOutSharp.Compression/LZ.cs @@ -11,430 +11,95 @@ namespace BurnOutSharp.Compression /// public class LZ { - /// - /// LZStart (KERNEL32.@) - /// - public LZERROR Start() => LZERROR.LZERROR_OK; + #region Constructors /// - /// Initializes internal decompression buffers, returns lzfiledescriptor. - /// (return value the same as hfSrc, if hfSrc is not compressed) - /// on failure, returns error code <0 - /// lzfiledescriptors range from 0x400 to 0x410 (only 16 open files per process) - /// - /// since _llseek uses the same types as libc.lseek, we just use the macros of - /// libc + /// Constructor /// - public LZERROR Init(Stream hfSrc, out State lzs) + public LZ() { } + + #endregion + + #region Static Methods + + /// + /// Decompress LZ-compressed data + /// + /// Byte array representing the compressed data + /// Decompressed data as a byte array, null on error + public static byte[] Decompress(byte[] compressed) { - LZERROR ret = ReadHeader(hfSrc, out FileHeaader head); - if (ret != LZERROR.LZERROR_OK) - { - hfSrc.Seek(0, SeekOrigin.Begin); - if (ret == LZERROR.LZERROR_NOT_LZ) - { - lzs = new State { RealFD = hfSrc }; - return ret; - } - else - { - lzs = null; - return ret; - } - } + // If we have and invalid input + if (compressed == null || compressed.Length == 0) + return null; - lzs = new State(); - - lzs.RealFD = hfSrc; - lzs.LastChar = head.LastChar; - lzs.RealLength = head.RealLength; - - lzs.Get = new byte[GETLEN]; - lzs.GetLen = 0; - lzs.GetCur = 0; - - // Yes, preinitialize with spaces - lzs.Table = Enumerable.Repeat((byte)' ', LZ_TABLE_SIZE).ToArray(); - - // Yes, start 16 byte from the END of the table - lzs.CurTabEnt = 0xff0; - - return LZERROR.LZERROR_OK; + // Create a memory stream for the input and decompress that + var compressedStream = new MemoryStream(compressed); + return Decompress(compressedStream); } /// - /// LZDone (KERNEL32.@) + /// Decompress LZ-compressed data /// - public void Done() { } - - /// - /// gets the full filename of the compressed file 'in' by opening it - /// and reading the header - /// - /// "file." is being translated to "file" - /// "file.bl_" (with lastchar 'a') is being translated to "file.bla" - /// "FILE.BL_" (with lastchar 'a') is being translated to "FILE.BLA" - /// - public LZERROR GetExpandedName(string input, out string output) + /// Stream representing the compressed data + /// Decompressed data as a byte array, null on error + public static byte[] Decompress(Stream compressed) { - output = null; + // If we have and invalid input + if (compressed == null || compressed.Length == 0) + return null; - State state = OpenFile(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - if (state.Get == null) - return LZERROR.LZERROR_NOT_LZ; + // Create a new LZ for decompression + var lz = new LZ(); - string inputExtension = Path.GetExtension(input).TrimStart('.'); - if (string.IsNullOrWhiteSpace(inputExtension)) + // Open the input data + var sourceState = lz.Open(compressed, out _); + if (sourceState?.Window == null) + return null; + + // Create the output data and open it + var decompressedStream = new MemoryStream(); + var destState = lz.Open(decompressedStream, out _); + if (destState == null) + return null; + + // Decompress the data by copying + long read = lz.CopyTo(sourceState, destState, out LZERROR error); + + // Copy the data to the buffer + byte[] decompressed; + if (read == 0 || (error != LZERROR.LZERROR_OK && error != LZERROR.LZERROR_NOT_LZ)) { - output = Path.GetFileNameWithoutExtension(input); - return LZERROR.LZERROR_OK; + decompressed = null; + } + else + { + int dataEnd = (int)decompressedStream.Position; + decompressedStream.Seek(0, SeekOrigin.Begin); + decompressed = decompressedStream.ReadBytes(dataEnd); } - if (inputExtension.Length == 1) - { - if (inputExtension == "_") - { - output = $"{Path.GetFileNameWithoutExtension(input)}.{char.ToLower(state.LastChar)}"; - return LZERROR.LZERROR_OK; - } - else - { - output = Path.GetFileNameWithoutExtension(input); - return LZERROR.LZERROR_OK; - } - } + // Close the streams + lz.Close(sourceState); + lz.Close(destState); - if (!inputExtension.EndsWith("_")) - { - output = Path.GetFileNameWithoutExtension(input); - return LZERROR.LZERROR_OK; - } - - bool isLowerCase = char.IsUpper(output[0]); - char replacementChar = isLowerCase ? char.ToLower(state.LastChar) : char.ToUpper(state.LastChar); - string outputExtension = inputExtension.Substring(0, inputExtension.Length - 1) + replacementChar; - output = $"{Path.GetFileNameWithoutExtension(input)}.{outputExtension}"; - return LZERROR.LZERROR_OK; + return decompressed; } - /// - /// LZRead (KERNEL32.@) - /// - public int Read(State lzs, byte[] vbuf, int toread) - { - int howmuch; - byte b; - int buf = 0; // vbufPtr + #endregion - howmuch = toread; - - // If we have an uncompressed stream - if (lzs.Get == null) - return lzs.RealFD.Read(vbuf, 0, toread); - - // The decompressor itself is in a define, cause we need it twice - // in this function. (the decompressed byte will be in b) - // DECOMPRESS_ONE_BYTE - // { - // if (lzs.StringLen != 0) - // { - // b = lzs.Table[lzs.StringPos]; - // lzs.StringPos = (lzs.StringPos + 1) & 0xFFF; - // lzs.StringLen--; - // } - // else - // { - // if ((lzs.ByteType & 0x100) == 0) - // { - // if (Get(lzs, out b) != LZERROR.LZERROR_OK) - // return toread - howmuch; - - // lzs.ByteType = (ushort)(b | 0xFF00); - // } - // if ((lzs.ByteType & 1) != 0) - // { - // if (Get(lzs, out b) != LZERROR.LZERROR_OK) - // return toread - howmuch; - // } - // else - // { - // if (Get(lzs, out byte b1) != LZERROR.LZERROR_OK) - // return toread - howmuch; - // if (Get(lzs, out byte b2) != LZERROR.LZERROR_OK) - // return toread - howmuch; - - // // Format: - // // b1 b2 - // // AB CD - // // where CAB is the stringoffset in the table - // // and D+3 is the len of the string - // lzs.StringPos = (uint)(b1 | ((b2 & 0xf0) << 4)); - // lzs.StringLen = (byte)((b2 & 0xf) + 2); - - // // 3, but we use a byte already below... - // b = lzs.Table[lzs.StringPos]; - // lzs.StringPos = (lzs.StringPos + 1) & 0xFFF; - // } - - // lzs.ByteType >>= 1; - // } - - // // Store b in table - // lzs.Table[lzs.CurTabEnt++] = b; - // lzs.CurTabEnt &= 0xFFF; - // lzs.RealCurrent++; - // } - - // If someone has seeked, we have to bring the decompressor to that position - if (lzs.RealCurrent != lzs.RealWanted) - { - // If the wanted position is before the current position - // I see no easy way to unroll .. We have to restart at - // the beginning. *sigh* - if (lzs.RealCurrent > lzs.RealWanted) - { - // Flush decompressor state - lzs.RealFD.Seek(LZ_HEADER_LEN, SeekOrigin.Begin); - GetFlush(lzs); - lzs.RealCurrent = 0; - lzs.ByteType = 0; - lzs.StringLen = 0; - lzs.Table = Enumerable.Repeat((byte)' ', LZ_TABLE_SIZE).ToArray(); - lzs.CurTabEnt = 0xFF0; - } - - while (lzs.RealCurrent < lzs.RealWanted) - { - // DECOMPRESS_ONE_BYTE -- TODO: Make into helper method; original code is a DEFINE - if (lzs.StringLen != 0) - { - b = lzs.Table[lzs.StringPos]; - lzs.StringPos = (lzs.StringPos + 1) & 0xFFF; - lzs.StringLen--; - } - else - { - if ((lzs.ByteType & 0x100) == 0) - { - if (Get(lzs, out b) != LZERROR.LZERROR_OK) - return toread - howmuch; - - lzs.ByteType = (ushort)(b | 0xFF00); - } - if ((lzs.ByteType & 1) != 0) - { - if (Get(lzs, out b) != LZERROR.LZERROR_OK) - return toread - howmuch; - } - else - { - if (Get(lzs, out byte b1) != LZERROR.LZERROR_OK) - return toread - howmuch; - if (Get(lzs, out byte b2) != LZERROR.LZERROR_OK) - return toread - howmuch; - - // Format: - // b1 b2 - // AB CD - // where CAB is the stringoffset in the table - // and D+3 is the len of the string - lzs.StringPos = (uint)(b1 | ((b2 & 0xf0) << 4)); - lzs.StringLen = (byte)((b2 & 0xf) + 2); - - // 3, but we use a byte already below... - b = lzs.Table[lzs.StringPos]; - lzs.StringPos = (lzs.StringPos + 1) & 0xFFF; - } - - lzs.ByteType >>= 1; - } - - // Store b in table - lzs.Table[lzs.CurTabEnt++] = b; - lzs.CurTabEnt &= 0xFFF; - lzs.RealCurrent++; - } - } - - while (howmuch > 0) - { - // DECOMPRESS_ONE_BYTE -- TODO: Make into helper method; original code is a DEFINE - if (lzs.StringLen != 0) - { - b = lzs.Table[lzs.StringPos]; - lzs.StringPos = (lzs.StringPos + 1) & 0xFFF; - lzs.StringLen--; - } - else - { - if ((lzs.ByteType & 0x100) == 0) - { - if (Get(lzs, out b) != LZERROR.LZERROR_OK) - return toread - howmuch; - - lzs.ByteType = (ushort)(b | 0xFF00); - } - if ((lzs.ByteType & 1) != 0) - { - if (Get(lzs, out b) != LZERROR.LZERROR_OK) - return toread - howmuch; - } - else - { - if (Get(lzs, out byte b1) != LZERROR.LZERROR_OK) - return toread - howmuch; - if (Get(lzs, out byte b2) != LZERROR.LZERROR_OK) - return toread - howmuch; - - // Format: - // b1 b2 - // AB CD - // where CAB is the stringoffset in the table - // and D+3 is the len of the string - lzs.StringPos = (uint)(b1 | ((b2 & 0xf0) << 4)); - lzs.StringLen = (byte)((b2 & 0xf) + 2); - - // 3, but we use a byte already below... - b = lzs.Table[lzs.StringPos]; - lzs.StringPos = (lzs.StringPos + 1) & 0xFFF; - } - - lzs.ByteType >>= 1; - } - - // Store b in table - lzs.Table[lzs.CurTabEnt++] = b; - lzs.CurTabEnt &= 0xFFF; - lzs.RealCurrent++; - - lzs.RealWanted++; - vbuf[buf++] = b; - howmuch--; - } - - return toread; - } + #region State Management /// - /// LZSeek (KERNEL32.@) + /// Opens a stream and creates a state from it /// - public long Seek(State lzs, long offset, SeekOrigin type) + /// Source stream to create a state from + /// Output representing the last error + /// An initialized State, null on error + /// Uncompressed streams are represented by a State with no buffer + public State Open(Stream stream, out LZERROR error) { - // Not compressed? Just use normal Seek - if (lzs.Get == null) - return lzs.RealFD.Seek(offset, type); - - long newwanted = lzs.RealWanted; - switch (type) - { - case SeekOrigin.Current: - newwanted += offset; - break; - case SeekOrigin.End: - newwanted = lzs.RealLength - offset; - break; - default: - newwanted = offset; - break; - } - - if (newwanted > lzs.RealLength) - return (long)LZERROR.LZERROR_BADVALUE; - if (newwanted < 0) - return (long)LZERROR.LZERROR_BADVALUE; - - lzs.RealWanted = (uint)newwanted; - return newwanted; - } - - /// - /// Copies everything from src to dest - /// if src is a LZ compressed file, it will be uncompressed. - /// will return the number of bytes written to dest or errors. - /// - public long Copy(State src, State dest) - { - int usedlzinit = 0, ret, wret; - State oldsrc = src; - Stream srcfd; - DateTime filetime; - State lzs; - - const int BUFLEN = 1000; - byte[] buf = new byte[BUFLEN]; - - // we need that weird typedef, for i can't seem to get function pointer - // casts right. (Or they probably just do not like WINAPI in general) - //typedef UINT(WINAPI * _readfun)(HFILE, LPVOID, UINT); - - if (src.Get == null) - { - LZERROR err = Init(src.RealFD, out src); - if (err < LZERROR.LZERROR_NOT_LZ) - return (long)LZERROR.LZERROR_NOT_LZ; - - usedlzinit = 1; - } - - // Not compressed? just copy - if (src.Get == null) - { - src.RealFD.CopyTo(dest.RealFD); - return src.RealFD.Length; - } - - long len = 0; - while (true) - { - ret = Read(src, buf, BUFLEN); - if (ret <= 0) - { - if (ret == (int)LZERROR.LZERROR_NOT_LZ) - break; - if (ret == (int)LZERROR.LZERROR_BADINHANDLE) - return (long)LZERROR.LZERROR_READ; - return ret; - } - - len += ret; - dest.RealFD.Write(buf, 0, ret); - } - - // Maintain the timestamp of the source file to the destination file - // srcfd = (!(lzs = GET_LZ_STATE(src))) ? src : lzs->realfd; - // GetFileTime( LongToHandle(srcfd), NULL, NULL, &filetime ); - // SetFileTime( LongToHandle(dest), NULL, NULL, &filetime ); - - // Close handle - if (usedlzinit != 0) - Close(src); - - return len; - } - - /// - /// Opens a file. If not compressed, open it as a normal file. - /// - public State OpenFile(string fileName, FileMode mode, FileAccess access, FileShare fileShare = FileShare.ReadWrite) - { - try - { - Stream stream = File.Open(fileName, mode, access, fileShare); - LZERROR error = Init(stream, out State lzs); - if (error == LZERROR.LZERROR_OK || error == LZERROR.LZERROR_NOT_LZ) - return lzs; - } - catch { } - - return null; - } - - /// - /// Opens a file. If not compressed, open it as a normal file. - /// - public State OpenFile(Stream stream) - { - LZERROR error = Init(stream, out State lzs); + State lzs = Init(stream, out error); if (error == LZERROR.LZERROR_OK || error == LZERROR.LZERROR_NOT_LZ) return lzs; @@ -442,71 +107,424 @@ namespace BurnOutSharp.Compression } /// - /// Closes a state + /// Closes a state by invalidating the source /// + /// State object to close public void Close(State state) { - // TODO: Figure out if this is effectively all - state?.RealFD?.Close(); + try + { + state?.Source?.Close(); + } + catch { } + } + + /// + /// Initializes internal decompression buffers + /// + /// Input stream to create a state from + /// Output representing the last error + /// An initialized State, null on error + /// Uncompressed streams are represented by a State with no buffer + public State Init(Stream source, out LZERROR error) + { + // If we have an invalid source + if (source == null) + { + error = LZERROR.LZERROR_BADVALUE; + return null; + } + + // Attempt to read the header + var fileHeader = ParseFileHeader(source, out error); + + // If we had a valid but uncompressed stream + if (error == LZERROR.LZERROR_NOT_LZ) + { + source.Seek(0, SeekOrigin.Begin); + return new State { Source = source }; + } + + // If we had any error + else if (error != LZERROR.LZERROR_OK) + { + source.Seek(0, SeekOrigin.Begin); + return null; + } + + // Initialize the table with all spaces + byte[] table = Enumerable.Repeat((byte)' ', LZ_TABLE_SIZE).ToArray(); + + // Build the state + var state = new State + { + Source = source, + LastChar = fileHeader.LastChar, + RealLength = fileHeader.RealLength, + + Window = new byte[GETLEN], + WindowLength = 0, + WindowCurrent = 0, + + Table = table, + CurrentTableEntry = 0xff0, + }; + + // Return the state + return state; + } + + #endregion + + #region File Name Handling + + /// + /// Reconstructs the full filename of the compressed file + /// + public string GetExpandedName(string input, out LZERROR error) + { + // Try to open the file as a compressed stream + var fileStream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + var state = Open(fileStream, out error); + if (state?.Window == null) + return null; + + // Get the extension for modification + string inputExtension = Path.GetExtension(input).TrimStart('.'); + + // If we have no extension + if (string.IsNullOrWhiteSpace(inputExtension)) + return Path.GetFileNameWithoutExtension(input); + + // If we have an extension of length 1 + if (inputExtension.Length == 1) + { + if (inputExtension == "_") + return $"{Path.GetFileNameWithoutExtension(input)}.{char.ToLower(state.LastChar)}"; + else + return Path.GetFileNameWithoutExtension(input); + } + + // If we have an extension that doesn't end in an underscore + if (!inputExtension.EndsWith("_")) + return Path.GetFileNameWithoutExtension(input); + + // Build the new filename + bool isLowerCase = char.IsUpper(input[0]); + char replacementChar = isLowerCase ? char.ToLower(state.LastChar) : char.ToUpper(state.LastChar); + string outputExtension = inputExtension.Substring(0, inputExtension.Length - 1) + replacementChar; + return $"{Path.GetFileNameWithoutExtension(input)}.{outputExtension}"; + } + + #endregion + + #region Stream Functionality + + /// + /// Attempt to read the specified number of bytes from the State + /// + /// Source State to read from + /// Byte buffer to read into + /// Offset within the buffer to read + /// Number of bytes to read + /// Output representing the last error + /// The number of bytes read, if possible + /// + /// If the source data is compressed, this will decompress the data. + /// If the source data is uncompressed, it is copied directly + /// + public int Read(State source, byte[] buffer, int offset, int count, out LZERROR error) + { + // If we have an uncompressed input + if (source.Window == null) + { + error = LZERROR.LZERROR_NOT_LZ; + return source.Source.Read(buffer, offset, count); + } + + // If seeking has occurred, we need to perform the seek + if (source.RealCurrent != source.RealWanted) + { + // If the requested position is before the current, we need to reset + if (source.RealCurrent > source.RealWanted) + { + // Reset the decompressor state + source.Source.Seek(LZ_HEADER_LEN, SeekOrigin.Begin); + FlushWindow(source); + source.RealCurrent = 0; + source.ByteType = 0; + source.StringLength = 0; + source.Table = Enumerable.Repeat((byte)' ', LZ_TABLE_SIZE).ToArray(); + source.CurrentTableEntry = 0xFF0; + } + + // While we are not at the right offset + while (source.RealCurrent < source.RealWanted) + { + _ = DecompressByte(source, out error); + if (error != LZERROR.LZERROR_OK) + return 0; + } + } + + int bytesRemaining = count; + while (bytesRemaining > 0) + { + byte b = DecompressByte(source, out error); + if (error != LZERROR.LZERROR_OK) + return count - bytesRemaining; + + source.RealWanted++; + buffer[offset++] = b; + bytesRemaining--; + } + + error = LZERROR.LZERROR_OK; + return count; + } + + /// + /// Perform a seek on the source data + /// + /// State to seek within + /// Data offset to seek to + /// SeekOrigin representing how to seek + /// Output representing the last error + /// The position that was seeked to, -1 on error + public long Seek(State state, long offset, SeekOrigin seekOrigin, out LZERROR error) + { + // If we have an invalid state + if (state == null) + { + error = LZERROR.LZERROR_BADVALUE; + return -1; + } + + // If we have an uncompressed input + if (state.Window == null) + { + error = LZERROR.LZERROR_NOT_LZ; + return state.Source.Seek(offset, seekOrigin); + } + + // Otherwise, generate the new offset + long newWanted = state.RealWanted; + switch (seekOrigin) + { + case SeekOrigin.Current: + newWanted += offset; + break; + case SeekOrigin.End: + newWanted = state.RealLength - offset; + break; + default: + newWanted = offset; + break; + } + + // If we have an invalid new offset + if (newWanted < 0 && newWanted > state.RealLength) + { + error = LZERROR.LZERROR_BADVALUE; + return -1; + } + + error = LZERROR.LZERROR_OK; + state.RealWanted = (uint)newWanted; + return newWanted; + } + + /// + /// Copies all data from the source to the destination + /// + /// Source State to read from + /// Destination state to write to + /// Output representing the last error + /// The number of bytes written, -1 on error + /// + /// If the source data is compressed, this will decompress the data. + /// If the source data is uncompressed, it is copied directly + /// + public long CopyTo(State source, State dest, out LZERROR error) + { + error = LZERROR.LZERROR_OK; + + // If we have an uncompressed input + if (source.Window == null) + { + source.Source.CopyTo(dest.Source); + return source.Source.Length; + } + + // Loop until we have read everything + long length = 0; + while (true) + { + // Read at most 1000 bytes + byte[] buf = new byte[1000]; + int read = Read(source, buf, 0, buf.Length, out error); + + // If we had an error + if (read == 0) + { + if (error == LZERROR.LZERROR_NOT_LZ) + { + error = LZERROR.LZERROR_OK; + break; + } + else if (error != LZERROR.LZERROR_OK) + { + error = LZERROR.LZERROR_READ; + return 0; + } + } + + // Otherwise, append the length read and write the data + length += read; + dest.Source.Write(buf, 0, read); + } + + return length; + } + + /// + /// Decompress a single byte of data from the source State + /// + /// Source State to read from + /// Output representing the last error + /// The read byte, if possible + private byte DecompressByte(State source, out LZERROR error) + { + byte b; + + if (source.StringLength != 0) + { + b = source.Table[source.StringPosition]; + source.StringPosition = (source.StringPosition + 1) & 0xFFF; + source.StringLength--; + } + else + { + if ((source.ByteType & 0x100) == 0) + { + b = ReadByte(source, out error); + if (error != LZERROR.LZERROR_OK) + return 0; + + source.ByteType = (ushort)(b | 0xFF00); + } + if ((source.ByteType & 1) != 0) + { + b = ReadByte(source, out error); + if (error != LZERROR.LZERROR_OK) + return 0; + } + else + { + byte b1 = ReadByte(source, out error); + if (error != LZERROR.LZERROR_OK) + return 0; + + byte b2 = ReadByte(source, out error); + if (error != LZERROR.LZERROR_OK) + return 0; + + // Format: + // b1 b2 + // AB CD + // where CAB is the stringoffset in the table + // and D+3 is the len of the string + source.StringPosition = (uint)(b1 | ((b2 & 0xf0) << 4)); + source.StringLength = (byte)((b2 & 0xf) + 2); + + // 3, but we use a byte already below... + b = source.Table[source.StringPosition]; + source.StringPosition = (source.StringPosition + 1) & 0xFFF; + } + + source.ByteType >>= 1; + } + + // Store b in table + source.Table[source.CurrentTableEntry++] = b; + source.CurrentTableEntry &= 0xFFF; + source.RealCurrent++; + + error = LZERROR.LZERROR_OK; + return b; } /// /// Reads one compressed byte, including buffering /// - private LZERROR Get(State lzs, out byte b) + /// State to read using + /// Output representing the last error + /// Byte value that was read, if possible + private byte ReadByte(State state, out LZERROR error) { - if (lzs.GetCur < lzs.GetLen) + // If we have enough data in the buffer + if (state.WindowCurrent < state.WindowLength) { - b = lzs.Get[lzs.GetCur++]; - return LZERROR.LZERROR_OK; + error = LZERROR.LZERROR_OK; + return state.Window[state.WindowCurrent++]; } - else + + // Otherwise, read from the source + int ret = state.Source.Read(state.Window, 0, GETLEN); + if (ret == 0) { - int ret = lzs.RealFD.Read(lzs.Get, 0, GETLEN); - if (ret == 0) - { - b = 0; - return LZERROR.LZERROR_NOT_LZ; - } - - lzs.GetLen = (uint)ret; - lzs.GetCur = 1; - b = lzs.Get[0]; - return LZERROR.LZERROR_OK; + error = LZERROR.LZERROR_NOT_LZ; + return 0; } - } - private void GetFlush(State lzs) - { - lzs.GetCur = lzs.GetLen; + // Reset the window state + state.WindowLength = (uint)ret; + state.WindowCurrent = 1; + error = LZERROR.LZERROR_OK; + return state.Window[0]; } /// - /// Internal function, reads lzheader + /// Reset the current window position to the length /// - private LZERROR ReadHeader(Stream fd, out FileHeaader head) + /// State to flush + private void FlushWindow(State state) { - // Create the file header - head = new FileHeaader(); - - if (fd == null || !fd.CanSeek || !fd.CanRead) - return LZERROR.LZERROR_BADINHANDLE; - - // Ensure we're at the start of the stream - fd.Seek(0, SeekOrigin.Begin); - - byte[] magic = fd.ReadBytes(LZ_MAGIC_LEN); - head.Magic = Encoding.ASCII.GetString(magic); - if (head.Magic != MagicString) - return LZERROR.LZERROR_NOT_LZ; - - head.CompressionType = fd.ReadByteValue(); - if (head.CompressionType != (byte)'A') - return LZERROR.LZERROR_UNKNOWNALG; - - head.LastChar = (char)fd.ReadByteValue(); - head.RealLength = fd.ReadUInt32(); - return LZERROR.LZERROR_OK; + state.WindowCurrent = state.WindowLength; } + + /// + /// Parse a Stream into a file header + /// + /// Stream to parse + /// Output representing the last error + /// Filled file header on success, null on error + private FileHeaader ParseFileHeader(Stream data, out LZERROR error) + { + error = LZERROR.LZERROR_OK; + FileHeaader fileHeader = new FileHeaader(); + + byte[] magic = data.ReadBytes(LZ_MAGIC_LEN); + fileHeader.Magic = Encoding.ASCII.GetString(magic); + if (fileHeader.Magic != MagicString) + { + error = LZERROR.LZERROR_NOT_LZ; + return null; + } + + fileHeader.CompressionType = data.ReadByteValue(); + if (fileHeader.CompressionType != (byte)'A') + { + error = LZERROR.LZERROR_UNKNOWNALG; + return null; + } + + fileHeader.LastChar = (char)data.ReadByteValue(); + fileHeader.RealLength = data.ReadUInt32(); + + return fileHeader; + } + + #endregion } } \ No newline at end of file diff --git a/BurnOutSharp.Models/Compression/LZ/State.cs b/BurnOutSharp.Models/Compression/LZ/State.cs index 03221a49..554d1280 100644 --- a/BurnOutSharp.Models/Compression/LZ/State.cs +++ b/BurnOutSharp.Models/Compression/LZ/State.cs @@ -5,27 +5,27 @@ namespace BurnOutSharp.Models.Compression.LZ public sealed class State { /// - /// The real filedescriptor + /// Internal backing stream /// - public Stream RealFD { get; set; } + public Stream Source { get; set; } /// - /// The last char of the filename + /// The last char of the filename for replacement /// public char LastChar { get; set; } /// - /// The decompressed length of the file + /// Decompressed length of the file /// public uint RealLength { get; set; } /// - /// The position the decompressor currently is + /// Position the decompressor currently is /// public uint RealCurrent { get; set; } /// - /// The position the user wants to read from + /// Position the user wants to read from /// public uint RealWanted { get; set; } @@ -37,17 +37,17 @@ namespace BurnOutSharp.Models.Compression.LZ /// /// CURrent TABle ENTry /// - public uint CurTabEnt { get; set; } + public uint CurrentTableEntry { get; set; } /// /// Length and position of current string /// - public byte StringLen { get; set; } + public byte StringLength { get; set; } /// /// From stringtable /// - public uint StringPos { get; set; } + public uint StringPosition { get; set; } /// /// Bitmask within blocks @@ -57,16 +57,16 @@ namespace BurnOutSharp.Models.Compression.LZ /// /// GETLEN bytes /// - public byte[] Get { get; set; } + public byte[] Window { get; set; } /// /// Current read /// - public uint GetCur { get; set; } + public uint WindowCurrent { get; set; } /// /// Length last got /// - public uint GetLen { get; set; } + public uint WindowLength { get; set; } } } \ No newline at end of file diff --git a/BurnOutSharp/PackerType/CExe.cs b/BurnOutSharp/PackerType/CExe.cs index d6514c47..e4784554 100644 --- a/BurnOutSharp/PackerType/CExe.cs +++ b/BurnOutSharp/PackerType/CExe.cs @@ -105,19 +105,7 @@ namespace BurnOutSharp.PackerType { try { - LZ lz = new LZ(); - Models.Compression.LZ.State src = lz.OpenFile(new MemoryStream(payload)); - MemoryStream dstStream = new MemoryStream(); - Models.Compression.LZ.State dst = lz.OpenFile(dstStream); - long read = lz.Copy(src, dst); - if (read < 0) - return null; - - // Copy the data to the buffer - data = new ReadOnlySpan(dstStream.GetBuffer(), 0, (int)dstStream.Position).ToArray(); - - lz.Close(src); - lz.Close(dst); + data = LZ.Decompress(payload); } catch {