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
{