diff --git a/BinaryObjectScanner.Compression/LZ.cs b/BinaryObjectScanner.Compression/LZ.cs
deleted file mode 100644
index 50d50314..00000000
--- a/BinaryObjectScanner.Compression/LZ.cs
+++ /dev/null
@@ -1,525 +0,0 @@
-using System.IO;
-using System.Linq;
-using System.Text;
-using SabreTools.IO;
-using SabreTools.Models.Compression.LZ;
-using static SabreTools.Models.Compression.LZ.Constants;
-
-namespace BinaryObjectScanner.Compression
-{
- ///
- public class LZ
- {
- #region Constructors
-
- ///
- /// Constructor
- ///
- 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)
- {
- // If we have and invalid input
- if (compressed == null || compressed.Length == 0)
- return null;
-
- // Create a memory stream for the input and decompress that
- var compressedStream = new MemoryStream(compressed);
- return Decompress(compressedStream);
- }
-
- ///
- /// Decompress LZ-compressed data
- ///
- /// Stream representing the compressed data
- /// Decompressed data as a byte array, null on error
- public static byte[] Decompress(Stream compressed)
- {
- // If we have and invalid input
- if (compressed == null || compressed.Length == 0)
- return null;
-
- // Create a new LZ for decompression
- var lz = new LZ();
-
- // 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))
- {
- decompressed = null;
- }
- else
- {
- int dataEnd = (int)decompressedStream.Position;
- decompressedStream.Seek(0, SeekOrigin.Begin);
- decompressed = decompressedStream.ReadBytes(dataEnd);
- }
-
- // Close the streams
- lz.Close(sourceState);
- lz.Close(destState);
-
- return decompressed;
- }
-
- ///
- /// Reconstructs the full filename of the compressed file
- ///
- public static 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 = new LZ().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 State Management
-
- ///
- /// Opens a stream and creates a state from it
- ///
- /// 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)
- {
- State lzs = Init(stream, out error);
- if (error == LZERROR.LZERROR_OK || error == LZERROR.LZERROR_NOT_LZ)
- return lzs;
-
- return null;
- }
-
- ///
- /// Closes a state by invalidating the source
- ///
- /// State object to close
- public void Close(State state)
- {
- 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 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
- ///
- /// 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 we have enough data in the buffer
- if (state.WindowCurrent < state.WindowLength)
- {
- error = LZERROR.LZERROR_OK;
- return state.Window[state.WindowCurrent++];
- }
-
- // Otherwise, read from the source
- int ret = state.Source.Read(state.Window, 0, GETLEN);
- if (ret == 0)
- {
- error = LZERROR.LZERROR_NOT_LZ;
- return 0;
- }
-
- // Reset the window state
- state.WindowLength = (uint)ret;
- state.WindowCurrent = 1;
- error = LZERROR.LZERROR_OK;
- return state.Window[0];
- }
-
- ///
- /// Reset the current window position to the length
- ///
- /// State to flush
- private void FlushWindow(State state)
- {
- 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/BinaryObjectScanner/BinaryObjectScanner.csproj b/BinaryObjectScanner/BinaryObjectScanner.csproj
index 6314ad65..85863866 100644
--- a/BinaryObjectScanner/BinaryObjectScanner.csproj
+++ b/BinaryObjectScanner/BinaryObjectScanner.csproj
@@ -29,6 +29,7 @@
+
diff --git a/BinaryObjectScanner/FileType/MicrosoftLZ.cs b/BinaryObjectScanner/FileType/MicrosoftLZ.cs
index 508ff820..a1f1f30e 100644
--- a/BinaryObjectScanner/FileType/MicrosoftLZ.cs
+++ b/BinaryObjectScanner/FileType/MicrosoftLZ.cs
@@ -1,7 +1,7 @@
using System;
using System.IO;
-using BinaryObjectScanner.Compression;
using BinaryObjectScanner.Interfaces;
+using SabreTools.Compression.LZ;
namespace BinaryObjectScanner.FileType
{
@@ -40,14 +40,17 @@ namespace BinaryObjectScanner.FileType
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempPath);
- byte[] data = LZ.Decompress(stream);
+ var data = Decompressor.Decompress(stream);
+ if (data == null)
+ return null;
// Create the temp filename
string tempFile = "temp.bin";
if (!string.IsNullOrEmpty(file))
{
- string expandedFilePath = LZ.GetExpandedName(file, out _);
- tempFile = Path.GetFileName(expandedFilePath).TrimEnd('\0');
+ var expandedFilePath = Decompressor.GetExpandedName(file, out _);
+ if (expandedFilePath != null)
+ tempFile = Path.GetFileName(expandedFilePath).TrimEnd('\0');
if (tempFile.EndsWith(".ex"))
tempFile += "e";
else if (tempFile.EndsWith(".dl"))
diff --git a/BinaryObjectScanner/Packer/CExe.cs b/BinaryObjectScanner/Packer/CExe.cs
index 8a093f4f..354ef4d6 100644
--- a/BinaryObjectScanner/Packer/CExe.cs
+++ b/BinaryObjectScanner/Packer/CExe.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using BinaryObjectScanner.Compression;
using BinaryObjectScanner.Interfaces;
using ICSharpCode.SharpZipLib.Zip.Compression;
using SabreTools.Matching;
@@ -121,7 +120,7 @@ namespace BinaryObjectScanner.Packer
{
try
{
- data = LZ.Decompress(payload);
+ data = SabreTools.Compression.LZ.Decompressor.Decompress(payload);
}
catch
{
diff --git a/Test/Extractor.cs b/Test/Extractor.cs
index 79f9c0ec..a85c6ad7 100644
--- a/Test/Extractor.cs
+++ b/Test/Extractor.cs
@@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Linq;
using System.Text;
-using BinaryObjectScanner.Compression;
using BinaryObjectScanner.Utilities;
using OpenMcdf;
using SabreTools.IO;
@@ -421,13 +420,13 @@ namespace Test
// If the LZ file itself fails
try
{
- byte[] data = LZ.Decompress(stream);
+ byte[] data = SabreTools.Compression.LZ.Decompressor.Decompress(stream);
// Create the temp filename
string tempFile = "temp.bin";
if (!string.IsNullOrEmpty(file))
{
- string expandedFilePath = LZ.GetExpandedName(file, out _);
+ string expandedFilePath = SabreTools.Compression.LZ.Decompressor.GetExpandedName(file, out _);
tempFile = Path.GetFileName(expandedFilePath).TrimEnd('\0');
if (tempFile.EndsWith(".ex"))
tempFile += "e";
diff --git a/Test/Test.csproj b/Test/Test.csproj
index e1297dfb..81cffb9d 100644
--- a/Test/Test.csproj
+++ b/Test/Test.csproj
@@ -14,6 +14,7 @@
+