diff --git a/BurnOutSharp/External/libmspack/CAB/Cabinet.cs b/BurnOutSharp/External/libmspack/CAB/Cabinet.cs
new file mode 100644
index 00000000..f7f443de
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/Cabinet.cs
@@ -0,0 +1,175 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+/* Cabinet (.CAB) files are a form of file archive. Each cabinet contains
+ * "folders", which are compressed spans of data. Each cabinet has
+ * "files", whose metadata is in the cabinet header, but whose actual data
+ * is stored compressed in one of the "folders". Cabinets can span more
+ * than one physical file on disk, in which case they are a "cabinet set",
+ * and usually the last folder of each cabinet extends into the next
+ * cabinet.
+ *
+ * For a complete description of the format, see the MSDN site:
+ * http://msdn.microsoft.com/en-us/library/bb267310.aspx
+ */
+
+/* Notes on compliance with cabinet specification:
+ *
+ * One of the main changes between cabextract 0.6 and libmspack's cab
+ * decompressor is the move from block-oriented decompression to
+ * stream-oriented decompression.
+ *
+ * cabextract would read one data block from disk, decompress it with the
+ * appropriate method, then write the decompressed data. The CAB
+ * specification is specifically designed to work like this, as it ensures
+ * compression matches do not span the maximum decompressed block size
+ * limit of 32kb.
+ *
+ * However, the compression algorithms used are stream oriented, with
+ * specific hacks added to them to enforce the "individual 32kb blocks"
+ * rule in CABs. In other file formats, they do not have this limitation.
+ *
+ * In order to make more generalised decompressors, libmspack's CAB
+ * decompressor has moved from being block-oriented to more stream
+ * oriented. This also makes decompression slightly faster.
+ *
+ * However, this leads to incompliance with the CAB specification. The
+ * CAB controller can no longer ensure each block of input given to the
+ * decompressors is matched with their output. The "decompressed size" of
+ * each individual block is thrown away.
+ *
+ * Each CAB block is supposed to be seen as individually compressed. This
+ * means each consecutive data block can have completely different
+ * "uncompressed" sizes, ranging from 1 to 32768 bytes. However, in
+ * reality, all data blocks in a folder decompress to exactly 32768 bytes,
+ * excepting the final block.
+ *
+ * Given this situation, the decompression algorithms are designed to
+ * realign their input bitstreams on 32768 output-byte boundaries, and
+ * various other special cases have been made. libmspack will not
+ * correctly decompress LZX or Quantum compressed folders where the blocks
+ * do not follow this "32768 bytes until last block" pattern. It could be
+ * implemented if needed, but hopefully this is not necessary -- it has
+ * not been seen in over 3Gb of CAB archives.
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ ///
+ /// A structure which represents a single cabinet file.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ /// If this cabinet is part of a merged cabinet set, the #files and #folders
+ /// fields are common to all cabinets in the set, and will be identical.
+ ///
+ ///
+ ///
+ ///
+ public class Cabinet
+ {
+ ///
+ /// The next cabinet in a chained list, if this cabinet was opened with
+ /// mscab_decompressor::search(). May be NULL to mark the end of the
+ /// list.
+ ///
+ public Cabinet Next { get; set; }
+
+ ///
+ /// The filename of the cabinet. More correctly, the filename of the
+ /// physical file that the cabinet resides in. This is given by the
+ /// library user and may be in any format.
+ ///
+ public string Filename { get; set; }
+
+ ///
+ /// The file offset of cabinet within the physical file it resides in.
+ ///
+ public long BaseOffset { get; set; }
+
+ ///
+ /// The length of the cabinet file in bytes.
+ ///
+ public uint Length { get; set; }
+
+ ///
+ /// The previous cabinet in a cabinet set, or NULL.
+ ///
+ public Cabinet PreviousCabinet { get; set; }
+
+ ///
+ /// The next cabinet in a cabinet set, or NULL.
+ ///
+ public Cabinet NextCabinet { get; set; }
+
+ ///
+ /// The filename of the previous cabinet in a cabinet set, or NULL.
+ ///
+ public string PreviousName { get; set; }
+
+ ///
+ /// The filename of the next cabinet in a cabinet set, or NULL.
+ ///
+ public string NextName { get; set; }
+
+ ///
+ /// The name of the disk containing the previous cabinet in a cabinet, or NULL.
+ ///
+ public string PreviousInfo { get; set; }
+
+ ///
+ /// The name of the disk containing the next cabinet in a cabinet set, or NULL.
+ ///
+ public string NextInfo { get; set; }
+
+ ///
+ /// A list of all files in the cabinet or cabinet set.
+ ///
+ public InternalFile Files { get; set; }
+
+ ///
+ /// A list of all folders in the cabinet or cabinet set.
+ ///
+ public Folder Folders { get; set; }
+
+ ///
+ /// The set ID of the cabinet. All cabinets in the same set should have
+ /// the same set ID.
+ ///
+ public ushort SetID { get; set; }
+
+ ///
+ /// The index number of the cabinet within the set. Numbering should
+ /// start from 0 for the first cabinet in the set, and increment by 1 for
+ /// each following cabinet.
+ ///
+ public ushort SetIndex { get; set; }
+
+ ///
+ /// The number of bytes reserved in the header area of the cabinet.
+ ///
+ /// If this is non-zero and flags has MSCAB_HDR_RESV set, this data can
+ /// be read by the calling application. It is of the given length,
+ /// located at offset (base_offset + MSCAB_HDR_RESV_OFFSET) in the
+ /// cabinet file.
+ ///
+ ///
+ public ushort HeaderResv { get; set; }
+
+ ///
+ /// Header flags.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public HeaderFlags Flags { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/CabinetImpl.cs b/BurnOutSharp/External/libmspack/CAB/CabinetImpl.cs
new file mode 100644
index 00000000..dc0d7a89
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/CabinetImpl.cs
@@ -0,0 +1,18 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ public class CabinetImpl : Cabinet
+ {
+ public long BlocksOffset { get; set; }
+
+ public int BlockResverved { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/Compressor.cs b/BurnOutSharp/External/libmspack/CAB/Compressor.cs
new file mode 100644
index 00000000..befc5215
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/Compressor.cs
@@ -0,0 +1,26 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ ///
+ /// TODO
+ ///
+ public class Compressor
+ {
+ public int Dummy { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/CompressorImpl.cs b/BurnOutSharp/External/libmspack/CAB/CompressorImpl.cs
new file mode 100644
index 00000000..430be7b7
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/CompressorImpl.cs
@@ -0,0 +1,19 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ ///
+ /// TODO
+ ///
+ public class CompressorImpl : Compressor
+ {
+ public SystemImpl System { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/DataBlockHeader.cs b/BurnOutSharp/External/libmspack/CAB/DataBlockHeader.cs
new file mode 100644
index 00000000..42471e8a
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/DataBlockHeader.cs
@@ -0,0 +1,58 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+
+namespace LibMSPackSharp.CAB
+{
+ public class DataBlockHeader
+ {
+ #region Fields
+
+ ///
+ /// CRC32 checksum of the data
+ ///
+ public uint Checksum { get; private set; }
+
+ ///
+ /// Compressed size of the data
+ ///
+ public ushort CompressedSize { get; private set; }
+
+ ///
+ /// Uncompressed size of the data
+ ///
+ public ushort UncompressedSize { get; private set; }
+
+ #endregion
+
+ ///
+ /// Private constructor
+ ///
+ private DataBlockHeader() { }
+
+ ///
+ /// Constructor
+ ///
+ public static Error Create(byte[] data, out DataBlockHeader header)
+ {
+ header = null;
+ if (data == null || data.Length < 0x08)
+ return Error.MSPACK_ERR_READ;
+
+ header = new DataBlockHeader();
+
+ header.Checksum = BitConverter.ToUInt32(data, 0x00);
+ header.CompressedSize = BitConverter.ToUInt16(data, 0x04);
+ header.UncompressedSize = BitConverter.ToUInt16(data, 0x06);
+
+ return Error.MSPACK_ERR_OK;
+ }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/DecompressState.cs b/BurnOutSharp/External/libmspack/CAB/DecompressState.cs
new file mode 100644
index 00000000..4d99cf4d
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/DecompressState.cs
@@ -0,0 +1,91 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+
+namespace LibMSPackSharp.CAB
+{
+ public class DecompressState
+ {
+ ///
+ /// Current folder we're extracting from
+ ///
+ public Folder Folder { get; set; }
+
+ ///
+ /// Current folder split we're in
+ ///
+ public FolderData Data { get; set; }
+
+ ///
+ /// Uncompressed offset within folder
+ ///
+ public uint Offset { get; set; }
+
+ ///
+ /// Which block are we decompressing?
+ ///
+ public uint Block { get; set; }
+
+ ///
+ /// Cumulative sum of block output sizes
+ ///
+ public long Outlen { get; set; }
+
+ ///
+ /// Special I/O code for decompressor
+ ///
+ public SystemImpl Sys { get; set; }
+
+ ///
+ /// Type of compression used by folder
+ ///
+ public CompressionType CompressionType { get; set; }
+
+ ///
+ /// Decompressor code
+ ///
+ public Func Decompress { get; set; }
+
+ ///
+ /// Decompressor state
+ ///
+ public object DecompressorState { get; set; }
+
+ ///
+ /// Cabinet where input data comes from
+ ///
+ public Cabinet InputCabinet { get; set; }
+
+ ///
+ /// Input file handle
+ ///
+ public object InputFileHandle { get; set; }
+
+ ///
+ /// Output file handle
+ ///
+ public object OutputFileHandle { get; set; }
+
+ ///
+ /// Input data consumed
+ ///
+ public int IPtr { get; set; }
+
+ ///
+ /// Input data end
+ ///
+ public int IEnd { get; set; }
+
+ ///
+ /// One input block of data
+ ///
+ public byte[] Input { get; set; } = new byte[Implementation.CAB_INPUTBUF];
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/Decompressor.cs b/BurnOutSharp/External/libmspack/CAB/Decompressor.cs
new file mode 100644
index 00000000..f5e1677a
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/Decompressor.cs
@@ -0,0 +1,268 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+
+namespace LibMSPackSharp.CAB
+{
+ ///
+ /// A decompressor for .CAB (Microsoft Cabinet) files
+ ///
+ /// All fields are READ ONLY.
+ ///
+ ///
+ ///
+ public class Decompressor
+ {
+ ///
+ /// Opens a cabinet file and reads its contents.
+ ///
+ /// If the file opened is a valid cabinet file, all headers will be read
+ /// and a Cabinet structure will be returned, with a full list of
+ /// folders and files.
+ ///
+ /// In the case of an error occuring, NULL is returned and the error code
+ /// is available from last_error().
+ ///
+ /// The filename pointer should be considered "in use" until close() is
+ /// called on the cabinet.
+ ///
+ /// A self-referential pointer to the Decompressor instance being called
+ /// The filename of the cabinet file. This is passed directly to SystemImpl::open().
+ /// A pointer to a Cabinet structure, or NULL on failure
+ ///
+ ///
+ ///
+ public Func Open;
+
+ ///
+ /// Closes a previously opened cabinet or cabinet set.
+ ///
+ /// This closes a cabinet, all cabinets associated with it via the
+ /// Cabinet::next, Cabinet::prevcab and
+ /// Cabinet::nextcab pointers, and all folders and files. All
+ /// memory used by these entities is freed.
+ ///
+ /// The cabinet pointer is now invalid and cannot be used again. All
+ /// Folder and InternalFile pointers from that cabinet or cabinet
+ /// set are also now invalid, and cannot be used again.
+ ///
+ /// If the cabinet pointer given was created using search(), it MUST be
+ /// the cabinet pointer returned by search() and not one of the later
+ /// cabinet pointers further along the Cabinet::next chain.
+ ///
+ /// If extra cabinets have been added using append() or prepend(), these
+ /// will all be freed, even if the cabinet pointer given is not the first
+ /// cabinet in the set. Do NOT close() more than one cabinet in the set.
+ ///
+ /// The Cabinet::filename is not freed by the library, as it is
+ /// not allocated by the library. The caller should free this itself if
+ /// necessary, before it is lost forever.
+ ///
+ /// A self-referential pointer to the Decompressor instance being called
+ /// The cabinet to close
+ ///
+ ///
+ ///
+ ///
+ public Action Close;
+
+ ///
+ /// Searches a regular file for embedded cabinets.
+ ///
+ /// This opens a normal file with the given filename and will search the
+ /// entire file for embedded cabinet files
+ ///
+ /// If any cabinets are found, the equivalent of open() is called on each
+ /// potential cabinet file at the offset it was found. All successfully
+ /// open()ed cabinets are kept in a list.
+ ///
+ /// The first cabinet found will be returned directly as the result of
+ /// this method. Any further cabinets found will be chained in a list
+ /// using the Cabinet::next field.
+ ///
+ /// In the case of an error occuring anywhere other than the simulated
+ /// open(), NULL is returned and the error code is available from
+ /// last_error().
+ ///
+ /// If no error occurs, but no cabinets can be found in the file, NULL is
+ /// returned and last_error() returns MSPACK_ERR_OK.
+ ///
+ /// The filename pointer should be considered in use until close() is
+ /// called on the cabinet.
+ ///
+ /// close() should only be called on the result of search(), not on any
+ /// subsequent cabinets in the Cabinet::next chain.
+ ///
+ ///
+ /// a self-referential pointer to the Decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the file to search for cabinets. This
+ /// is passed directly to SystemImpl::open().
+ ///
+ /// a pointer to a Cabinet structure, or NULL
+ ///
+ ///
+ ///
+ public Func Search;
+
+ ///
+ /// Appends one Cabinet to another, forming or extending a cabinet
+ /// set.
+ ///
+ /// This will attempt to append one cabinet to another such that
+ /// (cab->nextcab == nextcab) && (nextcab->prevcab == cab) and
+ /// any folders split between the two cabinets are merged.
+ ///
+ /// The cabinets MUST be part of a cabinet set -- a cabinet set is a
+ /// cabinet that spans more than one physical cabinet file on disk -- and
+ /// must be appropriately matched.
+ ///
+ /// It can be determined if a cabinet has further parts to load by
+ /// examining the Cabinet::flags field:
+ ///
+ /// - if (flags & MSCAB_HDR_PREVCAB) is non-zero, there is a
+ /// predecessor cabinet to open() and prepend(). Its MS-DOS
+ /// case-insensitive filename is Cabinet::prevname
+ /// - if (flags & MSCAB_HDR_NEXTCAB) is non-zero, there is a
+ /// successor cabinet to open() and append(). Its MS-DOS case-insensitive
+ /// filename is Cabinet::nextname
+ ///
+ /// If the cabinets do not match, an error code will be returned. Neither
+ /// cabinet has been altered, and both should be closed seperately.
+ ///
+ /// Files and folders in a cabinet set are a single entity. All cabinets
+ /// in a set use the same file list, which is updated as cabinets in the
+ /// set are added. All pointers to Folder and InternalFile
+ /// structures in either cabinet must be discarded and re-obtained after
+ /// merging.
+ ///
+ ///
+ /// a self-referential pointer to the Decompressor
+ /// instance being called
+ ///
+ ///
+ /// the cabinet which will be appended to,
+ /// predecessor of nextcab
+ ///
+ ///
+ /// the cabinet which will be appended,
+ /// successor of cab
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ ///
+ ///
+ ///
+ public Func Append;
+
+ ///
+ /// Prepends one Cabinet to another, forming or extending a
+ /// cabinet set.
+ ///
+ /// This will attempt to prepend one cabinet to another, such that
+ /// (cab->prevcab == prevcab) && (prevcab->nextcab == cab) . In
+ /// all other respects, it is identical to append(). See append() for the
+ /// full documentation.
+ ///
+ ///
+ /// a self-referential pointer to the Decompressor
+ /// instance being called
+ ///
+ ///
+ /// the cabinet which will be prepended to,
+ /// successor of nextcab
+ ///
+ ///
+ /// the cabinet which will be prepended,
+ /// predecessor of cab
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ ///
+ ///
+ ///
+ public Func Prepend;
+
+ ///
+ /// Extracts a file from a cabinet or cabinet set.
+ ///
+ /// This extracts a compressed file in a cabinet and writes it to the given
+ /// filename.
+ ///
+ /// The MS-DOS filename of the file, InternalFile::filename, is NOT USED
+ /// by extract(). The caller must examine this MS-DOS filename, copy and
+ /// change it as necessary, create directories as necessary, and provide
+ /// the correct filename as a parameter, which will be passed unchanged
+ /// to the decompressor's SystemImpl::open()
+ ///
+ /// If the file belongs to a split folder in a multi-part cabinet set,
+ /// and not enough parts of the cabinet set have been loaded and appended
+ /// or prepended, an error will be returned immediately.
+ ///
+ ///
+ /// a self-referential pointer to the Decompressor
+ /// instance being called
+ ///
+ /// the file to be decompressed
+ /// the filename of the file being written to
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func Extract;
+
+ ///
+ /// Sets a CAB decompression engine parameter.
+ ///
+ /// The following parameters are defined:
+ /// - #MSCABD_PARAM_SEARCHBUF: How many bytes should be allocated as a
+ /// buffer when using search()? The minimum value is 4. The default
+ /// value is 32768.
+ /// - #MSCABD_PARAM_FIXMSZIP: If non-zero, extract() will ignore bad
+ /// checksums and recover from decompression errors in MS-ZIP
+ /// compressed folders. The default value is 0 (don't recover).
+ /// - #MSCABD_PARAM_DECOMPBUF: How many bytes should be used as an input
+ /// bit buffer by decompressors? The minimum value is 4. The default
+ /// value is 4096.
+ ///
+ ///
+ /// a self-referential pointer to the Decompressor
+ /// instance being called
+ ///
+ /// the parameter to set
+ /// the value to set the parameter to
+ ///
+ /// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there
+ /// is a problem with either parameter or value.
+ ///
+ ///
+ ///
+ public Func SetParam;
+
+ ///
+ /// Returns the error code set by the most recently called method.
+ ///
+ /// This is useful for open() and search(), which do not return an error
+ /// code directly.
+ ///
+ ///
+ /// a self-referential pointer to the Decompressor
+ /// instance being called
+ ///
+ /// the most recent error code
+ ///
+ ///
+ public Func LastError;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/DecompressorImpl.cs b/BurnOutSharp/External/libmspack/CAB/DecompressorImpl.cs
new file mode 100644
index 00000000..8a98b89d
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/DecompressorImpl.cs
@@ -0,0 +1,30 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ public class DecompressorImpl : Decompressor
+ {
+ public DecompressState State { get; set; }
+
+ public SystemImpl System { get; set; }
+
+ public int BufferSize { get; set; }
+
+ public int SearchBufferSize { get; set; }
+
+ public bool FixMSZip { get; set; }
+
+ public bool Salvage { get; set; }
+
+ public Error Error { get; set; }
+
+ public Error ReadError { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/Enums.cs b/BurnOutSharp/External/libmspack/CAB/Enums.cs
new file mode 100644
index 00000000..d0e8c55d
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/Enums.cs
@@ -0,0 +1,113 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+
+namespace LibMSPackSharp.CAB
+{
+ [Flags]
+ public enum CompressionType : ushort
+ {
+ COMPTYPE_MASK = 0x000f,
+
+ COMPTYPE_NONE = 0x0000,
+ COMPTYPE_MSZIP = 0x0001,
+ COMPTYPE_QUANTUM = 0x0002,
+ COMPTYPE_LZX = 0x0003,
+ }
+
+ [Flags]
+ public enum FileAttributes : byte
+ {
+ ///
+ /// Indicates the file is write protected.
+ ///
+ MSCAB_ATTRIB_RDONLY = 0x01,
+
+ ///
+ /// Indicates the file is hidden.
+ ///
+ MSCAB_ATTRIB_HIDDEN = 0x02,
+
+ ///
+ /// Indicates the file is a operating system file.
+ ///
+ MSCAB_ATTRIB_SYSTEM = 0x04,
+
+ ///
+ /// Indicates the file is "archived".
+ ///
+ MSCAB_ATTRIB_ARCH = 0x20,
+
+ ///
+ /// Indicates the file is an executable program.
+ ///
+ MSCAB_ATTRIB_EXEC = 0x40,
+
+ ///
+ /// Indicates the filename is in UTF8 format rather than ISO-8859-1.
+ ///
+ MSCAB_ATTRIB_UTF_NAME = 0x80,
+ }
+
+ [Flags]
+ public enum FileFlags : ushort
+ {
+ CONTINUED_FROM_PREV = 0xFFFD,
+ CONTINUED_TO_NEXT = 0xFFFE,
+ CONTINUED_PREV_AND_NEXT = 0xFFFF,
+ }
+
+ [Flags]
+ public enum HeaderFlags : ushort
+ {
+ ///
+ /// Indicates the cabinet is part of a cabinet set, and has a predecessor cabinet.
+ ///
+ MSCAB_HDR_PREVCAB = 0x0001,
+
+ ///
+ /// Indicates the cabinet is part of a cabinet set, and has a successor cabinet.
+ ///
+ MSCAB_HDR_NEXTCAB = 0x0002,
+
+ ///
+ /// Indicates the cabinet has reserved header space.
+ ///
+ MSCAB_HDR_RESV = 0x0004,
+ }
+
+ public enum Parameters
+ {
+ ///
+ /// Search buffer size.
+ ///
+ MSCABD_PARAM_SEARCHBUF = 0,
+
+ ///
+ /// Repair MS-ZIP streams?
+ ///
+ MSCABD_PARAM_FIXMSZIP = 1,
+
+ ///
+ /// Size of decompression buffer
+ ///
+ MSCABD_PARAM_DECOMPBUF = 2,
+
+ ///
+ /// salvage data from bad cabinets?
+ /// If enabled, open() will skip file with bad folder indices or filenames
+ /// rather than reject the whole cabinet, and extract() will limit rather than
+ /// reject files with invalid offsets and lengths, and bad data block checksums
+ /// will be ignored. Available only in CAB decoder version 2 and above.
+ ///
+ MSCABD_PARAM_SALVAGE = 3,
+ }
+
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/Folder.cs b/BurnOutSharp/External/libmspack/CAB/Folder.cs
new file mode 100644
index 00000000..0c5c845c
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/Folder.cs
@@ -0,0 +1,61 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ ///
+ /// A structure which represents a single folder in a cabinet or cabinet set.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ /// A folder is a single compressed stream of data. When uncompressed, it
+ /// holds the data of one or more files. A folder may be split across more
+ /// than one cabinet.
+ ///
+ public class Folder
+ {
+ ///
+ /// A pointer to the next folder in this cabinet or cabinet set, or NULL
+ /// if this is the final folder.
+ ///
+ public Folder Next { get; set; }
+
+ ///
+ /// The compression format used by this folder.
+ ///
+ /// The macro MSCABD_COMP_METHOD() should be used on this field to get
+ /// the algorithm used. The macro MSCABD_COMP_LEVEL() should be used to get
+ /// the "compression level".
+ ///
+ ///
+ ///
+ public CompressionType CompressionType { get; set; }
+
+ ///
+ /// The total number of data blocks used by this folder. This includes
+ /// data blocks present in other files, if this folder spans more than
+ /// one cabinet.
+ ///
+ public ushort NumBlocks { get; set; }
+
+ ///
+ /// Returns the compression method used by a folder.
+ ///
+ /// a value
+ /// a value
+ public CompressionType MSCABD_COMP_LEVEL(CompressionType compType) => (CompressionType)((((ushort)compType) >> 8) & 0x1F);
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/FolderData.cs b/BurnOutSharp/External/libmspack/CAB/FolderData.cs
new file mode 100644
index 00000000..1849f4bf
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/FolderData.cs
@@ -0,0 +1,29 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ ///
+ /// There is one of these for every cabinet a folder spans
+ ///
+ public class FolderData
+ {
+ public FolderData Next { get; set; }
+
+ ///
+ /// Cabinet file of this folder span
+ ///
+ public Cabinet Cab { get; set; }
+
+ ///
+ /// Cabinet offset of first datablock
+ ///
+ public long Offset { get; set; }
+ };
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/FolderImpl.cs b/BurnOutSharp/External/libmspack/CAB/FolderImpl.cs
new file mode 100644
index 00000000..009b9c07
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/FolderImpl.cs
@@ -0,0 +1,36 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ public class FolderImpl : Folder
+ {
+ ///
+ /// Where are the data blocks?
+ ///
+ public FolderData Data { get; set; }
+
+ ///
+ /// First file needing backwards merge
+ ///
+ public InternalFile MergePrev { get; set; }
+
+ ///
+ /// First file needing forwards merge
+ ///
+ public InternalFile MergeNext { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/Implementation.cs b/BurnOutSharp/External/libmspack/CAB/Implementation.cs
new file mode 100644
index 00000000..b11b2367
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/Implementation.cs
@@ -0,0 +1,1647 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+/* Cabinet (.CAB) files are a form of file archive. Each cabinet contains
+ * "folders", which are compressed spans of data. Each cabinet has
+ * "files", whose metadata is in the cabinet header, but whose actual data
+ * is stored compressed in one of the "folders". Cabinets can span more
+ * than one physical file on disk, in which case they are a "cabinet set",
+ * and usually the last folder of each cabinet extends into the next
+ * cabinet.
+ *
+ * For a complete description of the format, see the MSDN site:
+ * http://msdn.microsoft.com/en-us/library/bb267310.aspx
+ */
+
+/* Notes on compliance with cabinet specification:
+ *
+ * One of the main changes between cabextract 0.6 and libmspack's cab
+ * decompressor is the move from block-oriented decompression to
+ * stream-oriented decompression.
+ *
+ * cabextract would read one data block from disk, decompress it with the
+ * appropriate method, then write the decompressed data. The CAB
+ * specification is specifically designed to work like this, as it ensures
+ * compression matches do not span the maximum decompressed block size
+ * limit of 32kb.
+ *
+ * However, the compression algorithms used are stream oriented, with
+ * specific hacks added to them to enforce the "individual 32kb blocks"
+ * rule in CABs. In other file formats, they do not have this limitation.
+ *
+ * In order to make more generalised decompressors, libmspack's CAB
+ * decompressor has moved from being block-oriented to more stream
+ * oriented. This also makes decompression slightly faster.
+ *
+ * However, this leads to incompliance with the CAB specification. The
+ * CAB controller can no longer ensure each block of input given to the
+ * decompressors is matched with their output. The "decompressed size" of
+ * each individual block is thrown away.
+ *
+ * Each CAB block is supposed to be seen as individually compressed. This
+ * means each consecutive data block can have completely different
+ * "uncompressed" sizes, ranging from 1 to 32768 bytes. However, in
+ * reality, all data blocks in a folder decompress to exactly 32768 bytes,
+ * excepting the final block.
+ *
+ * Given this situation, the decompression algorithms are designed to
+ * realign their input bitstreams on 32768 output-byte boundaries, and
+ * various other special cases have been made. libmspack will not
+ * correctly decompress LZX or Quantum compressed folders where the blocks
+ * do not follow this "32768 bytes until last block" pattern. It could be
+ * implemented if needed, but hopefully this is not necessary -- it has
+ * not been seen in over 3Gb of CAB archives.
+ */
+
+using System;
+using System.Text;
+using LibMSPackSharp.Compression;
+
+namespace LibMSPackSharp.CAB
+{
+ public static class Implementation
+ {
+ #region Generic CAB Definitions
+
+ #region Structure Offsets
+
+ private const int cfhead_Signature = (0x00);
+ private const int cfhead_CabinetSize = (0x08);
+ private const int cfhead_FileOffset = (0x10);
+ private const int cfhead_MinorVersion = (0x18);
+ private const int cfhead_MajorVersion = (0x19);
+ private const int cfhead_NumFolders = (0x1A);
+ private const int cfhead_NumFiles = (0x1C);
+ private const int cfhead_Flags = (0x1E);
+ private const int cfhead_SetID = (0x20);
+ private const int cfhead_CabinetIndex = (0x22);
+ private const int cfhead_SIZEOF = (0x24);
+
+ private const int cfheadext_HeaderReserved = (0x00);
+ private const int cfheadext_FolderReserved = (0x02);
+ private const int cfheadext_DataReserved = (0x03);
+ private const int cfheadext_SIZEOF = (0x04);
+
+ private const int cffold_DataOffset = (0x00);
+ private const int cffold_NumBlocks = (0x04);
+ private const int cffold_CompType = (0x06);
+ private const int cffold_SIZEOF = (0x08);
+
+ private const int cffile_UncompressedSize = (0x00);
+ private const int cffile_FolderOffset = (0x04);
+ private const int cffile_FolderIndex = (0x08);
+ private const int cffile_Date = (0x0A);
+ private const int cffile_Time = (0x0C);
+ private const int cffile_Attribs = (0x0E);
+ private const int cffile_SIZEOF = (0x10);
+
+ private const int cfdata_CheckSum = (0x00);
+ private const int cfdata_CompressedSize = (0x04);
+ private const int cfdata_UncompressedSize = (0x06);
+ private const int cfdata_SIZEOF = (0x08);
+
+ #endregion
+
+ // CAB data blocks are <= 32768 bytes in uncompressed form.Uncompressed
+ // blocks have zero growth. MSZIP guarantees that it won't grow above
+ // uncompressed size by more than 12 bytes.LZX guarantees it won't grow
+ // more than 6144 bytes.Quantum has no documentation, but the largest
+ // block seen in the wild is 337 bytes above uncompressed size.
+
+ public const int CAB_BLOCKMAX = 32768;
+ public const int CAB_INPUTMAX = CAB_BLOCKMAX + 6144;
+
+ // input buffer needs to be CAB_INPUTMAX + 1 byte to allow for max-sized block
+ // plus 1 trailer byte added by cabd_sys_read_block() for Quantum alignment.
+ //
+ // When MSCABD_PARAM_SALVAGE is set, block size is not checked so can be
+ // up to 65535 bytes, so max input buffer size needed is 65535 + 1
+
+ public const int CAB_INPUTMAX_SALVAGE = 65535;
+ public const int CAB_INPUTBUF = CAB_INPUTMAX_SALVAGE + 1;
+
+ // There are no more than 65535 data blocks per folder, so a folder cannot
+ // be more than 32768*65535 bytes in length.As files cannot span more than
+ // one folder, this is also their max offset, length and offset+length limit.
+
+ public const int CAB_FOLDERMAX = 65535;
+ public const int CAB_LENGTHMAX = CAB_BLOCKMAX * CAB_FOLDERMAX;
+
+ #endregion
+
+ #region CABD_OPEN
+
+ ///
+ /// Opens a file and tries to read it as a cabinet file
+ ///
+ public static CabinetImpl Open(Decompressor d, string filename)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ CabinetImpl cab = null;
+
+ if (self == null)
+ return null;
+
+ SystemImpl system = self.System;
+ object fileHandle;
+ if ((fileHandle = system.Open(system, filename, OpenMode.MSPACK_SYS_OPEN_READ)) != null)
+ {
+ cab = new CabinetImpl();
+ cab.Filename = filename;
+ Error error = ReadHeaders(system, fileHandle, cab, 0, self.Salvage, false);
+ if (error != Error.MSPACK_ERR_OK)
+ {
+ Close(self, cab);
+ cab = null;
+ }
+
+ self.Error = error;
+ system.Close(fileHandle);
+ }
+ else
+ {
+ self.Error = Error.MSPACK_ERR_OPEN;
+ }
+
+ return cab;
+ }
+
+ #endregion
+
+ #region CABD_CLOSE
+
+ ///
+ /// Frees all memory associated with a given Cabinet.
+ ///
+ public static void Close(Decompressor d, Cabinet origcab)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+
+ FolderData dat, ndat;
+ Cabinet cab, ncab;
+ Folder fol, nfol;
+ InternalFile fi, nfi;
+
+ if (self == null)
+ return;
+
+ SystemImpl sys = self.System;
+
+ self.Error = Error.MSPACK_ERR_OK;
+
+ while (origcab != null)
+ {
+ // Free files
+ for (fi = origcab.Files; fi != null; fi = nfi)
+ {
+ nfi = fi.Next;
+ sys.Free(fi.Filename);
+ sys.Free(fi);
+ }
+
+ // Free folders
+ for (fol = origcab.Folders; fol != null; fol = nfol)
+ {
+ nfol = fol.Next;
+
+ // Free folder decompression state if it has been decompressed
+ if (self.State != null && (self.State.Folder == fol))
+ {
+ if (self.State.InputFileHandle != null)
+ sys.Close(self.State.InputFileHandle);
+
+ FreeDecompressionState(self);
+ sys.Free(self.State);
+ self.State = null;
+ }
+
+ // Free folder data segments
+ for (dat = ((FolderImpl)fol).Data.Next; dat != null; dat = ndat)
+ {
+ ndat = dat.Next;
+ sys.Free(dat);
+ }
+
+ sys.Free(fol);
+ }
+
+ // Free predecessor cabinets (and the original cabinet's strings)
+ for (cab = origcab; cab == null; cab = ncab)
+ {
+ ncab = cab.PreviousCabinet;
+ sys.Free(cab.PreviousName);
+ sys.Free(cab.NextName);
+ sys.Free(cab.PreviousInfo);
+ sys.Free(cab.NextInfo);
+ if (cab != origcab)
+ sys.Free(cab);
+ }
+
+ // Free successor cabinets
+ for (cab = origcab.NextCabinet; cab != null; cab = ncab)
+ {
+ ncab = cab.NextCabinet;
+ sys.Free(cab.PreviousName);
+ sys.Free(cab.NextName);
+ sys.Free(cab.PreviousInfo);
+ sys.Free(cab.NextInfo);
+ sys.Free(cab);
+ }
+
+ // Free actual cabinet structure
+ cab = origcab.Next;
+ sys.Free(origcab);
+
+ // Repeat full procedure again with the cab.Next pointer (if set)
+ origcab = cab;
+ }
+ }
+
+ #endregion
+
+ #region CABD_READ_HEADERS
+
+ ///
+ /// Reads the cabinet file header, folder list and file list.
+ /// Fills out a pre-existing Cabinet structure, allocates memory
+ /// for folders and files as necessary
+ ///
+ public static Error ReadHeaders(SystemImpl sys, object fh, CabinetImpl cab, long offset, bool salvage, bool quiet)
+ {
+ int num_folders, num_files, folder_resv, i, x;
+ Error err = Error.MSPACK_ERR_OK;
+ FileFlags fidx;
+ FolderImpl fol, linkfol = null;
+ InternalFile file, linkfile = null;
+ byte[] buf = new byte[64];
+
+ // Initialise pointers
+ if (cab == null)
+ cab = new CabinetImpl();
+
+ cab.Next = null;
+ cab.Files = null;
+ cab.Folders = null;
+ cab.PreviousCabinet = cab.NextCabinet = null;
+ cab.PreviousName = cab.NextName = null;
+ cab.PreviousInfo = cab.NextInfo = null;
+
+ cab.BaseOffset = offset;
+
+ // Seek to CFHEADER
+ if (!sys.Seek(fh, offset, SeekMode.MSPACK_SYS_SEEK_START))
+ return Error.MSPACK_ERR_SEEK;
+
+ // Read in the CFHEADER
+ if (sys.Read(fh, buf, 0, cfhead_SIZEOF) != cfhead_SIZEOF)
+ return Error.MSPACK_ERR_READ;
+
+ // Check for "MSCF" signature
+ if (BitConverter.ToUInt32(buf, cfhead_Signature) != 0x4643534D)
+ return Error.MSPACK_ERR_SIGNATURE;
+
+ // Some basic header fields
+ cab.Length = BitConverter.ToUInt32(buf, cfhead_CabinetSize);
+ cab.SetID = BitConverter.ToUInt16(buf, cfhead_SetID);
+ cab.SetIndex = BitConverter.ToUInt16(buf, cfhead_CabinetIndex);
+
+ // Get the number of folders
+ num_folders = BitConverter.ToUInt16(buf, cfhead_NumFolders);
+ if (num_folders == 0)
+ {
+ if (!quiet) sys.Message(fh, "no folders in cabinet.");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Get the number of files
+ num_files = BitConverter.ToUInt16(buf, cfhead_NumFiles);
+ if (num_files == 0)
+ {
+ if (!quiet) sys.Message(fh, "no files in cabinet.");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Check cabinet version
+ if ((buf[cfhead_MajorVersion] != 1) && (buf[cfhead_MinorVersion] != 3))
+ {
+ if (!quiet) sys.Message(fh, "WARNING; cabinet version is not 1.3");
+ }
+
+ // Read the reserved-sizes part of header, if present
+ cab.Flags = (HeaderFlags)BitConverter.ToUInt16(buf, cfhead_Flags);
+
+ if (cab.Flags.HasFlag(HeaderFlags.MSCAB_HDR_RESV))
+ {
+ if (sys.Read(fh, buf, 0, cfheadext_SIZEOF) != cfheadext_SIZEOF)
+ return Error.MSPACK_ERR_READ;
+
+ cab.HeaderResv = BitConverter.ToUInt16(buf, cfheadext_HeaderReserved);
+ folder_resv = buf[cfheadext_FolderReserved];
+ cab.BlockResverved = buf[cfheadext_DataReserved];
+
+ if (cab.HeaderResv > 60000)
+ {
+ if (!quiet) sys.Message(fh, "WARNING; reserved header > 60000.");
+ }
+
+ // Skip the reserved header
+ if (cab.HeaderResv != 0)
+ {
+ if (!sys.Seek(fh, cab.HeaderResv, SeekMode.MSPACK_SYS_SEEK_CUR))
+ return Error.MSPACK_ERR_SEEK;
+ }
+ }
+ else
+ {
+ cab.HeaderResv = 0;
+ folder_resv = 0;
+ cab.BlockResverved = 0;
+ }
+
+ // Read name and info of preceeding cabinet in set, if present
+ if (cab.Flags.HasFlag(HeaderFlags.MSCAB_HDR_PREVCAB))
+ {
+ cab.PreviousName = ReadString(sys, fh, false, ref err);
+ if (err != Error.MSPACK_ERR_OK)
+ return err;
+
+ cab.PreviousInfo = ReadString(sys, fh, true, ref err);
+ if (err != Error.MSPACK_ERR_OK)
+ return err;
+ }
+
+ // Read name and info of next cabinet in set, if present
+ if (cab.Flags.HasFlag(HeaderFlags.MSCAB_HDR_NEXTCAB))
+ {
+ cab.NextName = ReadString(sys, fh, false, ref err);
+ if (err != Error.MSPACK_ERR_OK)
+ return err;
+
+ cab.NextInfo = ReadString(sys, fh, true, ref err);
+ if (err != Error.MSPACK_ERR_OK)
+ return err;
+ }
+
+ // Read folders
+ for (i = 0; i < num_folders; i++)
+ {
+ if (sys.Read(fh, buf, 0, cffold_SIZEOF) != cffold_SIZEOF)
+ return Error.MSPACK_ERR_READ;
+
+ if (folder_resv != 0)
+ {
+ if (!sys.Seek(fh, folder_resv, SeekMode.MSPACK_SYS_SEEK_CUR))
+ return Error.MSPACK_ERR_SEEK;
+ }
+
+ fol = new FolderImpl();
+
+ fol.Next = null;
+ fol.CompressionType = (CompressionType)BitConverter.ToUInt16(buf, cffold_CompType);
+ fol.NumBlocks = BitConverter.ToUInt16(buf, cffold_NumBlocks);
+ fol.MergePrev = null;
+ fol.MergeNext = null;
+
+ fol.Data = new FolderData();
+ fol.Data.Next = null;
+ fol.Data.Cab = cab;
+ fol.Data.Offset = offset + (int)(BitConverter.ToUInt32(buf, cffold_DataOffset));
+
+ // Link folder into list of folders
+ if (linkfol == null)
+ cab.Folders = fol;
+ else
+ linkfol.Next = fol;
+
+ linkfol = fol;
+ }
+
+ // Read files
+ for (i = 0; i < num_files; i++)
+ {
+ if (sys.Read(fh, buf, 0, cffile_SIZEOF) != cffile_SIZEOF)
+ return Error.MSPACK_ERR_READ;
+
+ file = new InternalFile();
+
+ file.Next = null;
+ file.Length = BitConverter.ToUInt32(buf, cffile_UncompressedSize);
+ file.Attributes = (FileAttributes)BitConverter.ToUInt16(buf, cffile_Attribs);
+ file.Offset = BitConverter.ToUInt32(buf, cffile_FolderOffset);
+
+ // Set folder pointer
+ fidx = (FileFlags)BitConverter.ToUInt16(buf, cffile_FolderIndex);
+ if (fidx < FileFlags.CONTINUED_FROM_PREV)
+ {
+ // Normal folder index; count up to the correct folder
+ if ((int)fidx < num_folders)
+ {
+ Folder ifol = cab.Folders;
+ while (fidx-- != 0)
+ {
+ if (ifol != null)
+ ifol = ifol.Next;
+ }
+
+ file.Folder = ifol;
+ }
+ else
+ {
+ Console.WriteLine("invalid folder index");
+ file.Folder = null;
+ }
+ }
+ else
+ {
+ // Either CONTINUED_TO_NEXT, CONTINUED_FROM_PREV or CONTINUED_PREV_AND_NEXT
+ if (fidx == FileFlags.CONTINUED_TO_NEXT || fidx == FileFlags.CONTINUED_PREV_AND_NEXT)
+ {
+ // Get last folder
+ Folder ifol = cab.Folders;
+ while (ifol.Next != null)
+ {
+ ifol = ifol.Next;
+ }
+
+ file.Folder = ifol;
+
+ // Set "merge next" pointer
+ fol = (FolderImpl)ifol;
+ if (fol.MergeNext == null)
+ fol.MergeNext = file;
+ }
+
+ if (fidx == FileFlags.CONTINUED_FROM_PREV || fidx == FileFlags.CONTINUED_PREV_AND_NEXT)
+ {
+ // Get first folder
+ file.Folder = cab.Folders;
+
+ // Set "merge prev" pointer
+ fol = (FolderImpl)file.Folder;
+ if (fol.MergePrev == null)
+ fol.MergePrev = file;
+ }
+ }
+
+ // Get time
+ x = BitConverter.ToUInt16(buf, cffile_Time);
+ file.LastModifiedTimeHour = (byte)(x >> 11);
+ file.LastModifiedTimeMinute = (byte)((x >> 5) & 0x3F);
+ file.LastModifiedTimeSecond = (byte)((x << 1) & 0x3E);
+
+ // Get date
+ x = BitConverter.ToUInt16(buf, cffile_Date);
+ file.LastModifiedDateDay = (byte)(x & 0x1F);
+ file.LastModifiedDateMonth = (byte)((x >> 5) & 0xF);
+ file.LastModifiedDateYear = (x >> 9) + 1980;
+
+ // Get filename
+ file.Filename = ReadString(sys, fh, false, ref err);
+
+ // If folder index or filename are bad, either skip it or fail
+ if (err != Error.MSPACK_ERR_OK || file.Folder == null)
+ {
+ sys.Free(file.Filename);
+ sys.Free(file);
+ if (salvage)
+ continue;
+
+ return err != Error.MSPACK_ERR_OK ? err : Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Link file entry into file list
+ if (linkfile == null)
+ cab.Files = file;
+ else
+ linkfile.Next = file;
+
+ linkfile = file;
+ }
+
+ if (cab.Files == null)
+ {
+ // We never actually added any files to the file list. Something went wrong.
+ // The file header may have been invalid */
+ Console.WriteLine($"No files found, even though header claimed to have {num_files} files");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ private static string ReadString(SystemImpl sys, object fh, bool permitEmpty, ref Error error)
+ {
+ long position = sys.Tell(fh);
+ byte[] buf = new byte[256];
+ int len, i;
+
+ // Read up to 256 bytes
+ if ((len = sys.Read(fh, buf, 0, 256)) <= 0)
+ {
+ error = Error.MSPACK_ERR_READ;
+ return null;
+ }
+
+ // Search for a null terminator in the buffer
+ bool ok = false;
+ for (i = 0; i < len; i++)
+ {
+ if (buf[i] == 0x00)
+ {
+ ok = true;
+ break;
+ }
+ }
+
+ // Optionally reject empty strings
+ if (i == 0 && !permitEmpty)
+ ok = false;
+
+ if (!ok)
+ {
+ error = Error.MSPACK_ERR_DATAFORMAT;
+ return null;
+ }
+
+ len = i + 1;
+
+ // Set the data stream to just after the string and return
+ if (!sys.Seek(fh, position + len, SeekMode.MSPACK_SYS_SEEK_START))
+ {
+ error = Error.MSPACK_ERR_SEEK;
+ return null;
+ }
+
+ error = Error.MSPACK_ERR_OK;
+ return Encoding.ASCII.GetString(buf, 0, len);
+ }
+
+ #endregion
+
+ #region CABD_SEARCH, CABD_FIND
+
+ ///
+ /// Opens a file, finds its extent, allocates a search buffer,
+ /// then reads through the whole file looking for possible cabinet headers.
+ /// If it finds any, it tries to read them as real cabinets. Returns a linked
+ /// list of results
+ ///
+ public static Cabinet Search(Decompressor d, string filename)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+
+ if (self == null)
+ return null;
+
+ SystemImpl sys = self.System;
+
+ // Allocate a search buffer
+ byte[] search_buf = sys.Alloc(sys, self.SearchBufferSize);
+ if (search_buf == null)
+ {
+ self.Error = Error.MSPACK_ERR_NOMEMORY;
+ return null;
+ }
+
+ // Open file and get its full file length
+ object fh; CabinetImpl cab = null;
+ if ((fh = sys.Open(sys, filename, OpenMode.MSPACK_SYS_OPEN_READ)) != null)
+ {
+ long firstlen = 0;
+ if ((self.Error = SystemImpl.GetFileLength(sys, fh, out long filelen)) == Error.MSPACK_ERR_OK)
+ self.Error = Find(self, search_buf, fh, filename, filelen, ref firstlen, out cab);
+
+ // Truncated / extraneous data warning:
+ if (firstlen != 0 && (firstlen != filelen) && (cab == null || cab.BaseOffset == 0))
+ {
+ if (firstlen < filelen)
+ sys.Message(fh, $"WARNING; possible {filelen - firstlen} extra bytes at end of file.");
+ else
+ sys.Message(fh, $"WARNING; file possibly truncated by {firstlen - filelen} bytes.");
+ }
+
+ sys.Close(fh);
+ }
+ else
+ {
+ self.Error = Error.MSPACK_ERR_OPEN;
+ }
+
+ // Free the search buffer
+ sys.Free(search_buf);
+
+ return cab;
+ }
+
+ ///
+ /// The inner loop of , to make it easier to
+ /// break out of the loop and be sure that all resources are freed
+ ///
+ public static Error Find(DecompressorImpl self, byte[] buf, object fh, string filename, long flen, ref long firstlen, out CabinetImpl firstcab)
+ {
+ firstcab = null;
+ CabinetImpl cab, link = null;
+ long caboff, offset, length;
+ SystemImpl sys = self.System;
+ long p, pend;
+ byte state = 0;
+ uint cablen_u32 = 0, foffset_u32 = 0;
+ int false_cabs = 0;
+
+ // Search through the full file length
+ for (offset = 0; offset < flen; offset += length)
+ {
+ // Search length is either the full length of the search buffer, or the
+ // amount of data remaining to the end of the file, whichever is less.
+ length = flen - offset;
+ if (length > self.SearchBufferSize)
+ length = self.SearchBufferSize;
+
+ // Fill the search buffer with data from disk
+ if (sys.Read(fh, buf, 0, (int)length) != (int)length)
+ return Error.MSPACK_ERR_READ;
+
+ // FAQ avoidance strategy
+ if (offset == 0 && BitConverter.ToUInt32(buf, 0) == 0x28635349)
+ sys.Message(fh, "WARNING; found InstallShield header. Use unshield (https://github.com/twogood/unshield) to unpack this file");
+
+ // Read through the entire buffer.
+ for (p = 0, pend = length; p < pend;)
+ {
+ switch (state)
+ {
+ // Starting state
+ case 0:
+ // We spend most of our time in this while loop, looking for
+ // a leading 'M' of the 'MSCF' signature
+ while (p < pend && buf[p] != 0x4D)
+ {
+ p++;
+ }
+
+ // If we found that 'M', advance state
+ if (p++ < pend)
+ state = 1;
+
+ break;
+
+ // Verify that the next 3 bytes are 'S', 'C' and 'F'
+ case 1:
+ state = (byte)(buf[p++] == 0x53 ? 2 : 0);
+ break;
+ case 2:
+ state = (byte)(buf[p++] == 0x43 ? 3 : 0);
+ break;
+ case 3:
+ state = (byte)(buf[p++] == 0x46 ? 4 : 0);
+ break;
+
+ // We don't care about bytes 4-7 (see default: for action)
+
+ // Bytes 8-11 are the overall length of the cabinet
+ case 8:
+ cablen_u32 = buf[p++];
+ state++;
+ break;
+ case 9:
+ cablen_u32 |= (uint)buf[p++] << 8;
+ state++;
+ break;
+ case 10:
+ cablen_u32 |= (uint)buf[p++] << 16;
+ state++;
+ break;
+ case 11:
+ cablen_u32 |= (uint)buf[p++] << 24;
+ state++;
+ break;
+
+ // We don't care about bytes 12-15 (see default: for action)
+
+ // Bytes 16-19 are the offset within the cabinet of the filedata */
+ case 16:
+ foffset_u32 = buf[p++];
+ state++;
+ break;
+ case 17:
+ foffset_u32 |= (uint)buf[p++] << 8;
+ state++;
+ break;
+ case 18:
+ foffset_u32 |= (uint)buf[p++] << 16;
+ state++;
+ break;
+ case 19:
+ foffset_u32 |= (uint)buf[p++] << 24;
+
+ // Now we have recieved 20 bytes of potential cab header. work out
+ // the offset in the file of this potential cabinet
+ caboff = offset + p - 20;
+
+ // Should reading cabinet fail, restart search just after 'MSCF'
+ offset = caboff + 4;
+
+ // Vapture the "length of cabinet" field if there is a cabinet at
+ // offset 0 in the file, regardless of whether the cabinet can be
+ // read correctly or not
+ if (caboff == 0)
+ firstlen = cablen_u32;
+
+ // Check that the files offset is less than the alleged length of
+ // the cabinet, and that the offset + the alleged length are
+ // 'roughly' within the end of overall file length. In salvage
+ // mode, don't check the alleged length, allow it to be garbage */
+ if ((foffset_u32 < cablen_u32) &&
+ ((caboff + foffset_u32) < (flen + 32)) &&
+ (((caboff + cablen_u32) < (flen + 32)) || self.Salvage))
+ {
+ // Likely cabinet found -- try reading it
+ cab = new CabinetImpl();
+ cab.Filename = filename;
+
+ if (ReadHeaders(sys, fh, cab, caboff, self.Salvage, quiet: true) != Error.MSPACK_ERR_OK)
+ {
+ // Destroy the failed cabinet
+ Close(self, cab);
+ false_cabs++;
+ }
+ else
+ {
+ // Cabinet read correctly!
+
+ // Link the cab into the list
+ if (link == null)
+ firstcab = cab;
+ else
+ link.Next = cab;
+
+ link = cab;
+
+ // Cause the search to restart after this cab's data.
+ offset = caboff + cablen_u32;
+ }
+ }
+
+ // Restart search
+ if (offset >= flen)
+ return Error.MSPACK_ERR_OK;
+
+ if (!sys.Seek(fh, offset, SeekMode.MSPACK_SYS_SEEK_START))
+ return Error.MSPACK_ERR_SEEK;
+
+ length = 0;
+ p = pend;
+ state = 0;
+ break;
+
+ // For bytes 4-7 and 12-15, just advance state/pointer
+ default:
+ p++;
+ state++;
+ break;
+ }
+ }
+ }
+
+ if (false_cabs != 0)
+ Console.WriteLine($"{false_cabs} false cabinets found");
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region CABD_MERGE, CABD_PREPEND, CABD_APPEND
+
+ ///
+ public static Error Prepend(Decompressor d, Cabinet cab, Cabinet prevcab)
+ {
+ return Merge(d, prevcab, cab);
+ }
+
+ ///
+ public static Error Append(Decompressor d, Cabinet cab, Cabinet nextcab)
+ {
+ return Merge(d, cab, nextcab);
+ }
+
+ ///
+ /// Joins cabinets together, also merges split folders between these two
+ /// cabinets only. This includes freeing the duplicate folder and file(s)
+ /// and allocating a further mscabd_folder_data structure to append to the
+ /// merged folder's data parts list.
+ ///
+ public static Error Merge(Decompressor d, Cabinet lcab, Cabinet rcab)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+
+ FolderData data, ndata;
+ FolderImpl lfol, rfol;
+ InternalFile fi, rfi, lfi;
+
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ SystemImpl sys = self.System;
+
+ // Basic args check
+ if (lcab == null || rcab == null || (lcab == rcab))
+ {
+ Console.WriteLine("lcab null, rcab null or lcab = rcab");
+ return self.Error = Error.MSPACK_ERR_ARGS;
+ }
+
+ // Check there's not already a cabinet attached
+ if (lcab.NextCabinet != null || rcab.PreviousCabinet != null)
+ {
+ Console.WriteLine("cabs already joined");
+ return self.Error = Error.MSPACK_ERR_ARGS;
+ }
+
+ // Do not create circular cabinet chains
+ Cabinet cab;
+ for (cab = lcab.PreviousCabinet; cab != null; cab = cab.PreviousCabinet)
+ {
+ if (cab == rcab)
+ {
+ Console.WriteLine("circular!");
+ return self.Error = Error.MSPACK_ERR_ARGS;
+ }
+ }
+ for (cab = rcab.NextCabinet; cab != null; cab = cab.NextCabinet)
+ {
+ if (cab == lcab)
+ {
+ Console.WriteLine("circular!");
+ return self.Error = Error.MSPACK_ERR_ARGS;
+ }
+ }
+
+ // Warn about odd set IDs or indices
+ if (lcab.SetID != rcab.SetID)
+ sys.Message(null, "WARNING; merged cabinets with differing Set IDs.");
+
+ if (lcab.SetIndex > rcab.SetIndex)
+ sys.Message(null, "WARNING; merged cabinets with odd order.");
+
+ // Merging the last folder in lcab with the first folder in rcab
+ lfol = (FolderImpl)lcab.Folders;
+ rfol = (FolderImpl)rcab.Folders;
+ while (lfol.Next != null)
+ {
+ lfol = (FolderImpl)lfol.Next;
+ }
+
+ // Do we need to merge folders?
+ if (lfol.MergeNext == null && rfol.MergePrev == null)
+ {
+ // No, at least one of the folders is not for merging
+
+ // Attach cabs
+ lcab.NextCabinet = rcab;
+ rcab.PreviousCabinet = lcab;
+
+ // Attach folders
+ lfol.Next = rfol;
+
+ // Attach files
+ fi = lcab.Files;
+ while (fi.Next != null)
+ {
+ fi = fi.Next;
+ }
+
+ fi.Next = rcab.Files;
+ }
+ else
+ {
+ // Folder merge required - do the files match?
+ if (!CanMergeFolders(sys, lfol, rfol))
+ return self.Error = Error.MSPACK_ERR_DATAFORMAT;
+
+ // Allocate a new folder data structure
+ data = new FolderData();
+
+ // Attach cabs
+ lcab.NextCabinet = rcab;
+ rcab.PreviousCabinet = lcab;
+
+ // Append rfol's data to lfol
+ ndata = lfol.Data;
+ while (ndata.Next != null)
+ {
+ ndata = ndata.Next;
+ }
+
+ ndata.Next = data;
+ data = rfol.Data;
+ rfol.Data.Next = null;
+
+ // lfol becomes rfol.
+ // NOTE: special case, don't merge if rfol is merge prev and next,
+ // rfol.MergeNext is going to be deleted, so keep lfol's version
+ // instead
+ lfol.NumBlocks += (ushort)(rfol.NumBlocks - 1);
+ if ((rfol.MergeNext == null) || (rfol.MergeNext.Folder != rfol))
+ lfol.MergeNext = rfol.MergeNext;
+
+ // Attach the rfol's folder (except the merge folder)
+ while (lfol.Next != null)
+ {
+ lfol = (FolderImpl)lfol.Next;
+ }
+
+ lfol.Next = rfol.Next;
+
+ // Free disused merge folder
+ sys.Free(rfol);
+
+ // Attach rfol's files
+ fi = lcab.Files;
+ while (fi.Next != null)
+ {
+ fi = fi.Next;
+ }
+
+ fi.Next = rcab.Files;
+
+ // Delete all files from rfol's merge folder
+ lfi = null;
+ for (fi = lcab.Files; fi != null; fi = rfi)
+ {
+ rfi = fi.Next;
+
+ // If file's folder matches the merge folder, unlink and free it
+ if (fi.Folder == rfol)
+ {
+ if (lfi != null)
+ lfi.Next = rfi;
+ else
+ lcab.Files = rfi;
+
+ sys.Free(fi.Filename);
+ sys.Free(fi);
+ }
+ else
+ {
+ lfi = fi;
+ }
+ }
+ }
+
+ // All done! fix files and folders pointers in alsl cabs so they all
+ // point to the same list
+ for (cab = lcab.PreviousCabinet; cab != null; cab = cab.PreviousCabinet)
+ {
+ cab.Files = lcab.Files;
+ cab.Folders = lcab.Folders;
+ }
+
+ for (cab = lcab.NextCabinet; cab != null; cab = cab.NextCabinet)
+ {
+ cab.Files = lcab.Files;
+ cab.Folders = lcab.Folders;
+ }
+
+ return self.Error = Error.MSPACK_ERR_OK;
+ }
+
+ ///
+ /// Decides if two folders are OK to merge
+ ///
+ private static bool CanMergeFolders(SystemImpl sys, FolderImpl lfol, FolderImpl rfol)
+ {
+ InternalFile lfi, rfi, l, r;
+ bool matching = true;
+
+ // Check that both folders use the same compression method/settings
+ if (lfol.CompressionType != rfol.CompressionType)
+ {
+ Console.WriteLine("folder merge: compression type mismatch");
+ return false;
+ }
+
+ // Check there are not too many data blocks after merging
+ if ((lfol.NumBlocks + rfol.NumBlocks) > CAB_FOLDERMAX)
+ {
+ Console.WriteLine("folder merge: too many data blocks in merged folders");
+ return false;
+ }
+
+ if ((lfi = lfol.MergeNext) == null || (rfi = rfol.MergePrev) == null)
+ {
+ Console.WriteLine("folder merge: one cabinet has no files to merge");
+ return false;
+ }
+
+ // For all files in lfol (which is the last folder in whichever cab and
+ // only has files to merge), compare them to the files from rfol. They
+ // should be identical in number and order. to verify this, check the
+ // offset and length of each file.
+ for (l = lfi, r = rfi; l != null; l = l.Next, r = r.Next)
+ {
+ if (r == null || (l.Offset != r.Offset) || (l.Length != r.Length))
+ {
+ matching = false;
+ break;
+ }
+ }
+
+ if (matching)
+ return true;
+
+ // If rfol does not begin with an identical copy of the files in lfol, make
+ // make a judgement call; if at least ONE file from lfol is in rfol, allow
+ // the merge with a warning about missing files.
+ matching = false;
+ for (l = lfi; l != null; l = l.Next)
+ {
+ for (r = rfi; r != null; r = r.Next)
+ {
+ if (l.Offset == r.Offset && l.Length == r.Length)
+ break;
+ }
+
+ if (r != null)
+ matching = true;
+ else
+ sys.Message(null, $"WARNING; merged file {l.Filename} not listed in both cabinets");
+ }
+
+ return matching;
+ }
+
+ #endregion
+
+ #region CABD_EXTRACT
+
+ ///
+ /// Extracts a file from a cabinet
+ ///
+ public static Error Extract(Decompressor d, InternalFile file, string filename)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ object fh;
+
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+ if (file == null)
+ return self.Error = Error.MSPACK_ERR_ARGS;
+
+ SystemImpl sys = self.System;
+ FolderImpl fol = (FolderImpl)file.Folder;
+
+ // If offset is beyond 2GB, nothing can be extracted
+ if (file.Offset > CAB_LENGTHMAX)
+ return self.Error = Error.MSPACK_ERR_DATAFORMAT;
+
+ // If file claims to go beyond 2GB either error out,
+ // or in salvage mode reduce file length so it fits 2GB limit
+ long filelen = file.Length;
+ if (filelen > CAB_LENGTHMAX || (file.Offset + filelen) > CAB_LENGTHMAX)
+ {
+ if (self.Salvage)
+ filelen = CAB_LENGTHMAX - file.Offset;
+ else
+ return self.Error = Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Extraction impossible if no folder, or folder needs predecessor
+ if (fol == null || fol.MergePrev != null)
+ {
+ sys.Message(null, $"ERROR; file \"{file.Filename}\" cannot be extracted, cabinet set is incomplete");
+ return self.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // If file goes beyond what can be decoded, given an error.
+ // In salvage mode, don't assume block sizes, just try decoding
+ if (!self.Salvage)
+ {
+ long maxlen = fol.NumBlocks * CAB_BLOCKMAX;
+ if ((file.Offset + filelen) > maxlen)
+ {
+ sys.Message(null, $"ERROR; file \"{file.Filename}\" cannot be extracted, cabinet set is incomplete");
+ return self.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+ }
+
+ // Allocate generic decompression state
+ if (self.State == null)
+ {
+ self.State = new DecompressState();
+ self.State.Folder = null;
+ self.State.Data = null;
+ self.State.Sys = sys;
+ self.State.Sys.Read = SysRead;
+ self.State.Sys.Write = SysWrite;
+ self.State.DecompressorState = null;
+ self.State.InputFileHandle = null;
+ self.State.InputCabinet = null;
+ }
+
+ // Do we need to change folder or reset the current folder?
+ if ((self.State.Folder != fol) || (self.State.Offset > file.Offset) || self.State.DecompressorState == null)
+ {
+ // Free any existing decompressor
+ FreeDecompressionState(self);
+
+ // Do we need to open a new cab file?
+ if (self.State.InputFileHandle == null || (fol.Data.Cab != self.State.InputCabinet))
+ {
+ // Close previous file handle if from a different cab
+ if (self.State.InputFileHandle != null)
+ sys.Close(self.State.InputFileHandle);
+
+ self.State.InputCabinet = fol.Data.Cab;
+ self.State.InputFileHandle = sys.Open(sys, fol.Data.Cab.Filename, OpenMode.MSPACK_SYS_OPEN_READ);
+ if (self.State.InputFileHandle == null)
+ return self.Error = Error.MSPACK_ERR_OPEN;
+ }
+
+ // Seek to start of data blocks
+ if (!sys.Seek(self.State.InputFileHandle, fol.Data.Offset, SeekMode.MSPACK_SYS_SEEK_START))
+ return self.Error = Error.MSPACK_ERR_SEEK;
+
+ // Set up decompressor
+ if (InitDecompressionState(self, fol.CompressionType) != Error.MSPACK_ERR_OK)
+ return self.Error;
+
+ // Initialise new folder state
+ self.State.Folder = fol;
+ self.State.Data = fol.Data;
+ self.State.Offset = 0;
+ self.State.Block = 0;
+ self.State.Outlen = 0;
+ self.State.IPtr = self.State.IEnd = 0;
+
+ // read_error lasts for the lifetime of a decompressor
+ self.ReadError = Error.MSPACK_ERR_OK;
+ }
+
+ // Open file for output
+ if ((fh = sys.Open(sys, filename, OpenMode.MSPACK_SYS_OPEN_WRITE)) == null)
+ return self.Error = Error.MSPACK_ERR_OPEN;
+
+ self.Error = Error.MSPACK_ERR_OK;
+
+ // If file has more than 0 bytes
+ if (filelen != 0)
+ {
+ long bytes;
+ Error error;
+ // Get to correct offset.
+ // - use null fh to say 'no writing' to cabd_sys_write()
+ // - if cabd_sys_read() has an error, it will set self.ReadError
+ // and pass back MSPACK_ERR_READ
+ self.State.OutputFileHandle = null;
+ if ((bytes = file.Offset - self.State.Offset) != 0)
+ {
+ error = self.State.Decompress(self.State.DecompressorState, bytes);
+ self.Error = (error == Error.MSPACK_ERR_READ) ? self.ReadError : error;
+ }
+
+ // If getting to the correct offset was error free, unpack file
+ if (self.Error == Error.MSPACK_ERR_OK)
+ {
+ self.State.OutputFileHandle = fh;
+ error = self.State.Decompress(self.State.DecompressorState, filelen);
+ self.Error = (error == Error.MSPACK_ERR_READ) ? self.ReadError : error;
+ }
+ }
+
+ // Close output file
+ sys.Close(fh);
+ self.State.OutputFileHandle = null;
+
+ return self.Error;
+ }
+
+ #endregion
+
+ #region CABD_INIT_DECOMP, CABD_FREE_DECOMP
+
+ ///
+ /// Initialises decompression state, according to which
+ /// decompression method was used. relies on self.State.Folder being the same
+ /// as when initialised.
+ ///
+ public static Error InitDecompressionState(DecompressorImpl self, CompressionType ct)
+ {
+ object fh = self;
+
+ self.State.CompressionType = ct;
+
+ switch (ct & CompressionType.COMPTYPE_MASK)
+ {
+ case CompressionType.COMPTYPE_NONE:
+ self.State.Decompress = NoneDecompress;
+ self.State.DecompressorState = NoneInit(self.State.Sys, fh, fh, self.BufferSize);
+ break;
+
+ case CompressionType.COMPTYPE_MSZIP:
+ self.State.Decompress = MSZIP.Decompress;
+ self.State.DecompressorState = MSZIP.Init(self.State.Sys, fh, fh, self.BufferSize, self.FixMSZip);
+ break;
+
+ case CompressionType.COMPTYPE_QUANTUM:
+ self.State.Decompress = QTM.Decompress;
+ self.State.DecompressorState = QTM.Init(self.State.Sys, fh, fh, ((ushort)ct >> 8) & 0x1f, self.BufferSize);
+ break;
+
+ case CompressionType.COMPTYPE_LZX:
+ self.State.Decompress = LZX.Decompress;
+ self.State.DecompressorState = LZX.Init(self.State.Sys, fh, fh, ((ushort)ct >> 8) & 0x1f, 0, self.BufferSize, 0, false);
+ break;
+
+ default:
+ return self.Error = Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ return self.Error = (self.State.DecompressorState != null) ? Error.MSPACK_ERR_OK : Error.MSPACK_ERR_NOMEMORY;
+ }
+
+ ///
+ /// Frees decompression state, according to which method was used.
+ ///
+ ///
+ public static void FreeDecompressionState(DecompressorImpl self)
+ {
+ if (self == null || self.State == null || self.State.DecompressorState == null)
+ return;
+
+ switch (self.State.CompressionType & CompressionType.COMPTYPE_MASK)
+ {
+ case CompressionType.COMPTYPE_NONE:
+ NoneFree((NoneState)self.State.DecompressorState);
+ break;
+
+ case CompressionType.COMPTYPE_MSZIP:
+ MSZIP.Free((MSZIPDStream)self.State.DecompressorState);
+ break;
+
+ case CompressionType.COMPTYPE_QUANTUM:
+ QTM.Free((QTMDStream)self.State.DecompressorState);
+ break;
+
+ case CompressionType.COMPTYPE_LZX:
+ LZX.Free((LZXDStream)self.State.DecompressorState);
+ break;
+ }
+
+ self.State.Decompress = null;
+ self.State.DecompressorState = null;
+ }
+
+ #endregion
+
+ #region CABD_SYS_READ, CABD_SYS_WRITE
+
+ ///
+ /// The internal reader function which the decompressors
+ /// use. will read data blocks (and merge split blocks) from the cabinet
+ /// and serve the read bytes to the decompressors
+ ///
+ private static int SysRead(object file, byte[] buffer, int pointer, int bytes)
+ {
+ DecompressorImpl self = (DecompressorImpl)file;
+ SystemImpl sys = self.System;
+ int avail, todo, outlen = 0;
+
+ bool ignore_cksum = self.Salvage ||
+ (self.FixMSZip &&
+ ((self.State.CompressionType & CompressionType.COMPTYPE_MASK) == CompressionType.COMPTYPE_MSZIP));
+ bool ignore_blocksize = self.Salvage;
+
+ todo = bytes;
+ while (todo > 0)
+ {
+ avail = self.State.IEnd - self.State.IPtr;
+
+ // If out of input data, read a new block
+ if (avail != 0)
+ {
+ // Copy as many input bytes available as possible
+ if (avail > todo)
+ avail = todo;
+
+ sys.Copy(self.State.Input, self.State.IPtr, buffer, pointer, avail);
+ self.State.IPtr += avail;
+ pointer += avail;
+ todo -= avail;
+ }
+ else
+ {
+ // Out of data, read a new block
+
+ // Check if we're out of input blocks, advance block counter
+ if (self.State.Block++ >= self.State.Folder.NumBlocks)
+ {
+ if (!self.Salvage)
+ self.ReadError = Error.MSPACK_ERR_DATAFORMAT;
+ else
+ Console.WriteLine("Ran out of CAB input blocks prematurely");
+
+ break;
+ }
+
+ // Read a block
+ self.ReadError = SysReadBlock(sys, self.State, ref outlen, ignore_cksum, ignore_blocksize);
+ if (self.ReadError != Error.MSPACK_ERR_OK)
+ return -1;
+
+ self.State.Outlen += outlen;
+
+ // Special Quantum hack -- trailer byte to allow the decompressor
+ // to realign itself. CAB Quantum blocks, unlike LZX blocks, can have
+ // anything from 0 to 4 trailing null bytes.
+ if ((self.State.CompressionType & CompressionType.COMPTYPE_MASK) == CompressionType.COMPTYPE_QUANTUM)
+ self.State.Input[self.State.IEnd++] = 0xFF;
+
+ // Is this the last block?
+ if (self.State.Block >= self.State.Folder.NumBlocks)
+ {
+ if ((self.State.CompressionType & CompressionType.COMPTYPE_MASK) == CompressionType.COMPTYPE_LZX)
+ {
+ // Special LZX hack -- on the last block, inform LZX of the
+ // size of the output data stream.
+ LZX.SetOutputLength((LZXDStream)self.State.DecompressorState, self.State.Outlen);
+ }
+ }
+ }
+ }
+
+ return bytes - todo;
+ }
+
+ ///
+ /// The internal writer function which the decompressors
+ /// use. it either writes data to disk (self.State.OutputFileHandle) with the real
+ /// sys.write() function, or does nothing with the data when
+ /// self.State.OutputFileHandle == null. advances self.State.Offset
+ ///
+ private static int SysWrite(object file, byte[] buffer, int pointer, int bytes)
+ {
+ DecompressorImpl self = (DecompressorImpl)file;
+ self.State.Offset += (uint)bytes;
+ if (self.State.OutputFileHandle != null)
+ return self.System.Write(self.State.OutputFileHandle, buffer, pointer, bytes);
+
+ return bytes;
+ }
+
+ #endregion
+
+ #region CABD_SYS_READ_BLOCK
+
+ ///
+ /// Reads a whole data block from a cab file. The block may span more than
+ /// one cab file, if it does then the fragments will be reassembled
+ ///
+ private static Error SysReadBlock(SystemImpl sys, DecompressState d, ref int output, bool ignore_cksum, bool ignore_blocksize)
+ {
+ byte[] hdr = new byte[cfdata_SIZEOF];
+ uint cksum;
+ int len, full_len;
+
+ // Reset the input block pointer and end of block pointer
+ d.IPtr = d.IEnd = 0;
+
+ do
+ {
+ // Read the block header
+ if (sys.Read(d.InputFileHandle, hdr, 0, cfdata_SIZEOF) != cfdata_SIZEOF)
+ return Error.MSPACK_ERR_READ;
+
+ // Skip any reserved block headers
+ if (d.Data.Cab.HeaderResv != 0 && !sys.Seek(d.InputFileHandle, d.Data.Cab.HeaderResv, SeekMode.MSPACK_SYS_SEEK_CUR))
+ return Error.MSPACK_ERR_SEEK;
+
+ // Blocks must not be over CAB_INPUTMAX in size
+ len = BitConverter.ToUInt16(hdr, cfdata_CompressedSize);
+ full_len = (d.IEnd - d.IPtr) + len; // Include cab-spanning blocks
+ if (full_len > CAB_INPUTMAX)
+ {
+ Console.WriteLine($"block size {full_len} > CAB_INPUTMAX");
+
+ // In salvage mode, blocks can be 65535 bytes but no more than that
+ if (!ignore_blocksize || full_len > CAB_INPUTMAX_SALVAGE)
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Blocks must not expand to more than CAB_BLOCKMAX
+ if (BitConverter.ToUInt16(hdr, cfdata_UncompressedSize) > CAB_BLOCKMAX)
+ {
+ Console.WriteLine("block size > CAB_BLOCKMAX");
+ if (!ignore_blocksize)
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Read the block data
+ if (sys.Read(d.InputFileHandle, d.Input, d.IEnd, len) != len)
+ return Error.MSPACK_ERR_READ;
+
+ // Perform checksum test on the block (if one is stored)
+ if ((cksum = BitConverter.ToUInt32(hdr, cfdata_CheckSum)) != 0)
+ {
+ uint sum2 = Checksum(d.Input, d.IEnd, (uint)len, 0);
+ if (Checksum(hdr, 4, 4, sum2) != cksum)
+ {
+ if (!ignore_cksum)
+ return Error.MSPACK_ERR_CHECKSUM;
+
+ sys.Message(d.InputFileHandle, "WARNING; bad block checksum found");
+ }
+ }
+
+ // Advance end of block pointer to include newly read data
+ d.IEnd += len;
+
+ // Uncompressed size == 0 means this block was part of a split block
+ // and it continues as the first block of the next cabinet in the set.
+ // Otherwise, this is the last part of the block, and no more block
+ // reading needs to be done.
+
+ // EXIT POINT OF LOOP -- uncompressed size != 0
+ if ((output = BitConverter.ToUInt16(hdr, cfdata_UncompressedSize)) != 0)
+ return Error.MSPACK_ERR_OK;
+
+ // Otherwise, advance to next cabinet
+
+ // Close current file handle
+ sys.Close(d.InputFileHandle);
+ d.InputFileHandle = null;
+
+ // Advance to next member in the cabinet set
+ if ((d.Data = d.Data.Next) == null)
+ {
+ sys.Message(d.InputFileHandle, "WARNING; ran out of cabinets in set. Are any missing?");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Open next cab file
+ d.InputCabinet = d.Data.Cab;
+ if ((d.InputFileHandle = sys.Open(sys, d.InputCabinet.Filename, OpenMode.MSPACK_SYS_OPEN_READ)) == null)
+ return Error.MSPACK_ERR_OPEN;
+
+ // Seek to start of data blocks
+ if (!sys.Seek(d.InputFileHandle, d.Data.Offset, SeekMode.MSPACK_SYS_SEEK_START))
+ return Error.MSPACK_ERR_SEEK;
+ } while (true);
+ }
+
+ private static uint Checksum(byte[] data, int pointer, uint bytes, uint cksum)
+ {
+ uint len, ul = 0;
+
+ for (len = bytes >> 2; len-- != 0; pointer += 4)
+ {
+ cksum ^= (uint)((data[pointer + 0]) | (data[pointer + 1] << 8) | (data[pointer + 2] << 16) | (data[pointer + 3] << 24));
+ }
+
+ switch (bytes & 3)
+ {
+ case 3:
+ ul |= (uint)(data[pointer++] << 16);
+ ul |= (uint)(data[pointer++] << 8);
+ ul |= data[pointer];
+ break;
+
+ case 2:
+ ul |= (uint)(data[pointer++] << 8);
+ ul |= data[pointer];
+ break;
+
+ case 1:
+ ul |= data[pointer];
+ break;
+ }
+
+ cksum ^= ul;
+ return cksum;
+ }
+
+ #endregion
+
+ #region NONED_INIT, NONED_DECOMPRESS, NONED_FREE
+
+ internal static NoneState NoneInit(SystemImpl sys, object input, object output, int bufsize)
+ {
+ NoneState state = new NoneState();
+ byte[] buf = sys.Alloc(sys, bufsize);
+ if (state != null && buf != null)
+ {
+ state.Sys = sys;
+ state.Input = input;
+ state.Output = output;
+ state.Buffer = buf;
+ state.BufferSize = bufsize;
+ }
+ else
+ {
+ sys.Free(buf);
+ sys.Free(state);
+ state = null;
+ }
+
+ return state;
+ }
+
+ internal static Error NoneDecompress(object s, long bytes)
+ {
+ NoneState state = (NoneState)s;
+ if (state == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ int run;
+ while (bytes > 0)
+ {
+ run = (bytes > state.BufferSize) ? state.BufferSize : (int)bytes;
+
+ if (state.Sys.Read(state.Input, state.Buffer, 0, run) != run)
+ return Error.MSPACK_ERR_READ;
+
+ if (state.Sys.Write(state.Output, state.Buffer, 0, run) != run)
+ return Error.MSPACK_ERR_WRITE;
+
+ bytes -= run;
+ }
+ return Error.MSPACK_ERR_OK;
+ }
+
+ internal static void NoneFree(object s)
+ {
+ NoneState state = (NoneState)s;
+ if (state != null)
+ {
+ SystemImpl sys = state.Sys;
+ sys.Free(state.Buffer);
+ sys.Free(state);
+ }
+ }
+
+ #endregion
+
+ #region CABD_PARAM
+
+ ///
+ /// Allows a parameter to be set
+ ///
+ public static Error Param(Decompressor d, Parameters param, int value)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ switch (param)
+ {
+ case Parameters.MSCABD_PARAM_SEARCHBUF:
+ if (value < 4)
+ return Error.MSPACK_ERR_ARGS;
+
+ self.SearchBufferSize = value;
+ break;
+
+ case Parameters.MSCABD_PARAM_FIXMSZIP:
+ self.FixMSZip = value != 0;
+ break;
+
+ case Parameters.MSCABD_PARAM_DECOMPBUF:
+ if (value < 4)
+ return Error.MSPACK_ERR_ARGS;
+
+ self.BufferSize = value;
+ break;
+
+ case Parameters.MSCABD_PARAM_SALVAGE:
+ self.Salvage = value != 0;
+ break;
+
+ default:
+ return Error.MSPACK_ERR_ARGS;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region CABD_ERROR
+
+ ///
+ /// Returns the last error that occurred
+ ///
+ public static Error LastError(Decompressor d)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ return (self != null) ? self.Error : Error.MSPACK_ERR_ARGS;
+ }
+
+ #endregion
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/InternalFile.cs b/BurnOutSharp/External/libmspack/CAB/InternalFile.cs
new file mode 100644
index 00000000..30a27387
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/InternalFile.cs
@@ -0,0 +1,90 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ ///
+ /// A structure which represents a single file in a cabinet or cabinet set.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ public class InternalFile
+ {
+ ///
+ /// The next file in the cabinet or cabinet set, or NULL if this is the final file.
+ ///
+ public InternalFile Next { get; set; }
+
+ ///
+ /// The filename of the file.
+ ///
+ /// A null terminated string of up to 255 bytes in length, it may be in
+ /// either ISO-8859-1 or UTF8 format, depending on the file attributes.
+ ///
+ ///
+ public string Filename { get; set; }
+
+ ///
+ /// The uncompressed length of the file, in bytes.
+ ///
+ public uint Length { get; set; }
+
+ ///
+ /// File attributes.
+ ///
+ public FileAttributes Attributes { get; set; }
+
+ ///
+ /// File's last modified time, hour field.
+ ///
+ public byte LastModifiedTimeHour { get; set; }
+
+ ///
+ /// File's last modified time, minute field.
+ ///
+ public byte LastModifiedTimeMinute { get; set; }
+
+ ///
+ /// File's last modified time, second field.
+ ///
+ public byte LastModifiedTimeSecond { get; set; }
+
+ ///
+ /// File's last modified date, day field.
+ ///
+ public byte LastModifiedDateDay { get; set; }
+
+ ///
+ /// File's last modified date, month field.
+ ///
+ public byte LastModifiedDateMonth { get; set; }
+
+ ///
+ /// File's last modified date, year field.
+ ///
+ public int LastModifiedDateYear { get; set; }
+
+ ///
+ /// A pointer to the folder that contains this file.
+ ///
+ public Folder Folder { get; set; }
+
+ ///
+ /// The uncompressed offset of this file in its folder.
+ ///
+ public uint Offset { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CAB/NoneState.cs b/BurnOutSharp/External/libmspack/CAB/NoneState.cs
new file mode 100644
index 00000000..64a279a5
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CAB/NoneState.cs
@@ -0,0 +1,27 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.CAB
+{
+ ///
+ /// The "not compressed" method decompressor
+ ///
+ public class NoneState
+ {
+ public SystemImpl Sys { get; set; }
+
+ public object Input { get; set; }
+
+ public object Output { get; set; }
+
+ public byte[] Buffer { get; set; }
+
+ public int BufferSize { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/CompressFile.cs b/BurnOutSharp/External/libmspack/CHM/CompressFile.cs
new file mode 100644
index 00000000..305c675e
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/CompressFile.cs
@@ -0,0 +1,53 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CHM
+{
+ ///
+ /// A structure which represents a file to be placed in a CHM helpfile.
+ ///
+ /// A contiguous array of these structures should be passed to
+ /// Compressor.Generate(). The array list is terminated with an
+ /// entry whose InternalFile.Section field is set to #MSCHMC_ENDLIST, the
+ /// other fields in this entry are ignored.
+ ///
+ public class CompressFile
+ {
+ ///
+ /// One of #MSCHMC_ENDLIST, #MSCHMC_UNCOMP or #MSCHMC_MSCOMP.
+ ///
+ public SectionType Section { get; set; }
+
+ ///
+ /// The filename of the source file that will be added to the CHM. This
+ /// is passed directly to mspack_system::open()
+ ///
+ public string Filename { get; set; }
+
+ ///
+ /// The full path and filename of the file within the CHM helpfile, a
+ /// UTF-1 encoded null-terminated string.
+ ///
+ public string CHMFilename { get; set; }
+
+ ///
+ /// The length of the file, in bytes. This will be adhered to strictly
+ /// and a read error will be issued if this many bytes cannot be read
+ /// from the real file at CHM generation time.
+ ///
+ public long Length { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/Compressor.cs b/BurnOutSharp/External/libmspack/CHM/Compressor.cs
new file mode 100644
index 00000000..05560832
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/Compressor.cs
@@ -0,0 +1,157 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+
+namespace LibMSPackSharp.CHM
+{
+ ///
+ /// A compressor for .CHM (Microsoft HTMLHelp) files.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ ///
+ ///
+ public class Compressor
+ {
+ ///
+ /// Generates a CHM help file.
+ ///
+ /// The help file will contain up to two sections, an Uncompressed
+ /// section and potentially an MSCompressed (LZX compressed)
+ /// section.
+ ///
+ /// While the contents listing of a CHM file is always in lexical order,
+ /// the file list passed in will be taken as the correct order for files
+ /// within the sections. It is in your interest to place similar files
+ /// together for better compression.
+ ///
+ /// There are two modes of generation, to use a temporary file or not to
+ /// use one. See use_temporary_file() for the behaviour of generate() in
+ /// these two different modes.
+ ///
+ ///
+ /// a self-referential pointer to the mschm_compressor
+ /// instance being called
+ ///
+ ///
+ /// an array of mschmc_file structures, terminated
+ /// with an entry whose mschmc_file::section field is
+ /// #MSCHMC_ENDLIST. The order of the list is
+ /// preserved within each section. The length of any
+ /// mschmc_file::chm_filename string cannot exceed
+ /// roughly 4096 bytes. Each source file must be able
+ /// to supply as many bytes as given in the
+ /// mschmc_file::length field.
+ ///
+ ///
+ /// the file to write the generated CHM helpfile to.
+ /// This is passed directly to mspack_system::open()
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ ///
+ ///
+ public Func Generate;
+
+ ///
+ /// Specifies whether a temporary file is used during CHM generation.
+ ///
+ /// The CHM file format includes data about the compressed section (such
+ /// as its overall size) that is stored in the output CHM file prior to
+ /// the compressed section itself. This unavoidably requires that the
+ /// compressed section has to be generated, before these details can be
+ /// set. There are several ways this can be handled. Firstly, the
+ /// compressed section could be generated entirely in memory before
+ /// writing any of the output CHM file.This approach is not used in
+ /// libmspack, as the compressed section can exceed the addressable
+ /// memory space on most architectures.
+ ///
+ /// libmspack has two options, either to write these unknowable sections
+ /// with blank data, generate the compressed section, then re-open the
+ /// output file for update once the compressed section has been
+ /// completed, or to write the compressed section to a temporary file,
+ /// then write the entire output file at once, performing a simple
+ /// file-to-file copy for the compressed section.
+ ///
+ /// The simple solution of buffering the entire compressed section in
+ /// memory can still be used, if desired.As the temporary file's
+ /// filename is passed directly to mspack_system::open(), it is possible
+ /// for a custom mspack_system implementation to hold this file in memory,
+ /// without writing to a disk.
+ ///
+ /// If a temporary file is set, generate() performs the following
+ /// sequence of events: the temporary file is opened for writing, the
+ /// compression algorithm writes to the temporary file, the temporary
+ /// file is closed.Then the output file is opened for writing and the
+ /// temporary file is re-opened for reading.The output file is written
+ /// and the temporary file is read from. Both files are then closed.The
+ /// temporary file itself is not deleted. If that is desired, the
+ /// temporary file should be deleted after the completion of generate(),
+ /// if it exists.
+ ///
+ /// If a temporary file is set not to be used, generate() performs the
+ /// following sequence of events: the output file is opened for writing,
+ /// then it is written and closed.The output file is then re-opened for
+ /// update, the appropriate sections are seek() ed to and re-written, then
+ /// the output file is closed.
+ ///
+ ///
+ /// a self-referential pointer to the mschm_compressor
+ /// instance being called
+ ///
+ ///
+ /// non-zero if the temporary file should be used,
+ /// zero if the temporary file should not be used.
+ ///
+ ///
+ /// a file to temporarily write compressed data to,
+ /// before opening it for reading and copying the
+ /// contents to the output file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ ///
+ public Func UseTemporaryFile;
+
+ ///
+ /// Sets a CHM compression engine parameter.
+ ///
+ ///
+ /// a self-referential pointer to the mschm_compressor
+ /// instance being called
+ ///
+ /// the parameter to set
+ /// the value to set the parameter to
+ ///
+ /// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there
+ /// is a problem with either parameter or value.
+ ///
+ ///
+ public Func SetParam;
+
+ ///
+ /// Returns the error code set by the most recently called method.
+ ///
+ ///
+ /// a self-referential pointer to the mschm_compressor
+ /// instance being called
+ ///
+ /// the most recent error code
+ ///
+ ///
+ public Func LastError;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/CompressorImpl.cs b/BurnOutSharp/External/libmspack/CHM/CompressorImpl.cs
new file mode 100644
index 00000000..9c27324a
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/CompressorImpl.cs
@@ -0,0 +1,22 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.CHM
+{
+ public class CompressorImpl : Compressor
+ {
+ public SystemImpl System { get; set; }
+
+ public string TempFile { get; set; }
+
+ public bool UseTempFile { get; set; }
+
+ public Error Error { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/DecompressFile.cs b/BurnOutSharp/External/libmspack/CHM/DecompressFile.cs
new file mode 100644
index 00000000..edf603f2
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/DecompressFile.cs
@@ -0,0 +1,52 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CHM
+{
+ ///
+ /// A structure which represents a file stored in a CHM helpfile.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ public class DecompressFile
+ {
+ ///
+ /// A pointer to the next file in the list, or NULL if this is the final file.
+ ///
+ public DecompressFile Next { get; set; }
+
+ ///
+ /// A pointer to the section that this file is located in. Indirectly,
+ /// it also points to the CHM helpfile the file is located in.
+ ///
+ public Section Section { get; set; }
+
+ ///
+ /// The offset within the section data that this file is located at.
+ ///
+ public long Offset { get; set; }
+
+ ///
+ /// The length of this file, in bytes
+ ///
+ public long Length { get; set; }
+
+ ///
+ /// The filename of this file -- a null terminated string in UTF-8.
+ ///
+ public string Filename { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/DecompressState.cs b/BurnOutSharp/External/libmspack/CHM/DecompressState.cs
new file mode 100644
index 00000000..24786506
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/DecompressState.cs
@@ -0,0 +1,51 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using LibMSPackSharp.Compression;
+
+namespace LibMSPackSharp.CHM
+{
+ public class DecompressState
+ {
+ ///
+ /// CHM file being decompressed
+ ///
+ public Header Header { get; set; }
+
+ ///
+ /// Uncompressed offset within folder
+ ///
+ public long Offset { get; set; }
+
+ ///
+ /// Offset in input file
+ ///
+ public long InOffset { get; set; }
+
+ ///
+ /// LZX decompressor state
+ ///
+ public LZXDStream State { get; set; }
+
+ ///
+ /// Special I/O code for decompressor
+ ///
+ public SystemImpl Sys { get; set; }
+
+ ///
+ /// Input file handle
+ ///
+ public object InputFileHandle { get; set; }
+
+ ///
+ /// Output file handle
+ ///
+ public object OutputFileHandle { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/Decompressor.cs b/BurnOutSharp/External/libmspack/CHM/Decompressor.cs
new file mode 100644
index 00000000..6ddb1ea3
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/Decompressor.cs
@@ -0,0 +1,185 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+
+namespace LibMSPackSharp.CHM
+{
+ ///
+ /// A decompressor for .CHM (Microsoft HTMLHelp) files
+ ///
+ /// All fields are READ ONLY.
+ ///
+ ///
+ ///
+ public class Decompressor
+ {
+ ///
+ /// Opens a CHM helpfile and reads its contents.
+ ///
+ /// If the file opened is a valid CHM helpfile, all headers will be read
+ /// and a mschmd_header structure will be returned, with a full list of
+ /// files.
+ ///
+ /// In the case of an error occuring, NULL is returned and the error code
+ /// is available from last_error().
+ ///
+ /// The filename pointer should be considered "in use" until close() is
+ /// called on the CHM helpfile.
+ ///
+ ///
+ /// a self-referential pointer to the mschm_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the CHM helpfile. This is passed
+ /// directly to mspack_system::open().
+ ///
+ /// a pointer to a mschmd_header structure, or NULL on failure
+ ///
+ public Func Open;
+
+ ///
+ /// Closes a previously opened CHM helpfile.
+ ///
+ /// This closes a CHM helpfile, frees the mschmd_header and all
+ /// mschmd_file structures associated with it (if any). This works on
+ /// both helpfiles opened with open() and helpfiles opened with
+ /// fast_open().
+ ///
+ /// The CHM header pointer is now invalid and cannot be used again. All
+ /// mschmd_file pointers referencing that CHM are also now invalid, and
+ /// cannot be used again.
+ ///
+ ///
+ /// a self-referential pointer to the mschm_decompressor
+ /// instance being called
+ ///
+ /// the CHM helpfile to close
+ ///
+ ///
+ public Action Close;
+
+ ///
+ /// Extracts a file from a CHM helpfile.
+ ///
+ /// This extracts a file from a CHM helpfile and writes it to the given
+ /// filename.The filename of the file, mscabd_file::filename, is not
+ /// used by extract(), but can be used by the caller as a guide for
+ /// constructing an appropriate filename.
+ ///
+ /// This method works both with files found in the mschmd_header::files
+ /// and mschmd_header::sysfiles list and mschmd_file structures generated
+ /// on the fly by fast_find().
+ ///
+ ///
+ /// a self-referential pointer to the mschm_decompressor
+ /// instance being called
+ ///
+ /// the file to be decompressed
+ /// the filename of the file being written to
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func Extract;
+
+ ///
+ /// Returns the error code set by the most recently called method.
+ ///
+ /// This is useful for open() and fast_open(), which do not return an
+ /// error code directly.
+ ///
+ ///
+ /// a self-referential pointer to the mschm_decompressor
+ /// instance being called
+ ///
+ /// the most recent error code
+ ///
+ ///
+ public Func LastError;
+
+ ///
+ /// Opens a CHM helpfile quickly.
+ ///
+ /// If the file opened is a valid CHM helpfile, only essential headers
+ /// will be read.A mschmd_header structure will be still be returned, as
+ /// with open(), but the mschmd_header::files field will be NULL.No
+ /// files details will be automatically read.The fast_find() method
+ /// must be used to obtain file details.
+ ///
+ /// In the case of an error occuring, NULL is returned and the error code
+ /// is available from last_error().
+ ///
+ /// The filename pointer should be considered "in use" until close() is
+ /// called on the CHM helpfile.
+ ///
+ ///
+ /// a self-referential pointer to the mschm_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the CHM helpfile. This is passed
+ /// directly to mspack_system::open().
+ ///
+ /// a pointer to a mschmd_header structure, or NULL on failure
+ ///
+ ///
+ ///
+ ///
+ public Func FastOpen;
+
+ ///
+ /// Finds file details quickly.
+ ///
+ /// Instead of reading all CHM helpfile headers and building a list of
+ /// files, fast_open() and fast_find() are intended for finding file
+ /// details only when they are needed.The CHM file format includes an
+ /// on-disk file index to allow this.
+ ///
+ /// Given a case-sensitive filename, fast_find() will search the on-disk
+ /// index for that file.
+ ///
+ /// If the file was found, the caller-provided mschmd_file structure will
+ /// be filled out like so:
+ /// - section: the correct value for the found file
+ /// - offset: the correct value for the found file
+ /// - length: the correct value for the found file
+ /// - all other structure elements: NULL or 0
+ ///
+ /// If the file was not found, MSPACK_ERR_OK will still be returned as the
+ /// result, but the caller-provided structure will be filled out like so:
+ /// - section: NULL
+ /// - offset: 0
+ /// - length: 0
+ /// - all other structure elements: NULL or 0
+ ///
+ /// This method is intended to be used in conjunction with CHM helpfiles
+ /// opened with fast_open(), but it also works with helpfiles opened
+ /// using the regular open().
+ ///
+ ///
+ /// a self-referential pointer to the mschm_decompressor
+ /// instance being called
+ ///
+ /// the CHM helpfile to search for the file
+ /// the filename of the file to search for
+ /// a pointer to a caller-provded mschmd_file structure
+ /// an error code, or MSPACK_ERR_OK if successful
+ ///
+ ///
+ ///
+ ///
+ public Func FastFind;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/DecompressorImpl.cs b/BurnOutSharp/External/libmspack/CHM/DecompressorImpl.cs
new file mode 100644
index 00000000..f9805ee9
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/DecompressorImpl.cs
@@ -0,0 +1,20 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.CHM
+{
+ public class DecompressorImpl : Decompressor
+ {
+ public SystemImpl System { get; set; }
+
+ public DecompressState State { get; set; }
+
+ public Error Error { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/Enums.cs b/BurnOutSharp/External/libmspack/CHM/Enums.cs
new file mode 100644
index 00000000..a0f400f0
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/Enums.cs
@@ -0,0 +1,83 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.CHM
+{
+ public enum Parameters
+ {
+ ///
+ /// Sets the "timestamp" of the CHM file
+ /// generated. This is not a timestamp, see mschmd_header::timestamp
+ /// for a description. If this timestamp is 0, generate() will use its
+ /// own algorithm for making a unique ID, based on the lengths and
+ /// names of files in the CHM itself. Defaults to 0, any value between
+ /// 0 and (2^32)-1 is valid.
+ ///
+ MSCHMC_PARAM_TIMESTAMP = 0,
+
+ ///
+ /// Sets the "language" of the CHM file
+ /// generated. This is not the language used in the CHM file, but the
+ /// language setting of the user who ran the HTMLHelp compiler. It
+ /// defaults to 0x0409. The valid range is between 0x0000 and 0x7F7F.
+ ///
+ MSCHMC_PARAM_LANGUAGE = 1,
+
+ ///
+ /// Sets the size of the LZX history window,
+ /// which is also the interval at which the compressed data stream can be
+ /// randomly accessed. The value is not a size in bytes, but a power of
+ /// two. The default value is 16 (which makes the window 2^16 bytes, or
+ /// 64 kilobytes), the valid range is from 15 (32 kilobytes) to 21 (2
+ /// megabytes).
+ ///
+ MSCHMC_PARAM_LZXWINDOW = 2,
+
+ ///
+ /// Sets the "density" of quick reference
+ /// entries stored at the end of directory listing chunk. Each chunk is
+ /// 4096 bytes in size, and contains as many file entries as there is
+ /// room for. At the other end of the chunk, a list of "quick reference"
+ /// pointers is included. The offset of every 'N'th file entry is given a
+ /// quick reference, where N = (2 ^ density) + 1.The default density is
+ /// 2. The smallest density is 0 (N = 2), the maximum is 10 (N = 1025). As
+ /// each file entry requires at least 5 bytes, the maximum number of
+ /// entries in a single chunk is roughly 800, so the maximum value 10
+ /// can be used to indicate there are no quickrefs at all.
+ ///
+ MSCHMC_PARAM_DENSITY = 3,
+
+ ///
+ /// Sets whether or not to include quick lookup
+ /// index chunk(s), in addition to normal directory listing chunks. A
+ /// value of zero means no index chunks will be created, a non-zero value
+ /// means index chunks will be created. The default is zero, "don't
+ /// create an index".
+ ///
+ MSCHMC_PARAM_INDEX = 4,
+ }
+
+ public enum SectionType
+ {
+ ///
+ /// end of CHM file list
+ ///
+ MSCHMC_ENDLIST = 0,
+
+ ///
+ /// this file is in the Uncompressed section
+ ///
+ MSCHMC_UNCOMP = 1,
+
+ ///
+ /// this file is in the MSCompressed section
+ ///
+ MSCHMC_MSCOMP = 2,
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/Header.cs b/BurnOutSharp/External/libmspack/CHM/Header.cs
new file mode 100644
index 00000000..ab8ac3b8
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/Header.cs
@@ -0,0 +1,135 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CHM
+{
+ ///
+ /// A structure which represents a CHM helpfile.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ public class Header
+ {
+ ///
+ /// The version of the CHM file format used in this file.
+ ///
+ public uint Version { get; set; }
+
+ ///
+ /// The "timestamp" of the CHM helpfile.
+ ///
+ /// It is the lower 32 bits of a 64-bit value representing the number of
+ /// centiseconds since 1601-01-01 00:00:00 UTC, plus 42. It is not useful
+ /// as a timestamp, but it is useful as a semi-unique ID.
+ ///
+ public uint Timestamp { get; set; }
+
+ ///
+ /// The default Language and Country ID (LCID) of the user who ran the
+ /// HTMLHelp Compiler. This is not the language of the CHM file itself.
+ ///
+ public uint Language { get; set; }
+
+ ///
+ /// The filename of the CHM helpfile. This is given by the library user
+ /// and may be in any format.
+ ///
+ public string Filename { get; set; }
+
+ ///
+ /// The length of the CHM helpfile, in bytes.
+ ///
+ public long Length { get; set; }
+
+ ///
+ /// A list of all non-system files in the CHM helpfile.
+ ///
+ public DecompressFile Files { get; set; }
+
+ ///
+ /// A list of all system files in the CHM helpfile.
+ ///
+ /// System files are files which begin with "::". They are meta-files
+ /// generated by the CHM creation process.
+ ///
+ public DecompressFile SysFiles { get; set; }
+
+ ///
+ /// The section 0 (uncompressed) data in this CHM helpfile.
+ ///
+ public UncompressedSection Sec0 { get; set; }
+
+ ///
+ /// The section 1 (MSCompressed) data in this CHM helpfile.
+ ///
+ public MSCompressedSection Sec1 { get; set; }
+
+ ///
+ /// The file offset of the first PMGL/PMGI directory chunk.
+ ///
+ public long DirOffset { get; set; }
+
+ ///
+ /// The number of PMGL/PMGI directory chunks in this CHM helpfile.
+ ///
+ public uint NumChunks { get; set; }
+
+ ///
+ /// The size of each PMGL/PMGI chunk, in bytes.
+ ///
+ public uint ChunkSize { get; set; }
+
+ ///
+ /// The "density" of the quick-reference section in PMGL/PMGI chunks.
+ ///
+ public uint Density { get; set; }
+
+ ///
+ /// The depth of the index tree.
+ ///
+ /// - if 1, there are no PMGI chunks, only PMGL chunks.
+ /// - if 2, there is 1 PMGI chunk. All chunk indices point to PMGL chunks.
+ /// - if 3, the root PMGI chunk points to secondary PMGI chunks, which in turn point to PMGL chunks.
+ /// - and so on...
+ ///
+ public uint Depth { get; set; }
+
+ ///
+ /// The number of the root PMGI chunk.
+ ///
+ /// If there is no index in the CHM helpfile, this will be 0xFFFFFFFF.
+ ///
+ public uint IndexRoot { get; set; }
+
+ ///
+ /// The number of the first PMGL chunk. Usually zero.
+ /// Available only in CHM decoder version 2 and above.
+ ///
+ public uint FirstPMGL { get; set; }
+
+ ///
+ /// The number of the last PMGL chunk. Usually num_chunks-1.
+ /// Available only in CHM decoder version 2 and above.
+ ///
+ public uint LastPMGL { get; set; }
+
+ ///
+ /// A cache of loaded chunks, filled in by mschm_decoder::fast_find().
+ /// Available only in CHM decoder version 2 and above.
+ ///
+ public byte[][] ChunkCache { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/Implementation.cs b/BurnOutSharp/External/libmspack/CHM/Implementation.cs
new file mode 100644
index 00000000..3f5f990e
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/Implementation.cs
@@ -0,0 +1,1540 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+using System.Linq;
+using System.Text;
+using LibMSPackSharp.Compression;
+
+namespace LibMSPackSharp.CHM
+{
+ public class Implementation
+ {
+ #region Generic CHM Definitions
+
+ #region Structure Offsets
+
+ private const int chmhead_Signature = 0x0000;
+ private const int chmhead_Version = 0x0004;
+ private const int chmhead_HeaderLen = 0x0008;
+ private const int chmhead_Unknown1 = 0x000C;
+ private const int chmhead_Timestamp = 0x0010;
+ private const int chmhead_LanguageID = 0x0014;
+ private const int chmhead_GUID1 = 0x0018;
+ private const int chmhead_GUID2 = 0x0028;
+ private const int chmhead_SIZEOF = 0x0038;
+
+ private const int chmhst_OffsetHS0 = 0x0000;
+ private const int chmhst_LengthHS0 = 0x0008;
+ private const int chmhst_OffsetHS1 = 0x0010;
+ private const int chmhst_LengthHS1 = 0x0018;
+ private const int chmhst_SIZEOF = 0x0020;
+ private const int chmhst3_OffsetCS0 = 0x0020;
+ private const int chmhst3_SIZEOF = 0x0028;
+
+ private const int chmhs0_Unknown1 = 0x0000;
+ private const int chmhs0_Unknown2 = 0x0004;
+ private const int chmhs0_FileLen = 0x0008;
+ private const int chmhs0_Unknown3 = 0x0010;
+ private const int chmhs0_Unknown4 = 0x0014;
+ private const int chmhs0_SIZEOF = 0x0018;
+
+ private const int chmhs1_Signature = 0x0000;
+ private const int chmhs1_Version = 0x0004;
+ private const int chmhs1_HeaderLen = 0x0008;
+ private const int chmhs1_Unknown1 = 0x000C;
+ private const int chmhs1_ChunkSize = 0x0010;
+ private const int chmhs1_Density = 0x0014;
+ private const int chmhs1_Depth = 0x0018;
+ private const int chmhs1_IndexRoot = 0x001C;
+ private const int chmhs1_FirstPMGL = 0x0020;
+ private const int chmhs1_LastPMGL = 0x0024;
+ private const int chmhs1_Unknown2 = 0x0028;
+ private const int chmhs1_NumChunks = 0x002C;
+ private const int chmhs1_LanguageID = 0x0030;
+ private const int chmhs1_GUID = 0x0034;
+ private const int chmhs1_Unknown3 = 0x0044;
+ private const int chmhs1_Unknown4 = 0x0048;
+ private const int chmhs1_Unknown5 = 0x004C;
+ private const int chmhs1_Unknown6 = 0x0050;
+ private const int chmhs1_SIZEOF = 0x0054;
+
+ private const int pmgl_Signature = 0x0000;
+ private const int pmgl_QuickRefSize = 0x0004;
+ private const int pmgl_Unknown1 = 0x0008;
+ private const int pmgl_PrevChunk = 0x000C;
+ private const int pmgl_NextChunk = 0x0010;
+ private const int pmgl_Entries = 0x0014;
+ private const int pmgl_headerSIZEOF = 0x0014;
+
+ private const int pmgi_Signature = 0x0000;
+ private const int pmgi_QuickRefSize = 0x0004;
+ private const int pmgi_Entries = 0x0008;
+ private const int pmgi_headerSIZEOF = 0x000C;
+
+ private const int lzxcd_Length = 0x0000;
+ private const int lzxcd_Signature = 0x0004;
+ private const int lzxcd_Version = 0x0008;
+ private const int lzxcd_ResetInterval = 0x000C;
+ private const int lzxcd_WindowSize = 0x0010;
+ private const int lzxcd_CacheSize = 0x0014;
+ private const int lzxcd_Unknown1 = 0x0018;
+ private const int lzxcd_SIZEOF = 0x001C;
+
+ private const int lzxrt_Unknown1 = 0x0000;
+ private const int lzxrt_NumEntries = 0x0004;
+ private const int lzxrt_EntrySize = 0x0008;
+ private const int lzxrt_TableOffset = 0x000C;
+ private const int lzxrt_UncompLen = 0x0010;
+ private const int lzxrt_CompLen = 0x0018;
+ private const int lzxrt_FrameLen = 0x0020;
+ private const int lzxrt_Entries = 0x0028;
+ private const int lzxrt_headerSIZEOF = 0x0028;
+
+ #endregion
+
+ // filenames of the system files used for decompression.
+ // Content and ControlData are essential.
+ // ResetTable is preferred, but SpanInfo can be used if not available
+ public const string ContentName = "::DataSpace/Storage/MSCompressed/Content";
+ public const string ControlName = "::DataSpace/Storage/MSCompressed/ControlData";
+ public const string SpanInfoName = "::DataSpace/Storage/MSCompressed/SpanInfo";
+ public const string ResetTableName = "::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable";
+
+ #endregion
+
+ #region CHMD_OPEN
+
+ ///
+ /// Opens a file and tries to read it as a CHM file.
+ /// Calls RealOpen() with entire=1.
+ ///
+ public static Header Open(Decompressor decompressor, string filename)
+ {
+ return RealOpen(decompressor, filename, true);
+ }
+
+ #endregion
+
+ #region CHMD_FAST_OPEN
+
+ ///
+ /// Opens a file and tries to read it as a CHM file, but does not read
+ /// the file headers. Calls chmd_real_open() with entire=0
+ ///
+ public static Header FastOpen(Decompressor decompressor, string filename)
+ {
+ return RealOpen(decompressor, filename, false);
+ }
+
+ #endregion
+
+ #region CHMD_REAL_OPEN
+
+ ///
+ /// The real implementation of chmd_open() and chmd_fast_open(). It simply
+ /// passes the "entire" parameter to chmd_read_headers(), which will then
+ /// either read all headers, or a bare mininum.
+ ///
+ private static Header RealOpen(Decompressor d, string filename, bool entire)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ Header chm = null;
+
+ if (d == null)
+ return null;
+
+ SystemImpl sys = self.System;
+
+ object fh;
+ if ((fh = sys.Open(sys, filename, OpenMode.MSPACK_SYS_OPEN_READ)) != null)
+ {
+ chm = new Header();
+ chm.Filename = filename;
+ Error error = ReadHeaders(sys, fh, chm, entire);
+ if (error != Error.MSPACK_ERR_OK)
+ {
+ // If the error is DATAFORMAT, and there are some results, return
+ // partial results with a warning, rather than nothing
+ if (error == Error.MSPACK_ERR_DATAFORMAT && (chm.Files != null || chm.SysFiles != null))
+ {
+ sys.Message(fh, "WARNING; contents are corrupt");
+ error = Error.MSPACK_ERR_OK;
+ }
+ else
+ {
+ Close(d, chm);
+ chm = null;
+ }
+ }
+
+ self.Error = error;
+ sys.Close(fh);
+ }
+ else
+ {
+ self.Error = Error.MSPACK_ERR_OPEN;
+ }
+
+ return chm;
+ }
+
+ #endregion
+
+ #region CHMD_CLOSE
+
+ ///
+ /// Frees all memory associated with a given mschmd_header
+ ///
+ public static void Close(Decompressor d, Header chm)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ DecompressFile fi, nfi;
+ uint i;
+
+ if (d == null)
+ return;
+
+ SystemImpl sys = self.System;
+
+ self.Error = Error.MSPACK_ERR_OK;
+
+ // Free files
+ for (fi = chm.Files; fi != null; fi = nfi)
+ {
+ nfi = fi.Next;
+ sys.Free(fi);
+ }
+ for (fi = chm.SysFiles; fi != null; fi = nfi)
+ {
+ nfi = fi.Next;
+ sys.Free(fi);
+ }
+
+ // If this CHM was being decompressed, free decompression state
+ if (self.State != null && (self.State.Header == chm))
+ {
+ if (self.State.InputFileHandle != null)
+ sys.Close(self.State.InputFileHandle);
+
+ if (self.State.State != null)
+ LZX.Free(self.State.State);
+
+ sys.Free(self.State);
+ self.State = null;
+ }
+
+ // If this CHM had a chunk cache, free it and contents
+ if (chm.ChunkCache != null)
+ {
+ for (i = 0; i < chm.NumChunks; i++)
+ {
+ sys.Free(chm.ChunkCache[i]);
+ }
+
+ sys.Free(chm.ChunkCache);
+ }
+
+ sys.Free(chm);
+ }
+
+ #endregion
+
+ #region CHMD_READ_HEADERS
+
+ ///
+ /// The GUIDs found in CHM headers
+ ///
+ private static readonly byte[] guids =
+ {
+ /* {7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC} */
+ 0x10, 0xFD, 0x01, 0x7C, 0xAA, 0x7B, 0xD0, 0x11,
+ 0x9E, 0x0C, 0x00, 0xA0, 0xC9, 0x22, 0xE6, 0xEC,
+
+ /* {7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC} */
+ 0x11, 0xFD, 0x01, 0x7C, 0xAA, 0x7B, 0xD0, 0x11,
+ 0x9E, 0x0C, 0x00, 0xA0, 0xC9, 0x22, 0xE6, 0xEC
+ };
+
+ ///
+ /// Reads the basic CHM file headers. If the "entire" parameter is
+ /// non-zero, all file entries will also be read. fills out a pre-existing
+ /// mschmd_header structure, allocates memory for files as necessary
+ ///
+ public static Error ReadHeaders(SystemImpl sys, object fh, Header chm, bool entire)
+ {
+ uint section, nameLen, x, errors, numChunks;
+ byte[] buf = new byte[0x54], chunk = null;
+ int name, p, end;
+ DecompressFile fi, link = null;
+ long offset, length;
+ int numEntries;
+
+ // Initialise pointers
+ chm.Files = null;
+ chm.SysFiles = null;
+ chm.ChunkCache = null;
+
+ chm.Sec0.Header = chm;
+ chm.Sec0.ID = 0;
+
+ chm.Sec1.Header = chm;
+ chm.Sec1.ID = 1;
+ chm.Sec1.Content = null;
+ chm.Sec1.Control = null;
+ chm.Sec1.SpanInfo = null;
+ chm.Sec1.ResetTable = null;
+
+ // Read the first header
+ if (sys.Read(fh, buf, 0, chmhead_SIZEOF) != chmhead_SIZEOF)
+ return Error.MSPACK_ERR_READ;
+
+ // Check ITSF signature
+ if (BitConverter.ToUInt32(buf, chmhead_Signature) != 0x46535449)
+ return Error.MSPACK_ERR_SIGNATURE;
+
+ // Check both header GUIDs
+ if (!buf.Skip(chmhead_GUID1).Take(32).SequenceEqual(guids))
+ {
+ Console.WriteLine("incorrect GUIDs");
+ return Error.MSPACK_ERR_SIGNATURE;
+ }
+
+ chm.Version = BitConverter.ToUInt32(buf, chmhead_Version);
+ chm.Timestamp = BitConverter.ToUInt32(buf, chmhead_Timestamp);
+ chm.Language = BitConverter.ToUInt32(buf, chmhead_LanguageID);
+ if (chm.Version > 3)
+ sys.Message(fh, "WARNING; CHM version > 3");
+
+ // Read the header section table
+ if (sys.Read(fh, buf, 0, chmhst3_SIZEOF) != chmhst3_SIZEOF)
+ return Error.MSPACK_ERR_READ;
+
+ // chmhst3_OffsetCS0 does not exist in version 1 or 2 CHM files.
+ // The offset will be corrected later, once HS1 is read.
+ if ((offset = BitConverter.ToInt64(buf, chmhst_OffsetHS0)) != 0
+ || (chm.DirOffset = BitConverter.ToInt64(buf, chmhst_OffsetHS1)) != 0
+ || (chm.Sec0.Offset = BitConverter.ToInt64(buf, chmhst3_OffsetCS0)) != 0)
+ {
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Seek to header section 0
+ if (!sys.Seek(fh, offset, SeekMode.MSPACK_SYS_SEEK_START))
+ return Error.MSPACK_ERR_SEEK;
+
+ // Read header section 0
+ if (sys.Read(fh, buf, 0, chmhs0_SIZEOF) != chmhs0_SIZEOF)
+ return Error.MSPACK_ERR_READ;
+
+ if ((chm.Length = BitConverter.ToInt64(buf, chmhs0_FileLen)) != 0)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ // Seek to header section 1
+ if (!sys.Seek(fh, chm.DirOffset, SeekMode.MSPACK_SYS_SEEK_START))
+ return Error.MSPACK_ERR_SEEK;
+
+ // Read header section 1
+ if (sys.Read(fh, buf, 0, chmhs1_SIZEOF) != chmhs1_SIZEOF)
+ return Error.MSPACK_ERR_READ;
+
+ chm.DirOffset = sys.Tell(fh);
+ chm.ChunkSize = BitConverter.ToUInt32(buf, chmhs1_ChunkSize);
+ chm.Density = BitConverter.ToUInt32(buf, chmhs1_Density);
+ chm.Depth = BitConverter.ToUInt32(buf, chmhs1_Depth);
+ chm.IndexRoot = BitConverter.ToUInt32(buf, chmhs1_IndexRoot);
+ chm.NumChunks = BitConverter.ToUInt32(buf, chmhs1_NumChunks);
+ chm.FirstPMGL = BitConverter.ToUInt32(buf, chmhs1_FirstPMGL);
+ chm.LastPMGL = BitConverter.ToUInt32(buf, chmhs1_LastPMGL);
+
+ if (chm.Version < 3)
+ {
+ // Versions before 3 don't have chmhst3_OffsetCS0
+ chm.Sec0.Offset = chm.DirOffset + (chm.ChunkSize * chm.NumChunks);
+ }
+
+ // Check if content offset or file size is wrong
+ if (chm.Sec0.Offset > chm.Length)
+ {
+ Console.WriteLine("content section begins after file has ended");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Ensure there are chunks and that chunk size is
+ // large enough for signature and num_entries
+ if (chm.ChunkSize < (pmgl_Entries + 2))
+ {
+ Console.WriteLine("chunk size not large enough");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ if (chm.NumChunks == 0)
+ {
+ Console.WriteLine("no chunks");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // The ChunkCache data structure is not great; large values for NumChunks
+ // or NumChunks*ChunkSize can exhaust all memory. Until a better chunk
+ // cache is implemented, put arbitrary limits on NumChunks and chunk size.
+ if (chm.NumChunks > 100000)
+ {
+ Console.WriteLine("more than 100,000 chunks");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+ if (chm.ChunkSize > 8192)
+ {
+ Console.WriteLine("chunk size over 8192 (get in touch if this is valid)");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+ if (chm.ChunkSize * (long)chm.NumChunks > chm.Length)
+ {
+ Console.WriteLine("chunks larger than entire file");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Common sense checks on header section 1 fields
+ if (chm.ChunkSize != 4096)
+ sys.Message(fh, "WARNING; chunk size is not 4096");
+
+ if (chm.FirstPMGL != 0)
+ sys.Message(fh, "WARNING; first PMGL chunk is not zero");
+
+ if (chm.FirstPMGL > chm.LastPMGL)
+ {
+ Console.WriteLine("first pmgl chunk is after last pmgl chunk");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ if (chm.IndexRoot != 0xFFFFFFFF && chm.IndexRoot >= chm.NumChunks)
+ {
+ Console.WriteLine("IndexRoot outside valid range");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // If we are doing a quick read, stop here!
+ if (!entire)
+ return Error.MSPACK_ERR_OK;
+
+ // Seek to the first PMGL chunk, and reduce the number of chunks to read
+ if ((x = chm.FirstPMGL) != 0)
+ {
+ if (!sys.Seek(fh, x * chm.ChunkSize, SeekMode.MSPACK_SYS_SEEK_CUR))
+ return Error.MSPACK_ERR_SEEK;
+ }
+
+ numChunks = chm.LastPMGL - x + 1;
+
+ if ((chunk = sys.Alloc(sys, (int)chm.ChunkSize)) == null)
+ return Error.MSPACK_ERR_NOMEMORY;
+
+ // Read and process all chunks from FirstPMGL to LastPMGL
+ errors = 0;
+ while (numChunks-- != 0)
+ {
+ // Read next chunk
+ if (sys.Read(fh, chunk, 0, (int)chm.ChunkSize) != (int)chm.ChunkSize)
+ {
+ sys.Free(chunk);
+ return Error.MSPACK_ERR_READ;
+ }
+
+ // Process only directory (PMGL) chunks
+ if (BitConverter.ToUInt32(chunk, pmgl_Signature) != 0x4C474D50)
+ continue;
+
+ if (BitConverter.ToUInt32(chunk, pmgl_QuickRefSize) < 2)
+ sys.Message(fh, "WARNING; PMGL quickref area is too small");
+
+ if (BitConverter.ToUInt32(chunk, pmgl_QuickRefSize) > chm.ChunkSize - pmgl_Entries)
+ sys.Message(fh, "WARNING; PMGL quickref area is too large");
+
+ p = pmgl_Entries;
+ end = (int)(chm.ChunkSize - 2);
+ numEntries = BitConverter.ToUInt16(chunk, end);
+
+ while (numEntries-- != 0)
+ {
+ // READ_ENCINT(nameLen)
+ nameLen = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ nameLen = (uint)((nameLen << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+
+ if (nameLen > (uint)(end - p))
+ goto chunk_end;
+
+ name = p; p += (int)nameLen;
+
+ // READ_ENCINT(section)
+ section = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ section = (uint)((section << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+
+ // READ_ENCINT(offset)
+ offset = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ offset = (offset << 7) | (chunk[p] & 0x7F);
+ } while ((chunk[p++] & 0x80) != 0);
+
+ // READ_ENCINT(length)
+ length = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ length = (length << 7) | (chunk[p] & 0x7F);
+ } while ((chunk[p++] & 0x80) != 0);
+
+ // Ignore blank or one-char (e.g. "/") filenames we'd return as blank
+ if (nameLen < 2 || chunk[name + 0] == 0x00 || chunk[name + 1] == 0x00)
+ continue;
+
+ // Empty files and directory names are stored as a file entry at
+ // offset 0 with length 0. We want to keep empty files, but not
+ // directory names, which end with a "/"
+ if ((offset == 0) && (length == 0))
+ {
+ if ((nameLen > 0) && (chunk[name + nameLen - 1] == '/'))
+ continue;
+ }
+
+ if (section > 1)
+ {
+ sys.Message(fh, $"invalid section number '{section}'.");
+ continue;
+ }
+
+ fi = new DecompressFile();
+
+ fi.Next = null;
+ fi.Filename = Encoding.UTF8.GetString(chunk, name, (int)nameLen) + "\0";
+ fi.Section = (section == 0) ? (Section)chm.Sec0 : (Section)chm.Sec1;
+ fi.Offset = offset;
+ fi.Length = length;
+
+ if (chunk[name + 0] == ':' && chunk[name + 1] == ':')
+ {
+ // System file
+ if (nameLen == 40 && fi.Filename.Trim().Equals(ContentName))
+ chm.Sec1.Content = fi;
+
+ else if (nameLen == 44 && fi.Filename.Trim().Equals(ControlName))
+ chm.Sec1.Control = fi;
+
+ else if (nameLen == 41 && fi.Filename.Trim().Equals(SpanInfoName))
+ chm.Sec1.SpanInfo = fi;
+
+ else if (nameLen == 105 && fi.Filename.Trim().Equals(ResetTableName))
+ chm.Sec1.ResetTable = fi;
+
+ fi.Next = chm.SysFiles;
+ chm.SysFiles = fi;
+ }
+ else
+ {
+ // Normal file
+ if (link != null)
+ link.Next = fi;
+ else
+ chm.Files = fi;
+
+ link = fi;
+ }
+ }
+
+ // This is reached either when num_entries runs out, or if
+ // reading data from the chunk reached a premature end of chunk
+ chunk_end:
+ if (numEntries >= 0)
+ {
+ Console.WriteLine("chunk ended before all entries could be read");
+ errors++;
+ }
+ }
+
+ sys.Free(chunk);
+ return (errors > 0) ? Error.MSPACK_ERR_DATAFORMAT : Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region CHMD_FAST_FIND
+
+ ///
+ /// uses PMGI index chunks and quickref data to quickly locate a file
+ /// directly from the on-disk index.
+ ///
+ /// TODO: protect against infinite loops in chunks (where pgml_NextChunk
+ /// or a PMGI index entry point to an already visited chunk)
+ ///
+ public static Error FastFind(Decompressor d, Header chm, string filename, DecompressFile f_ptr)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ SystemImpl sys;
+ object fh;
+
+ // p and end are initialised to prevent MSVC warning about "potentially"
+ // uninitialised usage. This is provably untrue, but MS won't fix:
+ // https://developercommunity.visualstudio.com/content/problem/363489/c4701-false-positive-warning.html
+ byte[] chunk = new byte[0];
+ int p = -1, end = -1, result = -1;
+ Error err = Error.MSPACK_ERR_OK;
+ uint n, sec;
+
+ if (self == null || chm == null || f_ptr == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ sys = self.System;
+
+ // Clear the results structure
+ f_ptr = new DecompressFile();
+
+ if ((fh = sys.Open(sys, chm.Filename, OpenMode.MSPACK_SYS_OPEN_READ)) == null)
+ return Error.MSPACK_ERR_OPEN;
+
+ // Go through PMGI chunk hierarchy to reach PMGL chunk
+ if (chm.IndexRoot < chm.NumChunks)
+ {
+ n = chm.IndexRoot;
+ for (; ; )
+ {
+ if ((chunk = ReadChunk(self, chm, fh, n)) == null)
+ {
+ sys.Close(fh);
+ return self.Error;
+ }
+
+ // Search PMGI/PMGL chunk. exit early if no entry found
+ if ((result = SearchChunk(chm, chunk, filename, ref p, ref end)) <= 0)
+ break;
+
+ /* found result. loop around for next chunk if this is PMGI */
+ if (chunk[3] == 0x4C)
+ {
+ break;
+ }
+ else
+ {
+ // READ_ENCINT(n)
+ n = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ n = (uint)((n << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+ }
+ }
+ }
+ else
+ {
+ // PMGL chunks only, search from first_pmgl to last_pmgl
+ for (n = chm.FirstPMGL; n <= chm.LastPMGL; n = BitConverter.ToUInt32(chunk, pmgl_NextChunk))
+ {
+ if ((chunk = ReadChunk(self, chm, fh, n)) == null)
+ {
+ err = self.Error;
+ break;
+ }
+
+ // Search PMGL chunk. exit if file found
+ if ((result = SearchChunk(chm, chunk, filename, ref p, ref end)) > 0)
+ break;
+
+ // Stop simple infinite loops: can't visit the same chunk twice
+ if (n == BitConverter.ToUInt32(chunk, pmgl_NextChunk))
+ break;
+ }
+ }
+
+ // If we found a file, read it
+ if (result > 0)
+ {
+ // READ_ENCINT(sec)
+ sec = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ sec = (uint)((sec << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+
+ f_ptr.Section = (sec == 0) ? (Section)chm.Sec0 : (Section)chm.Sec1;
+
+ // READ_ENCINT(f_ptr.Offset)
+ f_ptr.Offset = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ f_ptr.Offset = (uint)((f_ptr.Offset << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+
+ // READ_ENCINT(f_ptr.Length)
+ f_ptr.Length = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ f_ptr.Length = (uint)((f_ptr.Length << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+ }
+ else if (result < 0)
+ {
+ err = Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ sys.Close(fh);
+ return self.Error = err;
+
+ chunk_end:
+ Console.WriteLine("read beyond end of chunk entries");
+ sys.Close(fh);
+ return self.Error = Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ ///
+ /// Reads the given chunk into memory, storing it in a chunk cache
+ /// so it doesn't need to be read from disk more than once
+ ///
+ public static byte[] ReadChunk(DecompressorImpl self, Header chm, object fh, uint chunkNum)
+ {
+ SystemImpl sys = self.System;
+ byte[] buf;
+
+ // Check arguments - most are already checked by chmd_fast_find
+ if (chunkNum >= chm.NumChunks)
+ return null;
+
+ // ensure chunk cache is available
+ if (chm.ChunkCache == null)
+ chm.ChunkCache = new byte[chm.NumChunks][];
+
+ // try to answer out of chunk cache */
+ if (chm.ChunkCache[chunkNum] != null)
+ return chm.ChunkCache[chunkNum];
+
+ // Need to read chunk - allocate memory for it
+ if ((buf = sys.Alloc(sys, (int)chm.ChunkSize)) == null)
+ {
+ self.Error = Error.MSPACK_ERR_NOMEMORY;
+ return null;
+ }
+
+ // Seek to block and read it
+ if (!sys.Seek(fh, (chm.DirOffset + (chunkNum * chm.ChunkSize)), SeekMode.MSPACK_SYS_SEEK_START))
+ {
+ self.Error = Error.MSPACK_ERR_SEEK;
+ sys.Free(buf);
+ return null;
+ }
+
+ if (sys.Read(fh, buf, 0, (int)chm.ChunkSize) != (int)chm.ChunkSize)
+ {
+ self.Error = Error.MSPACK_ERR_READ;
+ sys.Free(buf);
+ return null;
+ }
+
+ // Check the signature. Is is PMGL or PMGI?
+ if (!((buf[0] == 0x50) && (buf[1] == 0x4D) && (buf[2] == 0x47) && ((buf[3] == 0x4C) || (buf[3] == 0x49))))
+ {
+ self.Error = Error.MSPACK_ERR_SEEK;
+ sys.Free(buf);
+ return null;
+ }
+
+ // All OK. Store chunk in cache and return it
+ return chm.ChunkCache[chunkNum] = buf;
+ }
+
+ ///
+ /// searches a PMGI/PMGL chunk for a given filename entry. Returns -1 on
+ /// data format error, 0 if entry definitely not found, 1 if entry
+ /// found.In the latter case, * result and* result_end are set pointing
+ /// to that entry's data (either the "next chunk" ENCINT for a PMGI or
+ /// the section, offset and length ENCINTs for a PMGL).
+ ///
+ /// In the case of PMGL chunks, the entry has definitely been
+ /// found.In the case of PMGI chunks, the entry which points to the
+ /// chunk that may eventually contain that entry has been found.
+ ///
+ public static int SearchChunk(Header chm, byte[] chunk, string filename, ref int result, ref int resultEnd)
+ {
+ int p;
+ uint nameLen;
+ uint left, right, midpoint, entriesOff;
+ bool is_pmgl;
+ int cmp;
+
+ // PMGL chunk or PMGI chunk? (note: read_chunk() has already
+ // checked the rest of the characters in the chunk signature)
+ if (chunk[3] == 0x4C)
+ {
+ is_pmgl = true;
+ entriesOff = pmgl_Entries;
+ }
+ else
+ {
+ is_pmgl = false;
+ entriesOff = pmgi_Entries;
+ }
+
+ // Step 1: binary search first filename of each QR entry
+ // - target filename == entry
+ // found file
+ // - target filename < all entries
+ // file not found
+ // - target filename > all entries
+ // proceed to step 2 using final entry
+ // - target filename between two searched entries
+ // Proceed to step 2
+ uint qrSize = BitConverter.ToUInt32(chunk, pmgl_QuickRefSize);
+ int start = (int)(chm.ChunkSize - 2);
+ int end = (int)(chm.ChunkSize - qrSize);
+ ushort numEntries = BitConverter.ToUInt16(chunk, start);
+ uint qrDensity = 1 + (uint)(1 << (int)chm.Density);
+ uint qrEntries = (numEntries + qrDensity - 1) / qrDensity;
+
+ if (numEntries == 0)
+ {
+ Console.Write("chunk has no entries");
+ return -1;
+ }
+
+ if (qrSize > chm.ChunkSize)
+ {
+ Console.Write("quickref size > chunk size");
+ return -1;
+ }
+
+ resultEnd = end;
+
+ if (((int)qrEntries * 2) > (start - end))
+ {
+ Console.Write("WARNING; more quickrefs than quickref space");
+ qrEntries = 0; // But we can live with it
+ }
+
+ if (qrEntries > 0)
+ {
+ left = 0;
+ right = qrEntries - 1;
+ do
+ {
+ // Pick new midpoint
+ midpoint = (left + right) >> 1;
+
+ // Compare filename with entry QR points to
+ p = (int)(entriesOff + (midpoint != 0 ? BitConverter.ToUInt16(chunk, (int)(start - (midpoint << 1))) : 0));
+
+ // READ_ENCINT(nameLen)
+ nameLen = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ nameLen = (uint)((nameLen << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+
+ if (nameLen > (uint)(end - p))
+ goto chunk_end;
+
+ cmp = string.Compare(filename, Encoding.ASCII.GetString(chunk, p, (int)nameLen), StringComparison.OrdinalIgnoreCase);
+
+ if (cmp == 0)
+ {
+ break;
+ }
+ else if (cmp < 0)
+ {
+ if (midpoint != 0)
+ right = midpoint - 1;
+ else
+ return 0;
+ }
+ else if (cmp > 0)
+ {
+ left = midpoint + 1;
+ }
+ } while (left <= right);
+
+ midpoint = (left + right) >> 1;
+
+ if (cmp == 0)
+ {
+ // Exact match!
+ p += (int)nameLen;
+ result = p;
+ return 1;
+ }
+
+ // Otherwise, read the group of entries for QR entry M
+ p = (int)(entriesOff + (midpoint != 0 ? BitConverter.ToUInt16(chunk, (int)(start - (midpoint << 1))) : 0));
+ numEntries -= (ushort)(midpoint * qrDensity);
+ if (numEntries > qrDensity)
+ numEntries = (ushort)qrDensity;
+ }
+ else
+ {
+ p = (int)entriesOff;
+ }
+
+ // Step 2: linear search through the set of entries reached in step 1.
+ // - filename == any entry
+ // found entry
+ // - filename < all entries (PMGI) or any entry (PMGL)
+ // entry not found, stop now
+ // - filename > all entries
+ // entry not found (PMGL) / maybe found (PMGI)
+ result = -1;
+ while (numEntries-- > 0)
+ {
+ // READ_ENCINT(nameLen)
+ nameLen = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ nameLen = (uint)((nameLen << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+
+ if (nameLen > (uint)(end - p))
+ goto chunk_end;
+
+ cmp = string.Compare(filename, Encoding.ASCII.GetString(chunk, p, (int)nameLen), StringComparison.OrdinalIgnoreCase);
+ p += (int)nameLen;
+
+ if (cmp == 0)
+ {
+ // Entry found
+ result = p;
+ return 1;
+ }
+
+ if (cmp < 0)
+ {
+ // Entry not found (PMGL) / maybe found (PMGI)
+ break;
+ }
+
+ // Read and ignore the rest of this entry
+ if (is_pmgl)
+ {
+ // Skip section, offset, and length
+ for (int i = 0; i < 3; i++)
+ {
+ // READ_ENCINT(R)
+ right = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ right = (uint)((right << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+ }
+ }
+ else
+ {
+ result = p; // Store potential final result
+
+ // Skip chunk number
+ // READ_ENCINT(R)
+ right = 0;
+ do
+ {
+ if (p >= end)
+ goto chunk_end;
+
+ right = (uint)((right << 7) | (chunk[p] & 0x7F));
+ } while ((chunk[p++] & 0x80) != 0);
+ }
+ }
+
+ // PMGL? not found. PMGI? maybe found
+ return (is_pmgl) ? 0 : (result != 0 ? 1 : 0);
+
+ chunk_end:
+ Console.WriteLine("reached end of chunk data while searching");
+ return -1;
+ }
+
+ #endregion
+
+ #region CHMD_EXTRACT
+
+ ///
+ /// Extracts a file from a CHM helpfile
+ ///
+ public static Error Extract(Decompressor d, DecompressFile file, string filename)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ if (file == null || file.Section == null)
+ return self.Error = Error.MSPACK_ERR_ARGS;
+
+ SystemImpl sys = self.System;
+ Header chm = file.Section.Header;
+
+ // Create decompression state if it doesn't exist
+ if (self.State == null)
+ {
+ self.State = new DecompressState();
+ self.State.Header = chm;
+ self.State.Offset = 0;
+ self.State.State = null;
+ self.State.Sys = sys;
+ self.State.Sys.Write = SysWrite;
+ self.State.InputFileHandle = null;
+ self.State.OutputFileHandle = null;
+ }
+
+ // Open input chm file if not open, or the open one is a different chm
+ if (self.State.InputFileHandle == null || (self.State.Header != chm))
+ {
+ if (self.State.InputFileHandle != null)
+ sys.Close(self.State.InputFileHandle);
+
+ if (self.State.State != null)
+ LZX.Free(self.State.State);
+
+ self.State.Header = chm;
+ self.State.Offset = 0;
+ self.State.State = null;
+ self.State.InputFileHandle = sys.Open(sys, chm.Filename, OpenMode.MSPACK_SYS_OPEN_READ);
+ if (self.State.InputFileHandle == null)
+ return self.Error = Error.MSPACK_ERR_OPEN;
+ }
+
+ // Open file for output
+ object fh;
+ if ((fh = sys.Open(sys, filename, OpenMode.MSPACK_SYS_OPEN_WRITE)) == null)
+ return self.Error = Error.MSPACK_ERR_OPEN;
+
+ // If file is empty, simply creating it is enough
+ if (file.Length == 0)
+ {
+ sys.Close(fh);
+ return self.Error = Error.MSPACK_ERR_OK;
+ }
+
+ self.Error = Error.MSPACK_ERR_OK;
+
+ switch (file.Section.ID)
+ {
+ // Uncompressed section file
+ case 0:
+ // Simple seek + copy
+ if (!sys.Seek(self.State.InputFileHandle, file.Section.Header.Sec0.Offset + file.Offset, SeekMode.MSPACK_SYS_SEEK_START))
+ {
+ self.Error = Error.MSPACK_ERR_SEEK;
+ }
+ else
+ {
+ byte[] buf = new byte[512];
+ long length = file.Length;
+ while (length > 0)
+ {
+ int run = 512;
+ if (run > length)
+ run = (int)length;
+
+ if (sys.Read(self.State.InputFileHandle, buf, 0, run) != run)
+ {
+ self.Error = Error.MSPACK_ERR_READ;
+ break;
+ }
+
+ if (sys.Write(fh, buf, 0, run) != run)
+ {
+ self.Error = Error.MSPACK_ERR_WRITE;
+ break;
+ }
+
+ length -= run;
+ }
+ }
+ break;
+
+ // MSCompressed section file
+ case 1:
+ // (Re)initialise compression state if we it is not yet initialised,
+ // or we have advanced too far and have to backtrack
+ if (self.State.State == null || (file.Offset < self.State.Offset))
+ {
+ if (self.State.State != null)
+ {
+ LZX.Free(self.State.State);
+ self.State.State = null;
+ }
+
+ if (InitDecompressor(self, file) != Error.MSPACK_ERR_OK)
+ break;
+ }
+
+ // Seek to input data
+ if (!sys.Seek(self.State.InputFileHandle, self.State.InOffset, SeekMode.MSPACK_SYS_SEEK_START))
+ {
+ self.Error = Error.MSPACK_ERR_SEEK;
+ break;
+ }
+
+ // Get to correct offset.
+ self.State.OutputFileHandle = null;
+ long bytes;
+ if ((bytes = file.Offset - self.State.Offset) != 0)
+ self.Error = LZX.Decompress(self.State.State, bytes);
+
+ // If getting to the correct offset was error free, unpack file
+ if (self.Error == Error.MSPACK_ERR_OK)
+ {
+ self.State.OutputFileHandle = fh;
+ self.Error = LZX.Decompress(self.State.State, file.Length);
+ }
+
+ // Save offset in input source stream, in case there is a section 0
+ // file between now and the next section 1 file extracted
+ self.State.InOffset = sys.Tell(self.State.InputFileHandle);
+
+ // If an LZX error occured, the LZX decompressor is now useless
+ if (self.Error != Error.MSPACK_ERR_OK)
+ {
+ if (self.State.State != null)
+ LZX.Free(self.State.State);
+
+ self.State.State = null;
+ }
+
+ break;
+ }
+
+ sys.Close(fh);
+ return self.Error;
+ }
+
+ #endregion
+
+ #region CHMD_SYS_WRITE
+
+ ///
+ /// chmd_sys_write is the internal writer function which the decompressor
+ /// uses. If either writes data to disk (self.State.OutputFileHandle) with the real
+ /// sys.write() function, or does nothing with the data when
+ /// self.State.OutputFileHandle == null. advances self.State.Offset.
+ ///
+ private static int SysWrite(object file, byte[] buffer, int offset, int bytes)
+ {
+ DecompressorImpl self = (DecompressorImpl)file;
+ self.State.Offset += bytes;
+ if (self.State.OutputFileHandle != null)
+ return self.System.Write(self.State.OutputFileHandle, buffer, offset, bytes);
+
+ return bytes;
+ }
+
+ #endregion
+
+ #region CHMD_INIT_DECOMP
+
+ ///
+ /// Initialises the LZX decompressor to decompress the compressed stream,
+ /// from the nearest reset offset and length that is needed for the given
+ /// file.
+ ///
+ public static Error InitDecompressor(DecompressorImpl self, DecompressFile file)
+ {
+ int window_size, window_bits, reset_interval, entry;
+ SystemImpl sys = self.System;
+ MSCompressedSection sec;
+ byte[] data;
+
+ sec = (MSCompressedSection)file.Section;
+
+ // Ensure we have a mscompressed content section
+ DecompressFile contentFile = null;
+ Error err = FindSysFile(self, sec, ref contentFile, ContentName);
+ if (err != Error.MSPACK_ERR_OK)
+ return self.Error = err;
+
+ sec.Content = contentFile;
+
+ // Ensure we have a ControlData file
+ DecompressFile controlFile = null;
+ err = FindSysFile(self, sec, ref controlFile, ControlName);
+ if (err != Error.MSPACK_ERR_OK)
+ return self.Error = err;
+
+ sec.Control = controlFile;
+
+ // Read ControlData
+ if (sec.Control.Length != lzxcd_SIZEOF)
+ {
+ Console.WriteLine("ControlData file is wrong size");
+ return self.Error = Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ if ((data = ReadSysFile(self, sec.Control)) == null)
+ {
+ Console.WriteLine("can't read mscompressed control data file");
+ return self.Error;
+ }
+
+ // Check LZXC signature
+ if (BitConverter.ToUInt32(data, lzxcd_Signature) != 0x43585A4C)
+ {
+ sys.Free(data);
+ return self.Error = Error.MSPACK_ERR_SIGNATURE;
+ }
+
+ // Read reset_interval and window_size and validate version number
+ switch (BitConverter.ToUInt32(data, lzxcd_Version))
+ {
+ case 1:
+ reset_interval = (int)BitConverter.ToUInt32(data, lzxcd_ResetInterval);
+ window_size = (int)BitConverter.ToUInt32(data, lzxcd_WindowSize);
+ break;
+ case 2:
+ reset_interval = (int)BitConverter.ToUInt32(data, lzxcd_ResetInterval) * LZX.LZX_FRAME_SIZE;
+ window_size = (int)BitConverter.ToUInt32(data, lzxcd_WindowSize) * LZX.LZX_FRAME_SIZE;
+ break;
+ default:
+ Console.WriteLine("bad controldata version");
+ sys.Free(data);
+ return self.Error = Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Free ControlData
+ sys.Free(data);
+
+ // Find window_bits from window_size
+ switch (window_size)
+ {
+ case 0x008000: window_bits = 15; break;
+ case 0x010000: window_bits = 16; break;
+ case 0x020000: window_bits = 17; break;
+ case 0x040000: window_bits = 18; break;
+ case 0x080000: window_bits = 19; break;
+ case 0x100000: window_bits = 20; break;
+ case 0x200000: window_bits = 21; break;
+ default:
+ Console.WriteLine("bad controldata window size");
+ return self.Error = Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Validate reset_interval
+ if (reset_interval == 0 || (reset_interval % LZX.LZX_FRAME_SIZE) != 0)
+ {
+ Console.WriteLine("bad controldata reset interval");
+ return self.Error = Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Which reset table entry would we like?
+ entry = (int)(file.Offset / reset_interval);
+
+ // Convert from reset interval multiple (usually 64k) to 32k frames
+ entry *= reset_interval / LZX.LZX_FRAME_SIZE;
+
+ // Read the reset table entry
+ if (ReadResetTable(self, sec, (uint)entry, out long length, out long offset))
+ {
+ // The uncompressed length given in the reset table is dishonest.
+ // The uncompressed data is always padded out from the given
+ // uncompressed length up to the next reset interval
+ length += reset_interval - 1;
+ length &= -reset_interval;
+ }
+ else
+ {
+ // if we can't read the reset table entry, just start from
+ // the beginning. Use spaninfo to get the uncompressed length
+ entry = 0;
+ offset = 0;
+ err = ReadSpanInfo(self, sec, out length);
+ }
+
+ if (err != Error.MSPACK_ERR_OK)
+ return self.Error = err;
+
+ // Get offset of compressed data stream:
+ // = offset of uncompressed section from start of file
+ // + offset of compressed stream from start of uncompressed section
+ // + offset of chosen reset interval from start of compressed stream
+ self.State.InOffset = file.Section.Header.Sec0.Offset + sec.Content.Offset + offset;
+
+ // Set start offset and overall remaining stream length
+ self.State.Offset = entry * LZX.LZX_FRAME_SIZE;
+ length -= self.State.Offset;
+
+ // Initialise LZX stream
+ self.State.State = LZX.Init(self.State.Sys, self.State.InputFileHandle, self, window_bits, reset_interval / LZX.LZX_FRAME_SIZE, 4096, length, false);
+
+ if (self.State.State == null)
+ self.Error = Error.MSPACK_ERR_NOMEMORY;
+
+ return self.Error;
+ }
+
+ #endregion
+
+ #region READ_RESET_TABLE
+
+ ///
+ /// Reads one entry out of the reset table. Also reads the uncompressed
+ /// data length. Writes these to offset_ptr and length_ptr respectively.
+ /// Returns non-zero for success, zero for failure.
+ ///
+ public static bool ReadResetTable(DecompressorImpl self, MSCompressedSection sec, uint entry, out long length_ptr, out long offset_ptr)
+ {
+ length_ptr = 0; offset_ptr = 0;
+ SystemImpl sys = self.System;
+ byte[] data;
+
+ // Do we have a ResetTable file?
+ DecompressFile resetTable = null;
+ Error err = FindSysFile(self, sec, ref resetTable, ResetTableName);
+ if (err != Error.MSPACK_ERR_OK)
+ return false;
+
+ sec.ResetTable = resetTable;
+
+ // Read ResetTable file
+ if (sec.ResetTable.Length < lzxrt_headerSIZEOF)
+ {
+ Console.WriteLine("ResetTable file is too short");
+ return false;
+ }
+
+ if (sec.ResetTable.Length > 1000000)
+ {
+ // Arbitrary upper limit
+ Console.WriteLine($"ResetTable >1MB ({sec.ResetTable.Length}), report if genuine");
+ return false;
+ }
+
+ if ((data = ReadSysFile(self, sec.ResetTable)) == null)
+ {
+ Console.WriteLine("can't read reset table");
+ return false;
+ }
+
+ // Check sanity of reset table
+ if (BitConverter.ToUInt32(data, lzxrt_FrameLen) != LZX.LZX_FRAME_SIZE)
+ {
+ Console.WriteLine(("bad reset table frame length"));
+ sys.Free(data);
+ return false;
+ }
+
+ // Get the uncompressed length of the LZX stream
+ if ((length_ptr = BitConverter.ToInt64(data, lzxrt_UncompLen)) == 0)
+ {
+ sys.Free(data);
+ return false;
+ }
+
+ uint entrysize = BitConverter.ToUInt32(data, lzxrt_EntrySize);
+ uint pos = BitConverter.ToUInt32(data, lzxrt_TableOffset) + (entry * entrysize);
+
+ // Ensure reset table entry for this offset exists
+ if (entry < BitConverter.ToUInt32(data, lzxrt_NumEntries) && pos <= (sec.ResetTable.Length - entrysize))
+ {
+ switch (entrysize)
+ {
+ case 4:
+ offset_ptr = BitConverter.ToUInt32(data, (int)pos);
+ err = 0;
+ break;
+ case 8:
+ offset_ptr = BitConverter.ToInt64(data, (int)pos);
+ break;
+ default:
+ Console.WriteLine("reset table entry size neither 4 nor 8");
+ err = Error.MSPACK_ERR_ARGS;
+ break;
+ }
+ }
+ else
+ {
+ Console.WriteLine("bad reset interval");
+ err = Error.MSPACK_ERR_ARGS;
+ }
+
+ // Free the reset table
+ sys.Free(data);
+
+ // Return success
+ return (err == Error.MSPACK_ERR_OK);
+ }
+
+ #endregion
+
+ #region READ_SPANINFO
+
+ ///
+ /// Reads the uncompressed data length from the spaninfo file.
+ /// Returns zero for success or a non-zero error code for failure.
+ ///
+ public static Error ReadSpanInfo(DecompressorImpl self, MSCompressedSection sec, out long length_ptr)
+ {
+ length_ptr = 0;
+ SystemImpl sys = self.System;
+
+ // Find SpanInfo file
+ DecompressFile spanInfo = null;
+ Error err = FindSysFile(self, sec, ref spanInfo, SpanInfoName);
+ if (err != Error.MSPACK_ERR_OK)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ sec.SpanInfo = spanInfo;
+
+ // Check it's large enough
+ if (sec.SpanInfo.Length != 8)
+ {
+ Console.WriteLine("SpanInfo file is wrong size");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Read the SpanInfo file
+ byte[] data;
+ if ((data = ReadSysFile(self, sec.SpanInfo)) == null)
+ {
+ Console.WriteLine("can't read SpanInfo file");
+ return self.Error;
+ }
+
+ // Get the uncompressed length of the LZX stream
+ length_ptr = BitConverter.ToInt64(data, 0);
+ sys.Free(data);
+ if (err != Error.MSPACK_ERR_OK)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ if (length_ptr <= 0)
+ {
+ Console.WriteLine("output length is invalid");
+ return Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region FIND_SYS_FILE
+
+ ///
+ /// Uses chmd_fast_find to locate a system file, and fills out that system
+ /// file's entry and links it into the list of system files. Returns zero
+ /// for success, non-zero for both failure and the file not existing.
+ ///
+ public static Error FindSysFile(DecompressorImpl self, MSCompressedSection sec, ref DecompressFile f_ptr, string name)
+ {
+ SystemImpl sys = self.System;
+ DecompressFile result = null;
+
+ // Already loaded
+ if (f_ptr != null)
+ return Error.MSPACK_ERR_OK;
+
+ // Try using fast_find to find the file - return DATAFORMAT error if
+ // it fails, or successfully doesn't find the file
+ if (FastFind(self, sec.Header, name, result) != Error.MSPACK_ERR_OK || result.Section == null)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ f_ptr = new DecompressFile();
+
+ // Copy result
+ f_ptr = result;
+ f_ptr.Filename = name;
+
+ // Link file into sysfiles list
+ f_ptr.Next = sec.Header.SysFiles;
+ sec.Header.SysFiles = f_ptr;
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region READ_SYS_FILE
+
+ ///
+ /// Allocates memory for a section 0 (uncompressed) file and reads it into memory.
+ ///
+ public static byte[] ReadSysFile(DecompressorImpl self, DecompressFile file)
+ {
+ SystemImpl sys = self.System;
+
+ if (file == null || file.Section == null || (file.Section.ID != 0))
+ {
+ self.Error = Error.MSPACK_ERR_DATAFORMAT;
+ return null;
+ }
+
+ int len = (int)file.Length;
+ byte[] data = new byte[len];
+
+ if (sys.Seek(self.State.InputFileHandle, file.Section.Header.Sec0.Offset + file.Offset, SeekMode.MSPACK_SYS_SEEK_START))
+ {
+ self.Error = Error.MSPACK_ERR_SEEK;
+ sys.Free(data);
+ return null;
+ }
+
+ if (sys.Read(self.State.InputFileHandle, data, 0, len) != len)
+ {
+ self.Error = Error.MSPACK_ERR_READ;
+ sys.Free(data);
+ return null;
+ }
+
+ return data;
+ }
+
+ #endregion
+
+ #region CHMD_ERROR
+
+ ///
+ /// Returns the last error that occurred
+ ///
+ ///
+ ///
+ public static Error LastError(Decompressor d)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ return (self != null ? self.Error : Error.MSPACK_ERR_ARGS);
+ }
+
+ #endregion
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/MSCompressedSection.cs b/BurnOutSharp/External/libmspack/CHM/MSCompressedSection.cs
new file mode 100644
index 00000000..f5d43bb9
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/MSCompressedSection.cs
@@ -0,0 +1,47 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CHM
+{
+ ///
+ /// A structure which represents the LZX compressed section of a CHM helpfile.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ public class MSCompressedSection : Section
+ {
+ ///
+ /// A pointer to the meta-file which represents all LZX compressed data.
+ ///
+ public DecompressFile Content { get; set; }
+
+ ///
+ /// A pointer to the file which contains the LZX control data.
+ ///
+ public DecompressFile Control { get; set; }
+
+ ///
+ /// A pointer to the file which contains the LZX reset table.
+ ///
+ public DecompressFile ResetTable { get; set; }
+
+ ///
+ /// A pointer to the file which contains the LZX span information.
+ /// Available only in CHM decoder version 2 and above.
+ ///
+ public DecompressFile SpanInfo { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/Section.cs b/BurnOutSharp/External/libmspack/CHM/Section.cs
new file mode 100644
index 00000000..968adff2
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/Section.cs
@@ -0,0 +1,41 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CHM
+{
+ ///
+ /// A structure which represents a section of a CHM helpfile.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ /// Not used directly, but used as a generic base type for
+ /// mschmd_sec_uncompressed and mschmd_sec_mscompressed.
+ ///
+ public class Section
+ {
+ ///
+ /// A pointer to the CHM helpfile that contains this section.
+ ///
+ public Header Header { get; set; }
+
+ ///
+ /// The section ID. Either 0 for the uncompressed section
+ /// mschmd_sec_uncompressed, or 1 for the LZX compressed section
+ /// mschmd_sec_mscompressed. No other section IDs are known.
+ ///
+ public uint ID { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/CHM/UncompressedSection.cs b/BurnOutSharp/External/libmspack/CHM/UncompressedSection.cs
new file mode 100644
index 00000000..9138cd99
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/CHM/UncompressedSection.cs
@@ -0,0 +1,31 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.CHM
+{
+ ///
+ /// A structure which represents the uncompressed section of a CHM helpfile.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ public class UncompressedSection : Section
+ {
+ ///
+ /// The file offset of where this section begins in the CHM helpfile.
+ ///
+ public long Offset { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Checksum.cs b/BurnOutSharp/External/libmspack/Checksum.cs
new file mode 100644
index 00000000..3095525b
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Checksum.cs
@@ -0,0 +1,111 @@
+/*
+ * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
+ * code or tables extracted from it, as desired without restriction.
+ *
+ * First, the polynomial itself and its table of feedback terms. The
+ * polynomial is
+ * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ * Note that we take it "backwards" and put the highest-order term in
+ * the lowest-order bit. The X^32 term is "implied"; the LSB is the
+ * X^31 term, etc. The X^0 term (usually shown as "+1") results in
+ * the MSB being 1
+ *
+ * Note that the usual hardware shift register implementation, which
+ * is what we're using (we're merely optimizing it by doing eight-bit
+ * chunks at a time) shifts bits into the lowest-order term. In our
+ * implementation, that means shifting towards the right. Why do we
+ * do it this way? Because the calculated CRC must be transmitted in
+ * order from highest-order term to lowest-order term. UARTs transmit
+ * characters in order from LSB to MSB. By storing the CRC this way
+ * we hand it to the UART in the order low-byte to high-byte; the UART
+ * sends each low-bit to hight-bit; and the result is transmission bit
+ * by bit from highest- to lowest-order term without requiring any bit
+ * shuffling on our part. Reception works similarly
+ *
+ * The feedback terms table consists of 256, 32-bit entries. Notes
+ *
+ * The table can be generated at runtime if desired; code to do so
+ * is shown later. It might not be obvious, but the feedback
+ * terms simply represent the results of eight shift/xor opera
+ * tions for all combinations of data and CRC register values
+ *
+ * The values must be right-shifted by eight bits by the "updcrc
+ * logic; the shift must be unsigned (bring in zeroes). On some
+ * hardware you could probably optimize the shift in assembler by
+ * using byte-swap instructions
+ * polynomial $edb88320
+ */
+
+namespace LibMSPackSharp
+{
+ public class Checksum
+ {
+ public static readonly uint[] CRC32Table = new uint[256]
+ {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
+ 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
+ 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
+ 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+ 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
+ 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
+ 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
+ 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
+ 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
+ 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
+ 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
+ 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
+ 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
+ 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
+ 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+ 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
+ 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
+ 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
+ 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
+ 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
+ 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
+ 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+ 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
+ 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
+ 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
+ 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
+ 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
+ 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
+ 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
+ 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
+ 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
+ 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
+ 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+ 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
+ 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
+ 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
+ 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
+ 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
+ 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
+ 0x2d02ef8d
+ };
+
+ ///
+ /// Return a 32-bit CRC of the contents of the buffer.
+ ///
+ public static uint CRC32(byte[] ss, int pointer, int len, uint val)
+ {
+ while (--len >= 0)
+ val = CRC32Table[(val ^ ss[pointer++]) & 0xff] ^ (val >> 8);
+
+ return val;
+ }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/CompressionStream.cs b/BurnOutSharp/External/libmspack/Compression/CompressionStream.cs
new file mode 100644
index 00000000..38424930
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/CompressionStream.cs
@@ -0,0 +1,460 @@
+/* This file is part of libmspack.
+ * (C) 2003-2013 Stuart Caie.
+ *
+ * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted
+ * by Microsoft Corporation.
+ *
+ * libmspack is free software { get; set; } you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+
+namespace LibMSPackSharp.Compression
+{
+ public abstract class CompressionStream
+ {
+ private const int CHAR_BIT = 8;
+
+ private const int BITBUF_WIDTH = 4 * CHAR_BIT;
+
+ ///
+ /// I/O routines
+ ///
+ public SystemImpl Sys { get; set; }
+
+ ///
+ /// Input file handle
+ ///
+ public object Input { get; set; }
+
+ ///
+ /// Output file handle
+ ///
+ public object Output { get; set; }
+
+ public Error Error { get; set; }
+
+ #region I/O buffering
+
+ public byte[] InputBuffer { get; set; }
+
+ public uint InputBufferSize { get; set; }
+
+ public int InputPointer { get; set; }
+
+ public int InputLength { get; set; }
+
+ public int OutputPointer { get; set; }
+
+ public int OutputLength { get; set; }
+
+ public uint BitBuffer { get; set; }
+
+ public uint BitsLeft { get; set; }
+
+ ///
+ /// Have we reached the end of input?
+ ///
+ public int InputEnd { get; set; }
+
+ #endregion
+
+ #region ReadBits Methods
+
+ /* This header defines macros that read data streams by
+ * the individual bits
+ *
+ * INIT_BITS initialises bitstream state in state structure
+ * STORE_BITS stores bitstream state in state structure
+ * RESTORE_BITS restores bitstream state from state structure
+ * ENSURE_BITS(n) ensure there are at least N bits in the bit buffer
+ * READ_BITS(var,n) takes N bits from the buffer and puts them in var
+ * PEEK_BITS(n) extracts without removing N bits from the bit buffer
+ * REMOVE_BITS(n) removes N bits from the bit buffer
+ *
+ * READ_BITS simply calls ENSURE_BITS, PEEK_BITS and REMOVE_BITS,
+ * which means it's limited to reading the number of bits you can
+ * ensure at any one time. It also fails if asked to read zero bits.
+ * If you need to read zero bits, or more bits than can be ensured in
+ * one go, use READ_MANY_BITS instead.
+ *
+ * These macros have variable names baked into them, so to use them
+ * you have to define some macros:
+ * - BITS_TYPE: the type name of your state structure
+ * - BITS_VAR: the variable that points to your state structure
+ * - define BITS_ORDER_MSB if bits are read from the MSB, or
+ * define BITS_ORDER_LSB if bits are read from the LSB
+ * - READ_BYTES: some code that reads more data into the bit buffer,
+ * it should use READ_IF_NEEDED (calls read_input if the byte buffer
+ * is empty), then INJECT_BITS(data,n) to put data from the byte
+ * buffer into the bit buffer.
+ *
+ * You also need to define some variables and structure members:
+ * - byte[] i_ptr; // current position in the byte buffer
+ * - byte[] i_end; // end of the byte buffer
+ * - uint bit_buffer; // the bit buffer itself
+ * - uint bits_left; // number of bits remaining
+ *
+ * If you use read_input() and READ_IF_NEEDED, they also expect these
+ * structure members:
+ * - struct mspack_system *sys; // to access sys->read()
+ * - uint error; // to record/return read errors
+ * - byte input_end; // to mark reaching the EOF
+ * - byte[] inbuf; // the input byte buffer
+ * - uint inbuf_size; // the size of the input byte buffer
+ *
+ * Your READ_BYTES implementation should read data from *i_ptr and
+ * put them in the bit buffer. READ_IF_NEEDED will call read_input()
+ * if i_ptr reaches i_end, and will fill up inbuf and set i_ptr to
+ * the start of inbuf and i_end to the end of inbuf.
+ *
+ * If you're reading in MSB order, the routines work by using the area
+ * beyond the MSB and the LSB of the bit buffer as a free source of
+ * zeroes when shifting. This avoids having to mask any bits. So we
+ * have to know the bit width of the bit buffer variable. We use
+ * and CHAR_BIT to find the size of the bit buffer in bits.
+ *
+ * If you are reading in LSB order, bits need to be masked. Normally
+ * this is done by computing the mask: N bits are masked by the value
+ * (1< 0)
+ {
+ if (bitsLeft <= (BITBUF_WIDTH - 16))
+ {
+ Error error = READ_BYTES(ref i_ptr, ref i_end, ref bitsLeft, ref bitBuffer);
+ if (error != Error.MSPACK_ERR_OK)
+ return error;
+ }
+
+ bitrun = (byte)((bitsLeft < needed) ? bitsLeft : needed);
+ val = (uint)((val << bitrun) | PEEK_BITS(bitrun, bitBuffer));
+ REMOVE_BITS(bitrun, ref bitsLeft, ref bitBuffer);
+ needed -= bitrun;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ public int PEEK_BITS(int nbits, uint bitBuffer)
+ {
+ return (int)(bitBuffer & ((1 << (nbits)) - 1));
+ }
+
+ public void REMOVE_BITS(int nbits, ref uint bitsLeft, ref uint bitBuffer)
+ {
+ bitBuffer >>= nbits;
+ bitsLeft -= (uint)nbits;
+ }
+
+ public void INJECT_BITS(uint bitdata, int nbits, ref uint bitsLeft, ref uint bitBuffer)
+ {
+ bitBuffer |= bitdata << (int)bitsLeft;
+ bitsLeft += (uint)nbits;
+ }
+
+ public abstract Error READ_BYTES(ref int i_ptr, ref int i_end, ref uint bitsLeft, ref uint bitBuffer);
+
+ // lsb_bit_mask[n] = (1 << n) - 1 */
+ private static readonly ushort[] lsb_bit_mask = new ushort[17]
+ {
+ 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,
+ 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff
+ };
+
+ public int PEEK_BITS_T(int nbits)
+ {
+ return (int)(BitBuffer & lsb_bit_mask[nbits]);
+ }
+
+ public void READ_BITS_T(ref int val, int nbits, ref int i_ptr, ref int i_end, ref uint bitsLeft, ref uint bitBuffer)
+ {
+ ENSURE_BITS(nbits, ref i_ptr, ref i_end, ref bitsLeft, ref bitBuffer);
+ val = PEEK_BITS_T(nbits);
+ REMOVE_BITS(nbits, ref bitsLeft, ref bitBuffer);
+ }
+
+ public Error READ_IF_NEEDED(ref int iPtr, ref int iEnd)
+ {
+ if (iPtr >= iEnd)
+ {
+ if (ReadInput(this) != Error.MSPACK_ERR_OK)
+ return Error;
+
+ iPtr = InputPointer;
+ iEnd = InputLength;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ private static Error ReadInput(CompressionStream p)
+ {
+ int read = p.Sys.Read(p.Input, p.InputBuffer, 0, (int)p.InputBufferSize);
+ if (read < 0)
+ return p.Error = Error.MSPACK_ERR_READ;
+
+ // We might overrun the input stream by asking for bits we don't use,
+ // so fake 2 more bytes at the end of input
+ if (read == 0)
+ {
+ if (p.InputEnd != 0)
+ {
+ Console.WriteLine("out of input bytes");
+ return p.Error = Error.MSPACK_ERR_READ;
+ }
+ else
+ {
+ read = 2;
+ p.InputBuffer[0] = p.InputBuffer[1] = 0;
+ p.InputEnd = 1;
+ }
+ }
+
+ // Update i_ptr and i_end
+ p.InputPointer = 0;
+ p.InputLength = read;
+ return Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region ReadHuff Methods
+
+ private const int HUFF_MAXBITS = 16;
+
+ ///
+ /// Decodes the next huffman symbol from the input bitstream into var.
+ /// Do not use this macro on a table unless build_decode_table() succeeded.
+ ///
+ public int READ_HUFFSYM(ushort[] decodingTable, ref uint var, int tablebits, byte[] lengthTable, int maxsymbols, ref int i, ref ushort sym, ref int i_ptr, ref int i_end, ref uint bitsLeft, ref uint bitBuffer)
+ {
+ ENSURE_BITS(HUFF_MAXBITS, ref i_ptr, ref i_end, ref bitsLeft, ref bitBuffer);
+ sym = decodingTable[PEEK_BITS(tablebits, bitBuffer)];
+ if (sym >= maxsymbols)
+ {
+ int ret = HUFF_TRAVERSE(decodingTable, tablebits, maxsymbols, ref i, ref sym, bitBuffer);
+ if (ret != 0)
+ return ret;
+ }
+
+ var = sym;
+ i = lengthTable[sym];
+ REMOVE_BITS(i, ref bitsLeft, ref bitBuffer);
+ return (int)Error.MSPACK_ERR_OK;
+ }
+
+ public int HUFF_TRAVERSE(ushort[] decodingTable, int tablebits, int maxsymbols, ref int i, ref ushort sym, uint bitBuffer)
+ {
+ i = tablebits - 1;
+ do
+ {
+ if (i++ > HUFF_MAXBITS)
+ return HUFF_ERROR();
+
+ sym = decodingTable[(sym << 1) | ((bitBuffer >> i) & 1)];
+ } while (sym >= maxsymbols);
+
+ return (int)Error.MSPACK_ERR_OK;
+ }
+
+ public abstract int HUFF_ERROR();
+
+ ///
+ /// This function was originally coded by David Tritscher.
+ ///
+ /// It builds a fast huffman decoding table from
+ /// a canonical huffman code lengths table.
+ ///
+ /// total number of symbols in this huffman tree.
+ /// any symbols with a code length of nbits or less can be decoded in one lookup of the table.
+ /// A table to get code lengths from [0 to nsyms-1]
+ ///
+ /// The table to fill up with decoded symbols and pointers.
+ /// Should be ((1<
+ /// 0 for OK or 1 for error
+ public static int MakeDecodeTable(uint nsyms, uint nbits, byte[] length, ushort[] table)
+ {
+ ushort sym, next_symbol;
+ uint leaf, fill;
+ uint reverse;
+ byte bit_num;
+ uint pos = 0; // The current position in the decode table
+ uint table_mask = (uint)(1 << (int)nbits);
+ uint bit_mask = table_mask >> 1; // Don't do 0 length codes
+
+ // Fill entries for codes short enough for a direct mapping
+ for (bit_num = 1; bit_num <= nbits; bit_num++)
+ {
+ for (sym = 0; sym < nsyms; sym++)
+ {
+ if (length[sym] != bit_num)
+ continue;
+
+ // Reverse the significant bits
+ fill = length[sym];
+ reverse = pos >> (int)(nbits - fill);
+ leaf = 0;
+
+ do
+ {
+ leaf <<= 1;
+ leaf |= reverse & 1;
+ reverse >>= 1;
+ } while (--fill != 0);
+
+ if ((pos += bit_mask) > table_mask)
+ return 1; // Table overrun
+
+ // Fill all possible lookups of this symbol with the symbol itself
+ fill = bit_mask;
+ next_symbol = (ushort)(1 << bit_num);
+
+ do
+ {
+ table[leaf] = sym;
+ leaf += next_symbol;
+ } while (--fill != 0);
+ }
+ bit_mask >>= 1;
+ }
+
+ // Exit with success if table is now complete
+ if (pos == table_mask)
+ return 0;
+
+ // Mark all remaining table entries as unused
+ for (sym = (ushort)pos; sym < table_mask; sym++)
+ {
+ reverse = sym;
+ leaf = 0;
+ fill = nbits;
+
+ do
+ {
+ leaf <<= 1;
+ leaf |= reverse & 1;
+ reverse >>= 1;
+ } while (--fill != 0);
+
+ table[leaf] = 0xFFFF;
+ }
+
+ // next_symbol = base of allocation for long codes
+ next_symbol = (ushort)(((table_mask >> 1) < nsyms) ? nsyms : (table_mask >> 1));
+
+ // Give ourselves room for codes to grow by up to 16 more bits.
+ // codes now start at bit nbits+16 and end at (nbits+16-codelength)
+ pos <<= 16;
+ table_mask <<= 16;
+ bit_mask = 1 << 15;
+
+ for (bit_num = (byte)(nbits + 1); bit_num <= HUFF_MAXBITS; bit_num++)
+ {
+ for (sym = 0; sym < nsyms; sym++)
+ {
+ if (length[sym] != bit_num)
+ continue;
+ if (pos >= table_mask)
+ return 1; // Table overflow
+
+ // Leaf = the first nbits of the code, reversed
+ reverse = pos >> 16;
+ leaf = 0;
+ fill = nbits;
+
+ do
+ {
+ leaf <<= 1;
+ leaf |= reverse & 1;
+ reverse >>= 1;
+ } while (--fill != 0);
+
+ for (fill = 0; fill < (bit_num - nbits); fill++)
+ {
+ // Ff this path hasn't been taken yet, 'allocate' two entries
+ if (table[leaf] == 0xFFFF)
+ {
+ table[(next_symbol << 1)] = 0xFFFF;
+ table[(next_symbol << 1) + 1] = 0xFFFF;
+ table[leaf] = next_symbol++;
+ }
+
+ // Follow the path and select either left or right for next bit
+ leaf = (uint)(table[leaf] << 1);
+ if (((pos >> (int)(15 - fill)) & 1) != 0)
+ leaf++;
+ }
+
+ table[leaf] = sym;
+ pos += bit_mask;
+ }
+
+ bit_mask >>= 1;
+ }
+
+ // Full table?
+ return (pos == table_mask) ? 0 : 1;
+ }
+
+ #endregion
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/DES.cs b/BurnOutSharp/External/libmspack/Compression/DES.cs
new file mode 100644
index 00000000..46e49ed9
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/DES.cs
@@ -0,0 +1,15 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.Compression
+{
+ public class des
+ {
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/Enums.cs b/BurnOutSharp/External/libmspack/Compression/Enums.cs
new file mode 100644
index 00000000..5f6a6b56
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/Enums.cs
@@ -0,0 +1,114 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.Compression
+{
+ public enum InflateErrorCode
+ {
+ INF_ERR_OK = 0,
+
+ ///
+ /// Unknown block type
+ ///
+ INF_ERR_BLOCKTYPE = -1,
+
+ ///
+ /// Block size complement mismatch
+ ///
+ INF_ERR_COMPLEMENT = -2,
+
+ ///
+ /// Error from flush_window callback
+ ///
+ INF_ERR_FLUSH = -3,
+
+ ///
+ /// Too many bits in bit buffer
+ ///
+ INF_ERR_BITBUF = -4,
+
+ ///
+ /// Too many symbols in blocktype 2 header
+ ///
+ INF_ERR_SYMLENS = -5,
+
+ ///
+ /// Failed to build bitlens huffman table
+ ///
+ INF_ERR_BITLENTBL = -6,
+
+ ///
+ /// Failed to build literals huffman table
+ ///
+ INF_ERR_LITERALTBL = -7,
+
+ ///
+ /// Failed to build distance huffman table
+ ///
+ INF_ERR_DISTANCETBL = -8,
+
+ ///
+ /// Bitlen RLE code goes over table size
+ ///
+ INF_ERR_BITOVERRUN = -9,
+
+ ///
+ /// Invalid bit-length code
+ ///
+ INF_ERR_BADBITLEN = -10,
+
+ ///
+ /// Out-of-range literal code
+ ///
+ INF_ERR_LITCODE = -11,
+
+ ///
+ /// Out-of-range distance code
+ ///
+ INF_ERR_DISTCODE = -12,
+
+ ///
+ /// Somehow, distance is beyond 32k
+ ///
+ INF_ERR_DISTANCE = -13,
+
+ ///
+ /// Out of bits decoding huffman symbol
+ ///
+ INF_ERR_HUFFSYM = -14,
+ }
+
+ public enum LZSSMode
+ {
+ LZSS_MODE_EXPAND = 0,
+
+ LZSS_MODE_MSHELP = 1,
+
+ LZSS_MODE_QBASIC = 2,
+ }
+
+ public enum LZXBlockType : byte
+ {
+ LZX_BLOCKTYPE_INVALID0 = 0,
+
+ LZX_BLOCKTYPE_VERBATIM = 1,
+
+ LZX_BLOCKTYPE_ALIGNED = 2,
+
+ LZX_BLOCKTYPE_UNCOMPRESSED = 3,
+
+ LZX_BLOCKTYPE_INVALID4 = 4,
+
+ LZX_BLOCKTYPE_INVALID5 = 5,
+
+ LZX_BLOCKTYPE_INVALID6 = 6,
+
+ LZX_BLOCKTYPE_INVALID7 = 7,
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/LZSS.cs b/BurnOutSharp/External/libmspack/Compression/LZSS.cs
new file mode 100644
index 00000000..a40a4071
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/LZSS.cs
@@ -0,0 +1,195 @@
+/* This file is part of libmspack.
+ * (C) 2003-2010 Stuart Caie.
+ *
+ * LZSS is a derivative of LZ77 and was created by James Storer and
+ * Thomas Szymanski in 1982. Haruhiko Okumura wrote a very popular C
+ * implementation.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.Compression
+{
+ public class LZSS
+ {
+ #region LZSS compression / decompression definitions
+
+ public const int LZSS_WINDOW_SIZE = 4096;
+ public const byte LZSS_WINDOW_FILL = 0x20;
+
+ #endregion
+
+ ///
+ /// Decompresses an LZSS stream.
+ ///
+ /// Input bytes will be read in as necessary using the system.read()
+ /// function with the input file handle given.This will continue until
+ /// system.read() returns 0 bytes, or an error.Errors will be passed
+ /// out of the function as MSPACK_ERR_READ errors. Input streams should
+ /// convey an "end of input stream" by refusing to supply all the bytes
+ /// that LZSS asks for when they reach the end of the stream, rather
+ /// than return an error code.
+ ///
+ /// Output bytes will be passed to the system.write() function, using
+ /// the output file handle given.More than one call may be made to
+ /// system.write().
+ ///
+ /// As EXPAND.EXE (SZDD/KWAJ), Microsoft Help and QBasic have slightly
+ /// different encodings for the control byte and matches, a "mode"
+ /// parameter is allowed, to choose the encoding.
+ ///
+ ///
+ /// an mspack_system structure used to read from
+ /// the input stream and write to the output
+ /// stream, also to allocate and free memory.
+ ///
+ /// an input stream with the LZSS data.
+ /// an output stream to write the decoded data to.
+ ///
+ /// the number of bytes to use as an input
+ /// bitstream buffer.
+ ///
+ /// one of LZSSMode values
+ /// an error code, or MSPACK_ERR_OK if successful
+ public static Error Decompress(SystemImpl system, object input, object output, int inputBufferSize, LZSSMode mode)
+ {
+ uint i, c, mpos, len;
+ int read;
+
+ // Check parameters
+ if (system == null
+ || inputBufferSize < 1
+ || (mode != LZSSMode.LZSS_MODE_EXPAND && mode != LZSSMode.LZSS_MODE_MSHELP && mode != LZSSMode.LZSS_MODE_QBASIC))
+ {
+ return Error.MSPACK_ERR_ARGS;
+ }
+
+ // Allocate memory
+ byte[] window = new byte[LZSS_WINDOW_SIZE + inputBufferSize];
+
+ // Initialise decompression
+ int inbuf = LZSS_WINDOW_SIZE;
+ for (i = 0; i < LZSS_WINDOW_SIZE; i++)
+ {
+ window[i] = LZSS_WINDOW_FILL;
+ }
+
+ uint pos = LZSS_WINDOW_SIZE - (uint)((mode == LZSSMode.LZSS_MODE_QBASIC) ? 18 : 16);
+ uint invert = (uint)((mode == LZSSMode.LZSS_MODE_MSHELP) ? ~0 : 0);
+ int iPtr = 0, iEnd = 0;
+
+ // Loop forever; exit condition is in ENSURE_BYTES macro
+ for (; ; )
+ {
+ //ENSURE_BYTES
+ if (iPtr >= iEnd)
+ {
+ read = system.Read(input, window, inbuf, inputBufferSize);
+ if (read <= 0)
+ {
+ system.Free(window);
+ return (read < 0) ? Error.MSPACK_ERR_READ : Error.MSPACK_ERR_OK;
+ }
+
+ iPtr = 0;
+ iEnd = read;
+ }
+
+ c = window[iPtr++] ^ invert;
+ for (i = 0x01; (i & 0xFF) != 0; i <<= 1)
+ {
+ if (c != 0 & i != 0)
+ {
+ // Literal
+ //ENSURE_BYTES
+ if (iPtr >= iEnd)
+ {
+ read = system.Read(input, window, inbuf, inputBufferSize);
+ if (read <= 0)
+ {
+ system.Free(window);
+ return (read < 0) ? Error.MSPACK_ERR_READ : Error.MSPACK_ERR_OK;
+ }
+
+ iPtr = 0;
+ iEnd = read;
+ }
+
+ window[pos] = window[iPtr++];
+
+ //ENSURE_BYTES
+ if (iPtr >= iEnd)
+ {
+ read = system.Read(input, window, inbuf, inputBufferSize);
+ if (read <= 0)
+ {
+ system.Free(window);
+ return (read < 0) ? Error.MSPACK_ERR_READ : Error.MSPACK_ERR_OK;
+ }
+
+ iPtr = 0;
+ iEnd = read;
+ }
+
+ pos++; pos &= LZSS_WINDOW_SIZE - 1;
+ }
+ else
+ {
+ // Match
+ //ENSURE_BYTES
+ if (iPtr >= iEnd)
+ {
+ read = system.Read(input, window, inbuf, inputBufferSize);
+ if (read <= 0)
+ {
+ system.Free(window);
+ return (read < 0) ? Error.MSPACK_ERR_READ : Error.MSPACK_ERR_OK;
+ }
+
+ iPtr = 0;
+ iEnd = read;
+ }
+
+ mpos = window[iPtr++];
+
+ //ENSURE_BYTES
+ if (iPtr >= iEnd)
+ {
+ read = system.Read(input, window, inbuf, inputBufferSize);
+ if (read <= 0)
+ {
+ system.Free(window);
+ return (read < 0) ? Error.MSPACK_ERR_READ : Error.MSPACK_ERR_OK;
+ }
+
+ iPtr = 0;
+ iEnd = read;
+ }
+
+ mpos |= (uint)(window[iPtr] & 0xF0) << 4;
+ len = (uint)(window[iPtr++] & 0x0F) + 3;
+ while (len-- != 0)
+ {
+ window[pos] = window[mpos];
+
+ //WRITE_BYTE;
+ if (system.Write(output, window, (int)pos, 1) != 1)
+ {
+ system.Free(window);
+ return Error.MSPACK_ERR_WRITE;
+ }
+
+ pos++;
+ pos &= LZSS_WINDOW_SIZE - 1;
+ mpos++;
+ mpos &= LZSS_WINDOW_SIZE - 1;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/LZX.cs b/BurnOutSharp/External/libmspack/Compression/LZX.cs
new file mode 100644
index 00000000..1946c7c2
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/LZX.cs
@@ -0,0 +1,1147 @@
+/* This file is part of libmspack.
+ * (C) 2003-2013 Stuart Caie.
+ *
+ * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted
+ * by Microsoft Corporation.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+/* Microsoft's LZX document (in cab-sdk.exe) and their implementation
+ * of the com.ms.util.cab Java package do not concur.
+ *
+ * In the LZX document, there is a table showing the correlation between
+ * window size and the number of position slots. It states that the 1MB
+ * window = 40 slots and the 2MB window = 42 slots. In the implementation,
+ * 1MB = 42 slots, 2MB = 50 slots. The actual calculation is 'find the
+ * first slot whose position base is equal to or more than the required
+ * window size'. This would explain why other tables in the document refer
+ * to 50 slots rather than 42.
+ *
+ * The constant NUM_PRIMARY_LENGTHS used in the decompression pseudocode
+ * is not defined in the specification.
+ *
+ * The LZX document does not state the uncompressed block has an
+ * uncompressed length field. Where does this length field come from, so
+ * we can know how large the block is? The implementation has it as the 24
+ * bits following after the 3 blocktype bits, before the alignment
+ * padding.
+ *
+ * The LZX document states that aligned offset blocks have their aligned
+ * offset huffman tree AFTER the main and length trees. The implementation
+ * suggests that the aligned offset tree is BEFORE the main and length
+ * trees.
+ *
+ * The LZX document decoding algorithm states that, in an aligned offset
+ * block, if an extra_bits value is 1, 2 or 3, then that number of bits
+ * should be read and the result added to the match offset. This is
+ * correct for 1 and 2, but not 3, where just a huffman symbol (using the
+ * aligned tree) should be read.
+ *
+ * Regarding the E8 preprocessing, the LZX document states 'No translation
+ * may be performed on the last 6 bytes of the input block'. This is
+ * correct. However, the pseudocode provided checks for the *E8 leader*
+ * up to the last 6 bytes. If the leader appears between -10 and -7 bytes
+ * from the end, this would cause the next four bytes to be modified, at
+ * least one of which would be in the last 6 bytes, which is not allowed
+ * according to the spec.
+ *
+ * The specification states that the huffman trees must always contain at
+ * least one element. However, many CAB files contain blocks where the
+ * length tree is completely empty (because there are no matches), and
+ * this is expected to succeed.
+ *
+ * The errors in LZX documentation appear have been corrected in the
+ * new documentation for the LZX DELTA format.
+ *
+ * http://msdn.microsoft.com/en-us/library/cc483133.aspx
+ *
+ * However, this is a different format, an extension of regular LZX.
+ * I have noticed the following differences, there may be more:
+ *
+ * The maximum window size has increased from 2MB to 32MB. This also
+ * increases the maximum number of position slots, etc.
+ *
+ * If the match length is 257 (the maximum possible), this signals
+ * a further length decoding step, that allows for matches up to
+ * 33024 bytes long.
+ *
+ * The format now allows for "reference data", supplied by the caller.
+ * If match offsets go further back than the number of bytes
+ * decompressed so far, that is them accessing the reference data.
+ */
+
+using System;
+
+namespace LibMSPackSharp.Compression
+{
+ public class LZX
+ {
+ #region LZX compression / decompression definitions
+
+ // Some constants defined by the LZX specification
+ public const int LZX_MIN_MATCH = 2;
+ public const int LZX_MAX_MATCH = 257;
+ public const int LZX_NUM_CHARS = 256;
+
+ public const int LZX_PRETREE_NUM_ELEMENTS = 20;
+ public const int LZX_ALIGNED_NUM_ELEMENTS = 8; // Aligned offset tree #elements
+ public const int LZX_NUM_PRIMARY_LENGTHS = 7; // This one missing from spec!
+ public const int LZX_NUM_SECONDARY_LENGTHS = 249; // Length tree #elements
+
+ // LZX huffman defines: tweak tablebits as desired
+
+ public const int LZX_PRETREE_MAXSYMBOLS = LZX_PRETREE_NUM_ELEMENTS;
+ public const int LZX_PRETREE_TABLEBITS = 6;
+ public const int LZX_MAINTREE_MAXSYMBOLS = LZX_NUM_CHARS + 290 * 8;
+ public const int LZX_MAINTREE_TABLEBITS = 12;
+ public const int LZX_LENGTH_MAXSYMBOLS = LZX_NUM_SECONDARY_LENGTHS + 1;
+ public const int LZX_LENGTH_TABLEBITS = 12;
+ public const int LZX_ALIGNED_MAXSYMBOLS = LZX_ALIGNED_NUM_ELEMENTS;
+ public const int LZX_ALIGNED_TABLEBITS = 7;
+ public const int LZX_LENTABLE_SAFETY = 64; // Table decoding overruns are allowed
+
+ public const int LZX_FRAME_SIZE = 32768; // The size of a frame in LZX
+
+ #endregion
+
+ #region LZX static data tables
+
+ /* LZX static data tables:
+ *
+ * LZX uses 'position slots' to represent match offsets. For every match,
+ * a small 'position slot' number and a small offset from that slot are
+ * encoded instead of one large offset.
+ *
+ * The number of slots is decided by how many are needed to encode the
+ * largest offset for a given window size. This is easy when the gap between
+ * slots is less than 128Kb, it's a linear relationship. But when extra_bits
+ * reaches its limit of 17 (because LZX can only ensure reading 17 bits of
+ * data at a time), we can only jump 128Kb at a time and have to start
+ * using more and more position slots as each window size doubles.
+ *
+ * position_base[] is an index to the position slot bases
+ *
+ * extra_bits[] states how many bits of offset-from-base data is needed.
+ *
+ * They are calculated as follows:
+ * extra_bits[i] = 0 where i < 4
+ * extra_bits[i] = floor(i/2)-1 where i >= 4 && i < 36
+ * extra_bits[i] = 17 where i >= 36
+ * position_base[0] = 0
+ * position_base[i] = position_base[i-1] + (1 << extra_bits[i-1])
+ */
+
+ private static readonly uint[] position_slots = new uint[11]
+ {
+ 30, 32, 34, 36, 38, 42, 50, 66, 98, 162, 290
+ };
+
+ private static readonly byte[] extra_bits = new byte[36]
+ {
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
+ 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16
+ };
+
+ private static readonly uint[] position_base = new uint[290]
+ {
+ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512,
+ 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768,
+ 49152, 65536, 98304, 131072, 196608, 262144, 393216, 524288, 655360,
+ 786432, 917504, 1048576, 1179648, 1310720, 1441792, 1572864, 1703936,
+ 1835008, 1966080, 2097152, 2228224, 2359296, 2490368, 2621440, 2752512,
+ 2883584, 3014656, 3145728, 3276800, 3407872, 3538944, 3670016, 3801088,
+ 3932160, 4063232, 4194304, 4325376, 4456448, 4587520, 4718592, 4849664,
+ 4980736, 5111808, 5242880, 5373952, 5505024, 5636096, 5767168, 5898240,
+ 6029312, 6160384, 6291456, 6422528, 6553600, 6684672, 6815744, 6946816,
+ 7077888, 7208960, 7340032, 7471104, 7602176, 7733248, 7864320, 7995392,
+ 8126464, 8257536, 8388608, 8519680, 8650752, 8781824, 8912896, 9043968,
+ 9175040, 9306112, 9437184, 9568256, 9699328, 9830400, 9961472, 10092544,
+ 10223616, 10354688, 10485760, 10616832, 10747904, 10878976, 11010048,
+ 11141120, 11272192, 11403264, 11534336, 11665408, 11796480, 11927552,
+ 12058624, 12189696, 12320768, 12451840, 12582912, 12713984, 12845056,
+ 12976128, 13107200, 13238272, 13369344, 13500416, 13631488, 13762560,
+ 13893632, 14024704, 14155776, 14286848, 14417920, 14548992, 14680064,
+ 14811136, 14942208, 15073280, 15204352, 15335424, 15466496, 15597568,
+ 15728640, 15859712, 15990784, 16121856, 16252928, 16384000, 16515072,
+ 16646144, 16777216, 16908288, 17039360, 17170432, 17301504, 17432576,
+ 17563648, 17694720, 17825792, 17956864, 18087936, 18219008, 18350080,
+ 18481152, 18612224, 18743296, 18874368, 19005440, 19136512, 19267584,
+ 19398656, 19529728, 19660800, 19791872, 19922944, 20054016, 20185088,
+ 20316160, 20447232, 20578304, 20709376, 20840448, 20971520, 21102592,
+ 21233664, 21364736, 21495808, 21626880, 21757952, 21889024, 22020096,
+ 22151168, 22282240, 22413312, 22544384, 22675456, 22806528, 22937600,
+ 23068672, 23199744, 23330816, 23461888, 23592960, 23724032, 23855104,
+ 23986176, 24117248, 24248320, 24379392, 24510464, 24641536, 24772608,
+ 24903680, 25034752, 25165824, 25296896, 25427968, 25559040, 25690112,
+ 25821184, 25952256, 26083328, 26214400, 26345472, 26476544, 26607616,
+ 26738688, 26869760, 27000832, 27131904, 27262976, 27394048, 27525120,
+ 27656192, 27787264, 27918336, 28049408, 28180480, 28311552, 28442624,
+ 28573696, 28704768, 28835840, 28966912, 29097984, 29229056, 29360128,
+ 29491200, 29622272, 29753344, 29884416, 30015488, 30146560, 30277632,
+ 30408704, 30539776, 30670848, 30801920, 30932992, 31064064, 31195136,
+ 31326208, 31457280, 31588352, 31719424, 31850496, 31981568, 32112640,
+ 32243712, 32374784, 32505856, 32636928, 32768000, 32899072, 33030144,
+ 33161216, 33292288, 33423360
+ };
+
+ private static void ResetState(LZXDStream lzx)
+ {
+ int i;
+
+ lzx.R0 = 1;
+ lzx.R1 = 1;
+ lzx.R2 = 1;
+ lzx.HeaderRead = 0;
+ lzx.BlockRemaining = 0;
+ lzx.BlockType = LZXBlockType.LZX_BLOCKTYPE_INVALID0;
+
+ // Initialise tables to 0 (because deltas will be applied to them)
+ for (i = 0; i < LZX_MAINTREE_MAXSYMBOLS; i++)
+ {
+ lzx.MAINTREE_len[i] = 0;
+ }
+
+ for (i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)
+ {
+ lzx.LENGTH_len[i] = 0;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Allocates and initialises LZX decompression state for decoding an LZX
+ /// stream.
+ ///
+ /// This routine uses system.alloc() to allocate memory. If memory
+ /// allocation fails, or the parameters to this function are invalid,
+ /// null is returned.
+ ///
+ ///
+ /// an mspack_system structure used to read from
+ /// the input stream and write to the output
+ /// stream, also to allocate and free memory.
+ ///
+ /// an input stream with the LZX data.
+ /// an output stream to write the decoded data to.
+ ///
+ /// the size of the decoding window, which must be
+ /// between 15 and 21 inclusive for regular LZX
+ /// data, or between 17 and 25 inclusive for
+ /// LZX DELTA data.
+ ///
+ /// the interval at which the LZX bitstream is
+ /// reset, in multiples of LZX frames (32678
+ /// bytes), e.g. a value of 2 indicates the input
+ /// stream resets after every 65536 output bytes.
+ /// A value of 0 indicates that the bitstream never
+ /// resets, such as in CAB LZX streams.
+ ///
+ ///
+ /// the number of bytes to use as an input
+ /// bitstream buffer.
+ ///
+ ///
+ /// the length in bytes of the entirely
+ /// decompressed output stream, if known in
+ /// advance. It is used to correctly perform the
+ /// Intel E8 transformation, which must stop 6
+ /// bytes before the very end of the
+ /// decompressed stream. It is not otherwise used
+ /// or adhered to. If the full decompressed
+ /// length is known in advance, set it here.
+ /// If it is NOT known, use the value 0, and call
+ /// lzxd_set_outputLength() once it is
+ /// known. If never set, 4 of the final 6 bytes
+ /// of the output stream may be incorrect.
+ ///
+ ///
+ /// should be zero for all regular LZX data,
+ /// non-zero for LZX DELTA encoded data.
+ ///
+ ///
+ /// a pointer to an initialised LZXDStream structure, or null if
+ /// there was not enough memory or parameters to the function were wrong.
+ ///
+ public static LZXDStream Init(SystemImpl system, object input, object output, int windowBits, int resetInterval, int inputBufferSize, long outputLength, bool isDelta)
+ {
+ uint windowSize = (uint)(1 << windowBits);
+
+ if (system == null) return null;
+
+ // LZX DELTA window sizes are between 2^17 (128KiB) and 2^25 (32MiB),
+ // regular LZX windows are between 2^15 (32KiB) and 2^21 (2MiB)
+ if (isDelta)
+ {
+ if (windowBits < 17 || windowBits > 25)
+ return null;
+ }
+ else
+ {
+ if (windowBits < 15 || windowBits > 21)
+ return null;
+ }
+
+ if (resetInterval < 0 || outputLength < 0)
+ {
+ Console.WriteLine("reset interval or output length < 0");
+ return null;
+ }
+
+ // Round up input buffer size to multiple of two
+ inputBufferSize = (inputBufferSize + 1) & -2;
+ if (inputBufferSize < 2)
+ return null;
+
+ // Allocate decompression state
+ LZXDStream lzx = new LZXDStream();
+
+ // Allocate decompression window and input buffer
+ lzx.Window = new byte[windowSize];
+ lzx.InputBuffer = new byte[inputBufferSize];
+
+ // Initialise decompression state
+ lzx.Sys = system;
+ lzx.Input = input;
+ lzx.Output = output;
+ lzx.Offset = 0;
+ lzx.Length = outputLength;
+
+ lzx.InputBufferSize = (uint)inputBufferSize;
+ lzx.WindowSize = (uint)(1 << windowBits);
+ lzx.ReferenceDataSize = 0;
+ lzx.WindowPosition = 0;
+ lzx.FramePosition = 0;
+ lzx.Frame = 0;
+ lzx.ResetInterval = (uint)resetInterval;
+ lzx.IntelFileSize = 0;
+ lzx.IntelStarted = false;
+ lzx.Error = Error.MSPACK_ERR_OK;
+ lzx.NumOffsets = position_slots[windowBits - 15] << 3;
+ lzx.IsDelta = isDelta;
+
+ lzx.OutputPointer = lzx.OutputLength = 0;
+ ResetState(lzx);
+ lzx.INIT_BITS();
+ return lzx;
+ }
+
+ // See description of outputLength in lzxd_init()
+ public static void SetOutputLength(LZXDStream lzx, long outputLength)
+ {
+ if (lzx != null && outputLength > 0)
+ lzx.Length = outputLength;
+ }
+
+ ///
+ /// Reads LZX DELTA reference data into the window and allows
+ /// lzxd_decompress() to reference it.
+ ///
+ /// Call this before the first call to lzxd_decompress().
+ ///
+ /// the LZX stream to apply this reference data to
+ ///
+ /// an mspack_system implementation to use with the
+ /// input param. Only read() will be called.
+ ///
+ /// an input file handle to read reference data using system.read().
+ ///
+ /// the length of the reference data. Cannot be longer
+ /// than the LZX window size.
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ public static Error SetReferenceData(LZXDStream lzx, SystemImpl system, object input, uint length)
+ {
+ if (lzx == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ if (!lzx.IsDelta)
+ {
+ Console.WriteLine("only LZX DELTA streams support reference data");
+ return Error.MSPACK_ERR_ARGS;
+ }
+
+ if (lzx.Offset != 0)
+ {
+ Console.WriteLine("too late to set reference data after decoding starts");
+ return Error.MSPACK_ERR_ARGS;
+ }
+
+ if (length > lzx.WindowSize)
+ {
+ Console.WriteLine($"reference length ({length}) is longer than the window");
+ return Error.MSPACK_ERR_ARGS;
+ }
+
+ if (length > 0 && (system == null || input == null))
+ {
+ Console.WriteLine("length > 0 but no system or input");
+ return Error.MSPACK_ERR_ARGS;
+ }
+
+ lzx.ReferenceDataSize = length;
+ if (length > 0)
+ {
+ // Copy reference data
+ int pos = (int)(lzx.WindowSize - length);
+ int bytes = system.Read(input, lzx.Window, pos, (int)length);
+
+ // length can't be more than 2^25, so no signedness problem
+ if (bytes < (int)length)
+ return Error.MSPACK_ERR_READ;
+ }
+
+ lzx.ReferenceDataSize = length;
+ return Error.MSPACK_ERR_OK;
+ }
+
+ ///
+ /// Decompresses entire or partial LZX streams.
+ ///
+ /// The number of bytes of data that should be decompressed is given as the
+ /// out_bytes parameter. If more bytes are decoded than are needed, they
+ /// will be kept over for a later invocation.
+ ///
+ /// The output bytes will be passed to the system.write() function given in
+ /// lzxd_init(), using the output file handle given in lzxd_init(). More than
+ /// one call may be made to system.write().
+ /// Input bytes will be read in as necessary using the system.read()
+ /// function given in lzxd_init(), using the input file handle given in
+ /// lzxd_init(). This will continue until system.read() returns 0 bytes,
+ /// or an error. Errors will be passed out of the function as
+ /// MSPACK_ERR_READ errors. Input streams should convey an "end of input
+ /// stream" by refusing to supply all the bytes that LZX asks for when they
+ /// reach the end of the stream, rather than return an error code.
+ ///
+ /// If any error code other than MSPACK_ERR_OK is returned, the stream
+ /// should be considered unusable and lzxd_decompress() should not be
+ /// called again on this stream.
+ ///
+ /// LZX decompression state, as allocated by lzxd_init().
+ /// the number of bytes of data to decompress.
+ /// an error code, or MSPACK_ERR_OK if successful
+ public static Error Decompress(object o, long outBytes)
+ {
+ LZXDStream lzx = (LZXDStream)o;
+ if (lzx == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ // Bitstream and huffman reading variables
+ uint bit_buffer = 0, bits_left = 0;
+ int i_ptr = 0, i_end = 0;
+ ushort sym = 0;
+
+ int match_length, extra, verbatim_bits = 0, bytes_todo;
+ int this_run, j = 0, warned = 0;
+ uint main_element = 0, length_footer = 0, aligned_bits = 0;
+ byte[] window, buf = new byte[12];
+ int runsrc, rundest;
+ uint frame_size = 0, end_frame, match_offset, window_posn;
+ uint R0, R1, R2;
+
+ // Easy answers
+ if (lzx == null || (outBytes < 0))
+ return Error.MSPACK_ERR_ARGS;
+
+ if (lzx.Error != Error.MSPACK_ERR_OK)
+ return lzx.Error;
+
+ // Flush out any stored-up bytes before we begin
+ int i = lzx.OutputLength - lzx.OutputPointer;
+ if (i > outBytes)
+ i = (int)outBytes;
+
+ if (i != 0)
+ {
+ if (lzx.Sys.Write(lzx.Output, lzx.e8_buf, lzx.OutputPointer, i) != i)
+ return lzx.Error = Error.MSPACK_ERR_WRITE;
+
+ lzx.OutputPointer += i;
+ lzx.Offset += i;
+ outBytes -= i;
+ }
+ if (outBytes == 0) return Error.MSPACK_ERR_OK;
+
+ // Restore local state
+ lzx.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ window = lzx.Window;
+ window_posn = lzx.WindowPosition;
+ R0 = lzx.R0;
+ R1 = lzx.R1;
+ R2 = lzx.R2;
+
+ end_frame = (uint)((lzx.Offset + outBytes) / LZX_FRAME_SIZE) + 1;
+
+ while (lzx.Frame < end_frame)
+ {
+ // Have we reached the reset interval? (if there is one?)
+ if (lzx.ResetInterval != 0 && ((lzx.Frame % lzx.ResetInterval) == 0))
+ {
+ if (lzx.BlockRemaining != 0)
+ {
+ // This is a file format error, we can make a best effort to extract what we can
+ Console.WriteLine("%d bytes remaining at reset interval", lzx.BlockRemaining);
+ if (warned == 0)
+ {
+ lzx.Sys.Message(null, "WARNING; invalid reset interval detected during LZX decompression");
+ warned++;
+ }
+ }
+
+ // Re-read the intel header and reset the huffman lengths
+ ResetState(lzx);
+ R0 = lzx.R0;
+ R1 = lzx.R1;
+ R2 = lzx.R2;
+ }
+
+ // LZX DELTA format has chunk_size, not present in LZX format
+ if (lzx.IsDelta)
+ {
+ lzx.ENSURE_BITS(16, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ lzx.REMOVE_BITS(16, ref bits_left, ref bit_buffer);
+ }
+
+ // Read header if necessary
+ if (lzx.HeaderRead == 0)
+ {
+ /* read 1 bit. if bit=0, intel filesize = 0.
+ * if bit=1, read intel filesize (32 bits) */
+ j = 0;
+ lzx.READ_BITS(ref i, 1, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (i != 0)
+ {
+ lzx.READ_BITS(ref i, 16, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ lzx.READ_BITS(ref j, 16, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ }
+
+ lzx.IntelFileSize = (i << 16) | j;
+ lzx.HeaderRead = 1;
+ }
+
+ // Calculate size of frame: all frames are 32k except the final frame
+ // which is 32kb or less. this can only be calculated when lzx.Length
+ // has been filled in.
+ frame_size = LZX_FRAME_SIZE;
+ if (lzx.Length != 0 && (lzx.Length - lzx.Offset) < frame_size)
+ frame_size = (uint)(lzx.Length - lzx.Offset);
+
+ // Decode until one more frame is available
+ bytes_todo = (int)(lzx.FramePosition + frame_size - window_posn);
+ while (bytes_todo > 0)
+ {
+ // Initialise new block, if one is needed
+ if (lzx.BlockRemaining == 0)
+ {
+ // Realign if previous block was an odd-sized UNCOMPRESSED block
+ if ((lzx.BlockType == LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED) && (lzx.BlockLength & 1) != 0)
+ {
+ lzx.READ_IF_NEEDED(ref i_ptr, ref i_end);
+ if (lzx.Error != Error.MSPACK_ERR_OK)
+ return lzx.Error;
+
+ i_ptr++;
+ }
+
+ // Read block type (3 bits) and block length (24 bits)
+ int blockType = 0;
+ lzx.READ_BITS(ref blockType, 3, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ lzx.BlockType = (LZXBlockType)blockType;
+ lzx.READ_BITS(ref i, 16, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ lzx.READ_BITS(ref j, 8, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ lzx.BlockRemaining = lzx.BlockLength = (uint)((i << 8) | j);
+ // Console.WriteLine("new block t%d len %u", lzx.BlockType, lzx.BlockLength);
+
+ // Read individual block headers
+ switch (lzx.BlockType)
+ {
+ // Read lengths of and build aligned huffman decoding tree
+ case LZXBlockType.LZX_BLOCKTYPE_ALIGNED:
+ for (i = 0; i < 8; i++)
+ {
+ lzx.READ_BITS(ref j, 3, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ lzx.ALIGNED_len[i] = (byte)j;
+ }
+
+ //BUILD_TABLE(ALIGNED)
+ if (LZXDStream.MakeDecodeTable(LZX_ALIGNED_MAXSYMBOLS, LZX_ALIGNED_TABLEBITS, lzx.ALIGNED_len, lzx.ALIGNED_table) != 0)
+ {
+ Console.WriteLine("failed to build ALIGNED table");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // Read lengths of and build main huffman decoding tree
+
+ //READ_LENGTHS(tbl, first, last)
+ lzx.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ if (ReadLens(lzx, lzx.MAINTREE_len, 0, 256) != 0)
+ return lzx.Error;
+ lzx.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ //READ_LENGTHS(tbl, first, last)
+ lzx.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ if (ReadLens(lzx, lzx.MAINTREE_len, 256, LZX_NUM_CHARS + lzx.NumOffsets) != 0)
+ return lzx.Error;
+ lzx.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ //BUILD_TABLE(MAINTREE)
+ if (LZXDStream.MakeDecodeTable(LZX_MAINTREE_MAXSYMBOLS, LZX_MAINTREE_TABLEBITS, lzx.MAINTREE_len, lzx.MAINTREE_table) != 0)
+ {
+ Console.WriteLine("failed to build MAINTREE table");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // If the literal 0xE8 is anywhere in the block...
+ if (lzx.MAINTREE_len[0xE8] != 0)
+ lzx.IntelStarted = true;
+
+ // Read lengths of and build lengths huffman decoding tree
+
+ //READ_LENGTHS(tbl, first, last)
+ lzx.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ if (ReadLens(lzx, lzx.LENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS) != 0)
+ return lzx.Error;
+ lzx.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ //BUILD_TABLE_MAYBE_EMPTY(LENGTH)
+ lzx.LENGTH_empty = 0;
+ if (LZXDStream.MakeDecodeTable(LZX_LENGTH_MAXSYMBOLS, LZX_LENGTH_TABLEBITS, lzx.LENGTH_len, lzx.LENGTH_table) != 0)
+ {
+ for (i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)
+ {
+ if (lzx.LENGTH_len[i] > 0)
+ {
+ Console.WriteLine("failed to build LENGTH table");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+ }
+
+ // Empty tree - allow it, but don't decode symbols with it
+ lzx.LENGTH_empty = 1;
+ }
+
+ break;
+
+ case LZXBlockType.LZX_BLOCKTYPE_VERBATIM:
+ // Read lengths of and build main huffman decoding tree
+
+ //READ_LENGTHS(tbl, first, last)
+ lzx.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ if (ReadLens(lzx, lzx.MAINTREE_len, 0, 256) != 0)
+ return lzx.Error;
+ lzx.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ //READ_LENGTHS(tbl, first, last)
+ lzx.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ if (ReadLens(lzx, lzx.MAINTREE_len, 256, LZX_NUM_CHARS + lzx.NumOffsets) != 0)
+ return lzx.Error;
+ lzx.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ //BUILD_TABLE(MAINTREE)
+ if (LZXDStream.MakeDecodeTable(LZX_MAINTREE_MAXSYMBOLS, LZX_MAINTREE_TABLEBITS, lzx.MAINTREE_len, lzx.MAINTREE_table) != 0)
+ {
+ Console.WriteLine("failed to build MAINTREE table");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // If the literal 0xE8 is anywhere in the block...
+ if (lzx.MAINTREE_len[0xE8] != 0)
+ lzx.IntelStarted = true;
+
+ // Read lengths of and build lengths huffman decoding tree
+
+ //READ_LENGTHS(tbl, first, last)
+ lzx.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ if (ReadLens(lzx, lzx.LENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS) != 0)
+ return lzx.Error;
+ lzx.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ //BUILD_TABLE_MAYBE_EMPTY(LENGTH)
+ lzx.LENGTH_empty = 0;
+ if (LZXDStream.MakeDecodeTable(LZX_LENGTH_MAXSYMBOLS, LZX_LENGTH_TABLEBITS, lzx.LENGTH_len, lzx.LENGTH_table) != 0)
+ {
+ for (i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)
+ {
+ if (lzx.LENGTH_len[i] > 0)
+ {
+ Console.WriteLine("failed to build LENGTH table");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+ }
+
+ // Empty tree - allow it, but don't decode symbols with it
+ lzx.LENGTH_empty = 1;
+ }
+
+ break;
+
+ case LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED:
+ // Because we can't assume otherwise
+ lzx.IntelStarted = true;
+
+ // Read 1-16 (not 0-15) bits to align to bytes
+ if (bits_left == 0)
+ lzx.ENSURE_BITS(16, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+
+ bits_left = 0; bit_buffer = 0;
+
+ // Read 12 bytes of stored R0 / R1 / R2 values
+ for (rundest = 0, i = 0; i < 12; i++)
+ {
+ lzx.READ_IF_NEEDED(ref i_ptr, ref i_end);
+ if (lzx.Error != Error.MSPACK_ERR_OK)
+ return lzx.Error;
+
+ buf[rundest++] = lzx.InputBuffer[i_ptr++];
+ }
+
+ R0 = (uint)(buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24));
+ R1 = (uint)(buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24));
+ R2 = (uint)(buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24));
+
+ break;
+
+ default:
+ Console.WriteLine("bad block type");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+ }
+
+ // decode more of the block:
+ // run = min(what's available, what's needed)
+ this_run = (int)lzx.BlockRemaining;
+ if (this_run > bytes_todo)
+ this_run = bytes_todo;
+
+ // Assume we decode exactly this_run bytes, for now
+ bytes_todo -= this_run;
+ lzx.BlockRemaining -= (uint)this_run;
+
+ // Decode at least this_run bytes
+ switch (lzx.BlockType)
+ {
+ case LZXBlockType.LZX_BLOCKTYPE_ALIGNED:
+ case LZXBlockType.LZX_BLOCKTYPE_VERBATIM:
+ while (this_run > 0)
+ {
+ lzx.READ_HUFFSYM(lzx.MAINTREE_table, ref main_element, LZX_MAINTREE_TABLEBITS, lzx.MAINTREE_len, LZX_MAINTREE_MAXSYMBOLS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (main_element < LZX_NUM_CHARS)
+ {
+ // Literal: 0 to LZX_NUM_CHARS-1
+ window[window_posn++] = (byte)main_element;
+ this_run--;
+ }
+ else
+ {
+ // Match: LZX_NUM_CHARS + ((slot<<3) | length_header (3 bits))
+ main_element -= LZX_NUM_CHARS;
+
+ // Get match length
+ match_length = (int)(main_element & LZX_NUM_PRIMARY_LENGTHS);
+ if (match_length == LZX_NUM_PRIMARY_LENGTHS)
+ {
+ if (lzx.LENGTH_empty != 0)
+ {
+ Console.WriteLine("LENGTH symbol needed but tree is empty");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ lzx.READ_HUFFSYM(lzx.LENGTH_table, ref length_footer, LZX_LENGTH_TABLEBITS, lzx.LENGTH_len, LZX_LENGTH_MAXSYMBOLS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ match_length += (int)length_footer;
+ }
+
+ match_length += LZX_MIN_MATCH;
+
+ // Get match offset
+ switch ((match_offset = (uint)(main_element >> 3)))
+ {
+ case 0: match_offset = R0; break;
+ case 1: match_offset = R1; R1 = R0; R0 = match_offset; break;
+ case 2: match_offset = R2; R2 = R0; R0 = match_offset; break;
+ default:
+ if (lzx.BlockType == LZXBlockType.LZX_BLOCKTYPE_VERBATIM)
+ {
+ if (match_offset == 3)
+ {
+ match_offset = 1;
+ }
+ else
+ {
+ extra = (match_offset >= 36) ? 17 : extra_bits[match_offset];
+ lzx.READ_BITS(ref verbatim_bits, extra, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ match_offset = (uint)(position_base[match_offset] - 2 + verbatim_bits);
+ }
+ }
+
+ // LZX_BLOCKTYPE_ALIGNED
+ else
+ {
+ extra = (match_offset >= 36) ? 17 : extra_bits[match_offset];
+ match_offset = position_base[match_offset] - 2;
+ if (extra > 3)
+ {
+ // >3: verbatim and aligned bits
+ extra -= 3;
+ lzx.READ_BITS(ref verbatim_bits, extra, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ match_offset += (uint)(verbatim_bits << 3);
+ lzx.READ_HUFFSYM(lzx.ALIGNED_table, ref aligned_bits, LZX_ALIGNED_TABLEBITS, lzx.ALIGNED_len, LZX_ALIGNED_MAXSYMBOLS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ match_offset += aligned_bits;
+ }
+ else if (extra == 3)
+ {
+ // 3: aligned bits only
+ lzx.READ_HUFFSYM(lzx.ALIGNED_table, ref aligned_bits, LZX_ALIGNED_TABLEBITS, lzx.ALIGNED_len, LZX_ALIGNED_MAXSYMBOLS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ match_offset += aligned_bits;
+ }
+ else if (extra > 0)
+ {
+ // 1-2: verbatim bits only
+ lzx.READ_BITS(ref verbatim_bits, extra, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ match_offset += (uint)verbatim_bits;
+ }
+ else
+ {
+ // 0: not defined in LZX specification!
+ match_offset = 1;
+ }
+ }
+
+ // Update repeated offset LRU queue
+ R2 = R1; R1 = R0; R0 = match_offset;
+ break;
+ }
+
+ // LZX DELTA uses max match length to signal even longer match
+ if (match_length == LZX_MAX_MATCH && lzx.IsDelta)
+ {
+ int extra_len = 0;
+ lzx.ENSURE_BITS(3, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer); /* 4 entry huffman tree */
+ if (lzx.PEEK_BITS(1, bit_buffer) == 0)
+ {
+ lzx.REMOVE_BITS(1, ref bits_left, ref bit_buffer); /* '0' . 8 extra length bits */
+ lzx.READ_BITS(ref extra_len, 8, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ }
+ else if (lzx.PEEK_BITS(2, bit_buffer) == 2)
+ {
+ lzx.REMOVE_BITS(2, ref bits_left, ref bit_buffer); /* '10' . 10 extra length bits + 0x100 */
+ lzx.READ_BITS(ref extra_len, 10, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ extra_len += 0x100;
+ }
+ else if (lzx.PEEK_BITS(3, bit_buffer) == 6)
+ {
+ lzx.REMOVE_BITS(3, ref bits_left, ref bit_buffer); /* '110' . 12 extra length bits + 0x500 */
+ lzx.READ_BITS(ref extra_len, 12, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ extra_len += 0x500;
+ }
+ else
+ {
+ lzx.REMOVE_BITS(3, ref bits_left, ref bit_buffer); /* '111' . 15 extra length bits */
+ lzx.READ_BITS(ref extra_len, 15, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ }
+
+ match_length += extra_len;
+ }
+
+ if ((window_posn + match_length) > lzx.WindowSize)
+ {
+ Console.WriteLine("match ran over window wrap");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // Copy match
+ rundest = (int)window_posn;
+ i = match_length;
+
+ // Does match offset wrap the window?
+ if (match_offset > window_posn)
+ {
+ if (match_offset > lzx.Offset &&
+ (match_offset - window_posn) > lzx.ReferenceDataSize)
+ {
+ Console.WriteLine("match offset beyond LZX stream");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ /* j = length from match offset to end of window */
+ j = (int)(match_offset - window_posn);
+ if (j > (int)lzx.WindowSize)
+ {
+ Console.WriteLine("match offset beyond window boundaries");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ runsrc = (int)(lzx.WindowSize - j);
+ if (j < i)
+ {
+ // if match goes over the window edge, do two copy runs
+ i -= j;
+ while (j-- > 0)
+ {
+ window[rundest++] = window[runsrc++];
+ }
+
+ runsrc = 0;
+ }
+
+ while (i-- > 0)
+ {
+ window[rundest++] = window[runsrc++];
+ }
+ }
+ else
+ {
+ runsrc = (int)(rundest - match_offset);
+ while (i-- > 0)
+ {
+ window[rundest++] = window[runsrc++];
+ }
+ }
+
+ this_run -= match_length;
+ window_posn += (uint)match_length;
+ }
+ }
+
+ break;
+
+ case LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED:
+ // As this_run is limited not to wrap a frame, this also means it
+ // won't wrap the window (as the window is a multiple of 32k)
+ rundest = (int)window_posn;
+ window_posn += (uint)this_run;
+ while (this_run > 0)
+ {
+ if ((i = i_end - i_ptr) == 0)
+ {
+ lzx.READ_IF_NEEDED(ref i_ptr, ref i_end);
+ if (lzx.Error != Error.MSPACK_ERR_OK)
+ return lzx.Error;
+ }
+ else
+ {
+ if (i > this_run)
+ i = this_run;
+
+ lzx.Sys.Copy(lzx.InputBuffer, i_ptr, window, rundest, i);
+ rundest += i;
+ i_ptr += i;
+ this_run -= i;
+ }
+ }
+ break;
+
+ default:
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH; // Might as well
+ }
+
+ // Did the final match overrun our desired this_run length?
+ if (this_run < 0)
+ {
+ if ((uint)(-this_run) > lzx.BlockRemaining)
+ {
+ Console.WriteLine($"overrun went past end of block by {-this_run} ({lzx.BlockRemaining} remaining)");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ lzx.BlockRemaining -= (uint)-this_run;
+ }
+ }
+
+ // Streams don't extend over frame boundaries
+ if ((window_posn - lzx.FramePosition) != frame_size)
+ {
+ Console.WriteLine("decode beyond output frame limits! %d != %d", window_posn - lzx.FramePosition, frame_size);
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // Re-align input bitstream
+ if (bits_left > 0)
+ lzx.ENSURE_BITS(16, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+
+ if ((bits_left & 15) != 0)
+ lzx.REMOVE_BITS((int)(bits_left & 15), ref bits_left, ref bit_buffer);
+
+ // Check that we've used all of the previous frame first
+ if (lzx.OutputPointer != lzx.OutputLength)
+ {
+ Console.WriteLine($"{lzx.OutputLength - lzx.OutputPointer} avail bytes, new {frame_size} frame");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // Does this intel block _really_ need decoding?
+ if (lzx.IntelStarted && lzx.IntelFileSize != 0 && (lzx.Frame < 32768) && (frame_size > 10))
+ {
+ int data = 0;
+ int dataend = (int)(frame_size - 10);
+ int curpos = (int)lzx.Offset;
+ int filesize = lzx.IntelFileSize;
+ int abs_off, rel_off;
+
+ lzx.OutputPointer = data;
+ lzx.Sys.Copy(lzx.Window, (int)lzx.FramePosition, lzx.e8_buf, data, (int)frame_size);
+
+ while (data < dataend)
+ {
+ if (lzx.e8_buf[data++] != 0xE8)
+ {
+ curpos++;
+ continue;
+ }
+
+ abs_off = lzx.e8_buf[data + 0] | (lzx.e8_buf[data + 1] << 8) | (lzx.e8_buf[data + 2] << 16) | (lzx.e8_buf[data + 3] << 24);
+ if ((abs_off >= -curpos) && (abs_off < filesize))
+ {
+ rel_off = (abs_off >= 0) ? abs_off - curpos : abs_off + filesize;
+ lzx.e8_buf[data + 0] = (byte)rel_off;
+ lzx.e8_buf[data + 1] = (byte)(rel_off >> 8);
+ lzx.e8_buf[data + 2] = (byte)(rel_off >> 16);
+ lzx.e8_buf[data + 3] = (byte)(rel_off >> 24);
+ }
+
+ data += 4;
+ curpos += 5;
+ }
+ }
+ else
+ {
+ lzx.OutputPointer = (int)lzx.FramePosition;
+ }
+
+ lzx.OutputLength = (int)(lzx.OutputPointer + frame_size);
+
+ // Write a frame
+ i = (int)((outBytes < frame_size) ? outBytes : frame_size);
+ if (lzx.Sys.Write(lzx.Output, lzx.Window, lzx.OutputPointer, i) != i)
+ return lzx.Error = Error.MSPACK_ERR_WRITE;
+
+ lzx.OutputPointer += i;
+ lzx.Offset += i;
+ outBytes -= i;
+
+ // Advance frame start position
+ lzx.FramePosition += frame_size;
+ lzx.Frame++;
+
+ // Wrap window / frame position pointers
+ if (window_posn == lzx.WindowSize)
+ window_posn = 0;
+ if (lzx.FramePosition == lzx.WindowSize)
+ lzx.FramePosition = 0;
+
+ }
+
+ if (outBytes != 0)
+ {
+ Console.WriteLine("bytes left to output");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // Store local state
+ lzx.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ lzx.WindowPosition = window_posn;
+ lzx.R0 = R0;
+ lzx.R1 = R1;
+ lzx.R2 = R2;
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ ///
+ /// Frees all state associated with an LZX data stream. This will call
+ /// system.free() using the system pointer given in lzxd_init().
+ ///
+ /// LZX decompression state to free.
+ public static void Free(LZXDStream lzx)
+ {
+ if (lzx != null)
+ {
+ SystemImpl sys = lzx.Sys;
+ sys.Free(lzx.InputBuffer);
+ sys.Free(lzx.Window);
+ sys.Free(lzx);
+ }
+ }
+
+ private static Error ReadLens(LZXDStream lzx, byte[] lens, uint first, uint last)
+ {
+ // Bit buffer and huffman symbol decode variables
+ uint bit_buffer = 0, bits_left = 0;
+ int i = 0;
+ ushort sym = 0;
+ int i_ptr = 0, i_end = 0;
+
+ int x, y = 0;
+ uint z = 0;
+
+ lzx.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ // Read lengths for pretree (20 symbols, lengths stored in fixed 4 bits)
+ for (x = 0; x < 20; x++)
+ {
+ lzx.READ_BITS(ref y, 4, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ lzx.PRETREE_len[x] = (byte)y;
+ }
+
+ //BUILD_TABLE(PRETREE)
+ if (LZXDStream.MakeDecodeTable(LZX_PRETREE_MAXSYMBOLS, LZX_PRETREE_TABLEBITS, lzx.PRETREE_len, lzx.PRETREE_table) != 0)
+ {
+ Console.WriteLine("failed to build PRETREE table");
+ return lzx.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ for (x = (int)first; x < last;)
+ {
+ lzx.READ_HUFFSYM(lzx.PRETREE_table, ref z, LZX_PRETREE_TABLEBITS, lzx.PRETREE_len, LZX_PRETREE_MAXSYMBOLS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (z == 17)
+ {
+ // Code = 17, run of ([read 4 bits]+4) zeros
+ lzx.READ_BITS(ref y, 4, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left); y += 4;
+ while (y-- != 0)
+ {
+ lens[x++] = 0;
+ }
+ }
+ else if (z == 18)
+ {
+ // Code = 18, run of ([read 5 bits]+20) zeros
+ lzx.READ_BITS(ref y, 5, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left); y += 20;
+ while (y-- != 0)
+ {
+ lens[x++] = 0;
+ }
+ }
+ else if (z == 19)
+ {
+ // Code = 19, run of ([read 1 bit]+4) [read huffman symbol]
+ lzx.READ_BITS(ref y, 1, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left); y += 4;
+ lzx.READ_HUFFSYM(lzx.PRETREE_table, ref z, LZX_PRETREE_TABLEBITS, lzx.PRETREE_len, LZX_PRETREE_MAXSYMBOLS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ z = lens[x] - z;
+ if (z < 0)
+ z += 17;
+
+ while (y-- != 0)
+ {
+ lens[x++] = (byte)z;
+ }
+ }
+ else
+ {
+ // Code = 0 to 16, delta current length entry
+ z = lens[x] - z;
+ if (z < 0)
+ z += 17;
+
+ lens[x++] = (byte)z;
+ }
+ }
+
+ lzx.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+
+ return Error.MSPACK_ERR_OK;
+ }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/LZXDStream.cs b/BurnOutSharp/External/libmspack/Compression/LZXDStream.cs
new file mode 100644
index 00000000..8de76563
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/LZXDStream.cs
@@ -0,0 +1,159 @@
+/* This file is part of libmspack.
+ * (C) 2003-2013 Stuart Caie.
+ *
+ * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted
+ * by Microsoft Corporation.
+ *
+ * libmspack is free software { get; set; } you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.Compression
+{
+ public class LZXDStream : CompressionStream
+ {
+ ///
+ /// Number of bytes actually output
+ ///
+ public long Offset { get; set; }
+
+ ///
+ /// Overall decompressed length of stream
+ ///
+ public long Length { get; set; }
+
+ ///
+ /// Decoding window
+ ///
+ public byte[] Window { get; set; }
+
+ ///
+ /// Window size
+ ///
+ public uint WindowSize { get; set; }
+
+ ///
+ /// LZX DELTA reference data size
+ ///
+ public uint ReferenceDataSize { get; set; }
+
+ ///
+ /// Number of match_offset entries in table
+ ///
+ public uint NumOffsets { get; set; }
+
+ ///
+ /// Decompression offset within window
+ ///
+ public uint WindowPosition { get; set; }
+
+ ///
+ /// Current frame offset within in window
+ ///
+ public uint FramePosition { get; set; }
+
+ ///
+ /// The number of 32kb frames processed
+ ///
+ public uint Frame { get; set; }
+
+ ///
+ /// Which frame do we reset the compressor?
+ ///
+ public uint ResetInterval { get; set; }
+
+ ///
+ /// For the LRU offset system
+ ///
+ public uint R0 { get; set; }
+
+ ///
+ /// For the LRU offset system
+ ///
+ public uint R1 { get; set; }
+
+ ///
+ /// For the LRU offset system
+ ///
+ public uint R2 { get; set; }
+
+ ///
+ /// Uncompressed length of this LZX block
+ ///
+ public uint BlockLength { get; set; }
+
+ ///
+ /// Uncompressed bytes still left to decode
+ ///
+ public uint BlockRemaining { get; set; }
+
+ ///
+ /// Magic header value used for transform
+ ///
+ public int IntelFileSize { get; set; }
+
+ ///
+ /// Has intel E8 decoding started?
+ ///
+ public bool IntelStarted { get; set; }
+
+ ///
+ /// Type of the current block
+ ///
+ public LZXBlockType BlockType { get; set; }
+
+ ///
+ /// Have we started decoding at all yet?
+ ///
+ public byte HeaderRead { get; set; }
+
+ ///
+ /// Does stream follow LZX DELTA spec?
+ ///
+ public bool IsDelta { get; set; }
+
+ #region Huffman code lengths
+
+ public byte[] PRETREE_len { get; set; } = new byte[LZX.LZX_PRETREE_MAXSYMBOLS + LZX.LZX_LENTABLE_SAFETY];
+ public byte[] MAINTREE_len { get; set; } = new byte[LZX.LZX_MAINTREE_MAXSYMBOLS + LZX.LZX_LENTABLE_SAFETY];
+ public byte[] LENGTH_len { get; set; } = new byte[LZX.LZX_LENGTH_MAXSYMBOLS + LZX.LZX_LENTABLE_SAFETY];
+ public byte[] ALIGNED_len { get; set; } = new byte[LZX.LZX_ALIGNED_MAXSYMBOLS + LZX.LZX_LENTABLE_SAFETY];
+
+ #endregion
+
+ #region Huffman decoding tables
+
+ public ushort[] PRETREE_table { get; set; } = new ushort[(1 << LZX.LZX_PRETREE_TABLEBITS) + (LZX.LZX_PRETREE_MAXSYMBOLS * 2)];
+ public ushort[] MAINTREE_table { get; set; } = new ushort[(1 << LZX.LZX_MAINTREE_TABLEBITS) + (LZX.LZX_MAINTREE_MAXSYMBOLS * 2)];
+ public ushort[] LENGTH_table { get; set; } = new ushort[(1 << LZX.LZX_LENGTH_TABLEBITS) + (LZX.LZX_LENGTH_MAXSYMBOLS * 2)];
+ public ushort[] ALIGNED_table { get; set; } = new ushort[(1 << LZX.LZX_ALIGNED_TABLEBITS) + (LZX.LZX_ALIGNED_MAXSYMBOLS * 2)];
+
+ #endregion
+
+ public byte LENGTH_empty { get; set; }
+
+ // This is used purely for doing the intel E8 transform
+ public byte[] e8_buf { get; set; } = new byte[LZX.LZX_FRAME_SIZE];
+
+ public override Error READ_BYTES(ref int i_ptr, ref int i_end, ref uint bitsLeft, ref uint bitBuffer)
+ {
+ Error error = READ_IF_NEEDED(ref i_ptr, ref i_end);
+ if (error != Error.MSPACK_ERR_OK)
+ return error;
+
+ byte b0 = InputBuffer[i_ptr++];
+
+ error = READ_IF_NEEDED(ref i_ptr, ref i_end);
+ if (error != Error.MSPACK_ERR_OK)
+ return error;
+
+ byte b1 = InputBuffer[i_ptr++];
+ INJECT_BITS((uint)((b1 << 8) | b0), 16, ref bitsLeft, ref bitBuffer);
+ return Error.MSPACK_ERR_OK;
+ }
+
+ public override int HUFF_ERROR() => (int)(Error = Error.MSPACK_ERR_DECRUNCH);
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/MSZIP.cs b/BurnOutSharp/External/libmspack/Compression/MSZIP.cs
new file mode 100644
index 00000000..ad441606
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/MSZIP.cs
@@ -0,0 +1,727 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * The deflate method was created by Phil Katz. MSZIP is equivalent to the
+ * deflate method.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+
+namespace LibMSPackSharp.Compression
+{
+ public class MSZIP
+ {
+ #region MSZIP (deflate) compression / (inflate) decompression definitions
+
+ public const int MSZIP_FRAME_SIZE = 32768; // Size of LZ history window
+ public const int MSZIP_LITERAL_MAXSYMBOLS = 288; // literal/length huffman tree
+ public const int MSZIP_LITERAL_TABLEBITS = 9;
+ public const int MSZIP_DISTANCE_MAXSYMBOLS = 32; // Distance huffman tree
+ public const int MSZIP_DISTANCE_TABLEBITS = 6;
+
+ // If there are less direct lookup entries than symbols, the longer
+ // code pointers will be <= maxsymbols. This must not happen, or we
+ // will decode entries badly
+ public static int MSZIP_LITERAL_TABLESIZE
+ {
+ get
+ {
+ if ((1 << MSZIP_LITERAL_TABLEBITS) < (MSZIP_LITERAL_MAXSYMBOLS * 2))
+ return (MSZIP_LITERAL_MAXSYMBOLS * 4);
+ else
+ return ((1 << MSZIP_LITERAL_TABLEBITS) + (MSZIP_LITERAL_MAXSYMBOLS * 2));
+ }
+ }
+
+ public static int MSZIP_DISTANCE_TABLESIZE
+ {
+ get
+ {
+ if ((1 << MSZIP_DISTANCE_TABLEBITS) < (MSZIP_DISTANCE_MAXSYMBOLS * 2))
+ return (MSZIP_DISTANCE_MAXSYMBOLS * 4);
+ else
+ return ((1 << MSZIP_DISTANCE_TABLEBITS) + (MSZIP_DISTANCE_MAXSYMBOLS * 2));
+ }
+ }
+
+ ///
+ /// Match lengths for literal codes 257.. 285
+ ///
+ private static readonly ushort[] lit_lengths = new ushort[29]
+ {
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27,
+ 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
+ };
+
+ ///
+ /// Match offsets for distance codes 0 .. 29
+ ///
+ private static readonly ushort[] dist_offsets = new ushort[30]
+ {
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385,
+ 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577
+ };
+
+ ///
+ /// Extra bits required for literal codes 257.. 285
+ ///
+ private static readonly byte[] lit_extrabits = new byte[29]
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2,
+ 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
+ };
+
+ ///
+ /// Extra bits required for distance codes 0 .. 29
+ ///
+ private static readonly byte[] dist_extrabits = new byte[30]
+ {
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
+ 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
+ };
+
+ ///
+ /// The order of the bit length Huffman code lengths
+ ///
+ private static readonly byte[] bitlen_order = new byte[19]
+ {
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+ };
+
+ #endregion
+
+ ///
+ /// Allocates MS-ZIP decompression stream for decoding the given stream.
+ ///
+ /// - uses system.alloc() to allocate memory
+ ///
+ /// - returns null if not enough memory
+ ///
+ /// - input_buffer_size is how many bytes to use as an input bitstream buffer
+ ///
+ /// - if RepairMode is non-zero, errors in decompression will be skipped
+ /// and 'holes' left will be filled with zero bytes. This allows at least
+ /// a partial recovery of erroneous data.
+ ///
+ public static MSZIPDStream Init(SystemImpl system, object input, object output, int input_buffer_size, bool repair_mode)
+ {
+ if (system == null)
+ return null;
+
+ // Round up input buffer size to multiple of two
+ input_buffer_size = (input_buffer_size + 1) & -2;
+ if (input_buffer_size < 2)
+ return null;
+
+ // Allocate decompression state
+ MSZIPDStream zip = new MSZIPDStream();
+
+ // Allocate input buffer
+ zip.InputBuffer = new byte[input_buffer_size];
+
+ // Initialise decompression state
+ zip.Sys = system;
+ zip.Input = input;
+ zip.Output = output;
+ zip.InputBufferSize = (uint)input_buffer_size;
+ zip.InputEnd = 0;
+ zip.Error = Error.MSPACK_ERR_OK;
+ zip.RepairMode = repair_mode;
+ zip.FlushWindow = FlushWindow;
+
+ zip.InputPointer = zip.InputLength = 0;
+ zip.OutputPointer = zip.OutputLength = 0;
+ zip.BitBuffer = 0; zip.BitsLeft = 0;
+
+ return zip;
+ }
+
+ ///
+ /// Decompresses, or decompresses more of, an MS-ZIP stream.
+ ///
+ /// - out_bytes of data will be decompressed and the function will return
+ /// with an MSPACK_ERR_OK return code.
+ ///
+ /// - decompressing will stop as soon as out_bytes is reached. if the true
+ /// amount of bytes decoded spills over that amount, they will be kept for
+ /// a later invocation of mszipd_decompress().
+ ///
+ /// - the output bytes will be passed to the system.write() function given in
+ /// mszipd_init(), using the output file handle given in mszipd_init(). More
+ /// than one call may be made to system.write()
+ ///
+ /// - MS-ZIP will read input bytes as necessary using the system.read()
+ /// function given in mszipd_init(), using the input file handle given in
+ /// mszipd_init(). This will continue until system.read() returns 0 bytes,
+ /// or an error.
+ ///
+ public static Error Decompress(object o, long out_bytes)
+ {
+ MSZIPDStream zip = (MSZIPDStream)o;
+ if (zip == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ // For the bit buffer
+ uint bit_buffer = 0, bits_left = 0;
+ int i_ptr = 0, i_end = 0;
+
+ int i, state, error;
+
+ // Easy answers
+ if (zip == null || (out_bytes < 0))
+ return Error.MSPACK_ERR_ARGS;
+
+ if (zip.Error != Error.MSPACK_ERR_OK)
+ return zip.Error;
+
+ // Flush out any stored-up bytes before we begin
+ i = zip.OutputLength - zip.OutputPointer;
+ if (i > out_bytes)
+ i = (int)out_bytes;
+
+ if (i != 0)
+ {
+ if (zip.Sys.Write(zip.Output, zip.Window, zip.OutputPointer, i) != i)
+ return zip.Error = Error.MSPACK_ERR_WRITE;
+
+ zip.OutputPointer += i;
+ out_bytes -= i;
+ }
+
+ if (out_bytes == 0)
+ return Error.MSPACK_ERR_OK;
+
+ while (out_bytes > 0)
+ {
+ // Unpack another block
+ zip.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ // Skip to next read 'CK' header
+ i = (int)(bits_left & 7);
+ zip.REMOVE_BITS(i, ref bits_left, ref bit_buffer); // Align to bytestream
+ state = 0;
+
+ do
+ {
+ zip.READ_BITS(ref i, 8, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (i == 'C')
+ state = 1;
+ else if ((state == 1) && (i == 'K'))
+ state = 2;
+ else
+ state = 0;
+ } while (state != 2);
+
+ // Inflate a block, repair and realign if necessary
+ zip.WindowPosition = 0;
+ zip.BytesOutput = 0;
+ zip.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+
+ if ((error = (int)Inflate(zip)) != 0)
+ {
+ Console.WriteLine($"inflate error {error}");
+ if (zip.RepairMode)
+ {
+ // Recover partially-inflated buffers
+ if (zip.BytesOutput == 0 && zip.WindowPosition > 0)
+ zip.FlushWindow(zip, zip.WindowPosition);
+
+ zip.Sys.Message(null, $"MSZIP error, {MSZIP_FRAME_SIZE - zip.BytesOutput} bytes of data lost.");
+ for (i = zip.BytesOutput; i < MSZIP_FRAME_SIZE; i++)
+ {
+ zip.Window[i] = 0x00;
+ }
+
+ zip.BytesOutput = MSZIP_FRAME_SIZE;
+ }
+ else
+ {
+ return zip.Error = (error > 0) ? (Error)error : Error.MSPACK_ERR_DECRUNCH;
+ }
+ }
+ zip.OutputPointer = 0;
+ zip.OutputLength = zip.BytesOutput;
+
+ // Write a frame
+ i = (out_bytes < zip.BytesOutput) ? (int)out_bytes : zip.BytesOutput;
+ if (zip.Sys.Write(zip.Output, zip.Window, zip.OutputPointer, i) != i)
+ return zip.Error = Error.MSPACK_ERR_WRITE;
+
+ // mspack errors (i.e. read errors) are fatal and can't be recovered
+ if (error > 0 && zip.RepairMode)
+ return (Error)error;
+
+ zip.OutputPointer += i;
+ out_bytes -= i;
+ }
+
+ if (out_bytes != 0)
+ {
+ Console.WriteLine("bytes left to output");
+ return zip.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ ///
+ /// Decompresses an entire MS-ZIP stream in a KWAJ file. Acts very much
+ /// like mszipd_decompress(), but doesn't take an out_bytes parameter
+ ///
+ public static Error DecompressKWAJ(MSZIPDStream zip)
+ {
+ // For the bit buffer
+ uint bit_buffer = 0, bits_left = 0;
+ int i_ptr = 0, i_end = 0;
+
+ int i = 0, error, block_len = 0;
+
+ // Unpack blocks until block_len == 0
+ for (; ; )
+ {
+ zip.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ // Align to bytestream, read block_len
+ i = (int)(bits_left & 7);
+ zip.REMOVE_BITS(i, ref bits_left, ref bit_buffer);
+ zip.READ_BITS(ref block_len, 8, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ zip.READ_BITS(ref i, 8, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ block_len |= i << 8;
+
+ if (block_len == 0)
+ break;
+
+ // Read "CK" header
+ zip.READ_BITS(ref i, 8, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (i != 'C')
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ zip.READ_BITS(ref i, 8, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (i != 'K')
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ // Inflate block
+ zip.WindowPosition = 0;
+ zip.BytesOutput = 0;
+ zip.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ if ((error = (int)Inflate(zip)) != 0)
+ {
+ Console.WriteLine($"inflate error {error}");
+ return zip.Error = (error > 0) ? (Error)error : Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // Write inflated block
+ if (zip.Sys.Write(zip.Output, zip.Window, 0, zip.BytesOutput) != zip.BytesOutput)
+ return zip.Error = Error.MSPACK_ERR_WRITE;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ ///
+ /// Frees all stream associated with an MS-ZIP data stream
+ ///
+ /// - calls system.free() using the system pointer given in mszipd_init()
+ ///
+ public static void Free(MSZIPDStream zip)
+ {
+ if (zip != null)
+ {
+ SystemImpl sys = zip.Sys;
+ sys.Free(zip.InputBuffer);
+ sys.Free(zip);
+ }
+ }
+
+ private static InflateErrorCode ZipReadLens(MSZIPDStream zip)
+ {
+ // For the bit buffer and huffman decoding
+ uint bit_buffer = 0, bits_left = 0;
+ int i_ptr = 0, i_end = 0;
+
+ // Bitlen Huffman codes -- immediate lookup, 7 bit max code length
+ ushort[] bl_table = new ushort[1 << 7];
+ byte[] bl_len = new byte[19];
+
+ byte[] lens = new byte[MSZIP_LITERAL_MAXSYMBOLS + MSZIP_DISTANCE_MAXSYMBOLS];
+ int lit_codes = 0, dist_codes = 0, code, last_code = 0, bitlen_codes = 0, i, run = 0;
+
+ zip.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ // Read the number of codes
+ zip.READ_BITS(ref lit_codes, 5, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ lit_codes += 257;
+ zip.READ_BITS(ref dist_codes, 5, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ dist_codes += 1;
+ zip.READ_BITS(ref bitlen_codes, 4, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ bitlen_codes += 4;
+
+ if (lit_codes > MSZIP_LITERAL_MAXSYMBOLS)
+ return InflateErrorCode.INF_ERR_SYMLENS;
+ if (dist_codes > MSZIP_DISTANCE_MAXSYMBOLS)
+ return InflateErrorCode.INF_ERR_SYMLENS;
+
+ // Read in the bit lengths in their unusual order
+ for (i = 0; i < bitlen_codes; i++)
+ {
+ int blLenTemp = bl_len[bitlen_order[i]];
+ zip.READ_BITS(ref blLenTemp, 3, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ bl_len[bitlen_order[i]] = (byte)blLenTemp;
+ }
+
+ while (i < 19)
+ {
+ bl_len[bitlen_order[i++]] = 0;
+ }
+
+ // Create decoding table with an immediate lookup */
+ if (MSZIPDStream.MakeDecodeTable(19, 7, bl_len, bl_table) != 0)
+ return InflateErrorCode.INF_ERR_BITLENTBL;
+
+ // Read literal / distance code lengths
+ for (i = 0; i < (lit_codes + dist_codes); i++)
+ {
+ // Single-level huffman lookup
+ zip.ENSURE_BITS(7, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ code = bl_table[zip.PEEK_BITS(7, bit_buffer)];
+ zip.REMOVE_BITS(bl_len[code], ref bits_left, ref bit_buffer);
+
+ if (code < 16)
+ {
+ lens[i] = (byte)(last_code = code);
+ }
+ else
+ {
+ switch (code)
+ {
+ case 16:
+ zip.READ_BITS(ref run, 2, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ run += 3;
+ code = last_code;
+ break;
+
+ case 17:
+ zip.READ_BITS(ref run, 3, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ run += 3;
+ code = 0;
+ break;
+
+ case 18:
+ zip.READ_BITS(ref run, 7, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ run += 11;
+ code = 0;
+ break;
+
+ default:
+ Console.WriteLine($"bad code!: {code}");
+ return InflateErrorCode.INF_ERR_BADBITLEN;
+ }
+
+ if ((i + run) > (lit_codes + dist_codes))
+ return InflateErrorCode.INF_ERR_BITOVERRUN;
+
+ while (run-- != 0)
+ {
+ lens[i++] = (byte)code;
+ }
+
+ i--;
+ }
+ }
+
+ // Copy LITERAL code lengths and clear any remaining
+ i = lit_codes;
+ zip.Sys.Copy(lens, 0, zip.LITERAL_len, 0, i);
+ while (i < MSZIP_LITERAL_MAXSYMBOLS)
+ {
+ zip.LITERAL_len[i++] = 0;
+ }
+
+ i = dist_codes;
+ zip.Sys.Copy(lens, lit_codes, zip.DISTANCE_len, 0, i);
+ while (i < MSZIP_DISTANCE_MAXSYMBOLS)
+ {
+ zip.DISTANCE_len[i++] = 0;
+ }
+
+ zip.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ return 0;
+ }
+
+ ///
+ /// A clean implementation of RFC 1951 / inflate
+ ///
+ private static InflateErrorCode Inflate(MSZIPDStream zip)
+ {
+ int last_block = 0, block_type = 0, distance = 0, length = 0, this_run, i;
+
+ // For the bit buffer and huffman decoding
+ uint bit_buffer = 0, bits_left = 0;
+ ushort sym = 0;
+ int i_ptr = 0, i_end = 0;
+
+ zip.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ do
+ {
+ // Read in last block bit
+ zip.READ_BITS(ref last_block, 1, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ // Read in block type
+ zip.READ_BITS(ref block_type, 2, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+
+ if (block_type == 0)
+ {
+ // Uncompressed block
+ byte[] lens_buf = new byte[4];
+
+ // Go to byte boundary
+ i = (int)(bits_left & 7);
+ zip.REMOVE_BITS(i, ref bits_left, ref bit_buffer);
+
+ // Read 4 bytes of data, emptying the bit-buffer if necessary
+ for (i = 0; (bits_left >= 8); i++)
+ {
+ if (i == 4)
+ return InflateErrorCode.INF_ERR_BITBUF;
+
+ lens_buf[i] = (byte)zip.PEEK_BITS(8, bit_buffer);
+ zip.REMOVE_BITS(8, ref bits_left, ref bit_buffer);
+ }
+ if (bits_left != 0) return InflateErrorCode.INF_ERR_BITBUF;
+ while (i < 4)
+ {
+ zip.READ_IF_NEEDED(ref i_ptr, ref i_end);
+ lens_buf[i++] = zip.InputBuffer[i_ptr++];
+ }
+
+ // Get the length and its complement
+ length = lens_buf[0] | (lens_buf[1] << 8);
+ i = lens_buf[2] | (lens_buf[3] << 8);
+ if (length != (~i & 0xFFFF))
+ return InflateErrorCode.INF_ERR_COMPLEMENT;
+
+ // Read and copy the uncompressed data into the window
+ while (length > 0)
+ {
+ zip.READ_IF_NEEDED(ref i_ptr, ref i_end);
+
+ this_run = length;
+ if (this_run > (uint)(i_end - i_ptr)) this_run = i_end - i_ptr;
+ if (this_run > (MSZIP_FRAME_SIZE - zip.WindowPosition))
+ this_run = (int)(MSZIP_FRAME_SIZE - zip.WindowPosition);
+
+ zip.Sys.Copy(zip.InputBuffer, i_ptr, zip.Window, (int)zip.WindowPosition, this_run);
+ zip.WindowPosition += (uint)this_run;
+ i_ptr += this_run;
+ length -= this_run;
+
+ // FLUSH_IF_NEEDED
+ if (zip.WindowPosition == MSZIP_FRAME_SIZE)
+ {
+ if (zip.FlushWindow(zip, MSZIP_FRAME_SIZE) != Error.MSPACK_ERR_OK)
+ return InflateErrorCode.INF_ERR_FLUSH;
+
+ zip.WindowPosition = 0;
+ }
+ }
+ }
+ else if ((block_type == 1) || (block_type == 2))
+ {
+ // Huffman-compressed LZ77 block
+ uint match_posn, code = 0;
+
+ if (block_type == 1)
+ {
+ // Block with fixed Huffman codes
+ i = 0;
+ while (i < 144)
+ {
+ zip.LITERAL_len[i++] = 8;
+ }
+
+ while (i < 256)
+ {
+ zip.LITERAL_len[i++] = 9;
+ }
+
+ while (i < 280)
+ {
+ zip.LITERAL_len[i++] = 7;
+ }
+
+ while (i < 288)
+ {
+ zip.LITERAL_len[i++] = 8;
+ }
+
+ for (i = 0; i < 32; i++)
+ {
+ zip.DISTANCE_len[i] = 5;
+ }
+ }
+ else
+ {
+ // Block with dynamic Huffman codes
+ zip.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+
+ if ((i = (int)ZipReadLens(zip)) != 0)
+ return (InflateErrorCode)i;
+
+ zip.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ }
+
+ // Now huffman lengths are read for either kind of block,
+ // create huffman decoding tables
+ if (MSZIPDStream.MakeDecodeTable(MSZIP_LITERAL_MAXSYMBOLS, MSZIP_LITERAL_TABLEBITS, zip.LITERAL_len, zip.LITERAL_table) != 0)
+ return InflateErrorCode.INF_ERR_LITERALTBL;
+
+ if (MSZIPDStream.MakeDecodeTable(MSZIP_DISTANCE_MAXSYMBOLS, MSZIP_DISTANCE_TABLEBITS, zip.DISTANCE_len, zip.DISTANCE_table) != 0)
+ return InflateErrorCode.INF_ERR_DISTANCETBL;
+
+ // Decode forever until end of block code
+ for (; ; )
+ {
+ zip.READ_HUFFSYM(zip.LITERAL_table, ref code, MSZIP_LITERAL_TABLEBITS, zip.LITERAL_len, MSZIP_LITERAL_MAXSYMBOLS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (code < 256)
+ {
+ zip.Window[zip.WindowPosition++] = (byte)code;
+
+ // FLUSH_IF_NEEDED
+ if (zip.WindowPosition == MSZIP_FRAME_SIZE)
+ {
+ if (zip.FlushWindow(zip, MSZIP_FRAME_SIZE) != Error.MSPACK_ERR_OK)
+ return InflateErrorCode.INF_ERR_FLUSH;
+
+ zip.WindowPosition = 0;
+ }
+ }
+ else if (code == 256)
+ {
+ // END OF BLOCK CODE: loop break point
+ break;
+ }
+ else
+ {
+ code -= 257; // Codes 257-285 are matches
+ if (code >= 29)
+ return InflateErrorCode.INF_ERR_LITCODE; // Codes 286-287 are illegal
+
+ zip.READ_BITS_T(ref length, lit_extrabits[code], ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ length += lit_lengths[code];
+
+ zip.READ_HUFFSYM(zip.DISTANCE_table, ref code, MSZIP_DISTANCE_TABLEBITS, zip.DISTANCE_len, MSZIP_DISTANCE_MAXSYMBOLS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (code >= 30)
+ return InflateErrorCode.INF_ERR_DISTCODE;
+
+ zip.READ_BITS_T(ref distance, dist_extrabits[code], ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ distance += dist_offsets[code];
+
+ // Match position is window position minus distance. If distance
+ // is more than window position numerically, it must 'wrap
+ // around' the frame size.
+ match_posn = (uint)(((distance > zip.WindowPosition) ? MSZIP_FRAME_SIZE : 0) + zip.WindowPosition - distance);
+
+ // Copy match
+ if (length < 12)
+ {
+ // Short match, use slower loop but no loop setup code
+ while (length-- != 0)
+ {
+ zip.Window[zip.WindowPosition++] = zip.Window[match_posn++];
+ match_posn &= MSZIP_FRAME_SIZE - 1;
+
+ // FLUSH_IF_NEEDED
+ if (zip.WindowPosition == MSZIP_FRAME_SIZE)
+ {
+ if (zip.FlushWindow(zip, MSZIP_FRAME_SIZE) != Error.MSPACK_ERR_OK)
+ return InflateErrorCode.INF_ERR_FLUSH;
+
+ zip.WindowPosition = 0;
+ }
+ }
+ }
+ else
+ {
+ // Longer match, use faster loop but with setup expense
+ int runsrc, rundest;
+ do
+ {
+ this_run = length;
+ if ((match_posn + this_run) > MSZIP_FRAME_SIZE)
+ this_run = MSZIP_FRAME_SIZE - (int)match_posn;
+
+ if ((zip.WindowPosition + this_run) > MSZIP_FRAME_SIZE)
+ this_run = MSZIP_FRAME_SIZE - (int)zip.WindowPosition;
+
+ rundest = (int)zip.WindowPosition;
+ zip.WindowPosition += (uint)this_run;
+ runsrc = (int)match_posn;
+ match_posn += (uint)this_run;
+ length -= this_run;
+
+ while (this_run-- != 0)
+ {
+ zip.Window[rundest++] = zip.Window[runsrc++];
+ }
+
+ if (match_posn == MSZIP_FRAME_SIZE)
+ match_posn = 0;
+
+ // FLUSH_IF_NEEDED
+ if (zip.WindowPosition == MSZIP_FRAME_SIZE)
+ {
+ if (zip.FlushWindow(zip, MSZIP_FRAME_SIZE) != Error.MSPACK_ERR_OK)
+ return InflateErrorCode.INF_ERR_FLUSH;
+
+ zip.WindowPosition = 0;
+ }
+ } while (length > 0);
+ }
+ }
+ }
+ }
+ else
+ {
+ // block_type == 3 -- bad block type
+ return InflateErrorCode.INF_ERR_BLOCKTYPE;
+ }
+ } while (last_block == 0);
+
+ // Flush the remaining data
+ if (zip.WindowPosition != 0)
+ {
+ if (zip.FlushWindow(zip, zip.WindowPosition) != Error.MSPACK_ERR_OK)
+ return InflateErrorCode.INF_ERR_FLUSH;
+ }
+
+ zip.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+
+ // Return success
+ return InflateErrorCode.INF_ERR_OK;
+ }
+
+ ///
+ /// inflate() calls this whenever the window should be flushed. As
+ /// MSZIP only expands to the size of the window, the implementation used
+ /// simply keeps track of the amount of data flushed, and if more than 32k
+ /// is flushed, an error is raised.
+ ///
+ private static Error FlushWindow(MSZIPDStream zip, uint data_flushed)
+ {
+ zip.BytesOutput += (int)data_flushed;
+ if (zip.BytesOutput > MSZIP_FRAME_SIZE)
+ {
+ Console.WriteLine($"overflow: {data_flushed} bytes flushed, total is now {zip.BytesOutput}");
+ return Error.MSPACK_ERR_ARGS;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/MSZIPDStream.cs b/BurnOutSharp/External/libmspack/Compression/MSZIPDStream.cs
new file mode 100644
index 00000000..d07c714b
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/MSZIPDStream.cs
@@ -0,0 +1,64 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * The deflate method was created by Phil Katz. MSZIP is equivalent to the
+ * deflate method.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+
+namespace LibMSPackSharp.Compression
+{
+ public class MSZIPDStream : CompressionStream
+ {
+ ///
+ /// 32kb history window
+ ///
+ public byte[] Window { get; set; } = new byte[MSZIP.MSZIP_FRAME_SIZE];
+
+ ///
+ /// Offset within window
+ ///
+ public uint WindowPosition { get; set; }
+
+ ///
+ /// inflate() will call this whenever the window should be emptied.
+ ///
+ public Func FlushWindow;
+
+ public bool RepairMode { get; set; }
+
+ public int BytesOutput { get; set; }
+
+ #region Huffman code lengths
+
+ public byte[] LITERAL_len { get; set; } = new byte[MSZIP.MSZIP_LITERAL_MAXSYMBOLS];
+ public byte[] DISTANCE_len { get; set; } = new byte[MSZIP.MSZIP_DISTANCE_MAXSYMBOLS];
+
+ #endregion
+
+ #region Huffman decoding tables
+
+ public ushort[] LITERAL_table { get; set; } = new ushort[MSZIP.MSZIP_LITERAL_TABLESIZE];
+ public ushort[] DISTANCE_table { get; set; } = new ushort[MSZIP.MSZIP_DISTANCE_TABLESIZE];
+
+ #endregion
+
+ public override Error READ_BYTES(ref int i_ptr, ref int i_end, ref uint bitsLeft, ref uint bitBuffer)
+ {
+ Error error = READ_IF_NEEDED(ref i_ptr, ref i_end);
+ if (error != Error.MSPACK_ERR_OK)
+ return error;
+
+ INJECT_BITS(InputBuffer[i_ptr++], 8, ref bitsLeft, ref bitBuffer);
+ return Error.MSPACK_ERR_OK;
+ }
+
+ public override int HUFF_ERROR() => (int)InflateErrorCode.INF_ERR_HUFFSYM;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/QTM.cs b/BurnOutSharp/External/libmspack/Compression/QTM.cs
new file mode 100644
index 00000000..d649355c
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/QTM.cs
@@ -0,0 +1,611 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * The Quantum method was created by David Stafford, adapted by Microsoft
+ * Corporation.
+ *
+ * This decompressor is based on an implementation by Matthew Russotto, used
+ * with permission.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+/* Quantum decompression implementation */
+
+/* This decompressor was researched and implemented by Matthew Russotto. It
+ * has since been tidied up by Stuart Caie. More information can be found at
+ * http://www.speakeasy.org/~russotto/quantumcomp.html
+ */
+
+using System;
+
+namespace LibMSPackSharp.Compression
+{
+ public class QTM
+ {
+ public const int QTM_FRAME_SIZE = 32768;
+
+ /* Quantum static data tables:
+ *
+ * Quantum uses 'position slots' to represent match offsets. For every
+ * match, a small 'position slot' number and a small offset from that slot
+ * are encoded instead of one large offset.
+ *
+ * position_base[] is an index to the position slot bases
+ *
+ * extra_bits[] states how many bits of offset-from-base data is needed.
+ *
+ * length_base[] and length_extra[] are equivalent in function, but are
+ * used for encoding selector 6 (variable length match) match lengths,
+ * instead of match offsets.
+ *
+ * They are generated with the following code:
+ * uint i, offset;
+ * for (i = 0, offset = 0; i < 42; i++) {
+ * position_base[i] = offset;
+ * extra_bits[i] = ((i < 2) ? 0 : (i - 2)) >> 1;
+ * offset += 1 << extra_bits[i];
+ * }
+ * for (i = 0, offset = 0; i < 26; i++) {
+ * length_base[i] = offset;
+ * length_extra[i] = (i < 2 ? 0 : i - 2) >> 2;
+ * offset += 1 << length_extra[i];
+ * }
+ * length_base[26] = 254; length_extra[26] = 0;
+ */
+
+ private static readonly uint[] position_base = new uint[42]
+ {
+ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768,
+ 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152,
+ 65536, 98304, 131072, 196608, 262144, 393216, 524288, 786432, 1048576, 1572864
+ };
+
+ private static readonly byte[] extra_bits = new byte[42]
+ {
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10,
+ 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19
+ };
+
+ private static readonly byte[] length_base = new byte[27]
+ {
+ 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 18, 22, 26,
+ 30, 38, 46, 54, 62, 78, 94, 110, 126, 158, 190, 222, 254
+ };
+
+ private static readonly byte[] length_extra = new byte[27]
+ {
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
+ };
+
+ ///
+ /// allocates Quantum decompression state for decoding the given stream.
+ ///
+ /// - returns null if window_bits is outwith the range 10 to 21 (inclusive).
+ /// - uses system.alloc() to allocate memory
+ /// - returns null if not enough memory
+ /// - window_bits is the size of the Quantum window, from 1Kb(10) to 2Mb(21).
+ /// - input_buffer_size is the number of bytes to use to store bitstream data.
+ ///
+ public static QTMDStream Init(SystemImpl system, object input, object output, int window_bits, int input_buffer_size)
+ {
+ uint window_size = (uint)(1 << window_bits);
+
+ if (system == null)
+ return null;
+
+ // Quantum supports window sizes of 2^10 (1Kb) through 2^21 (2Mb)
+ if (window_bits < 10 || window_bits > 21)
+ return null;
+
+ // Round up input buffer size to multiple of two
+ input_buffer_size = (input_buffer_size + 1) & -2;
+ if (input_buffer_size < 2) return null;
+
+ // Allocate decompression state
+ QTMDStream qtm = new QTMDStream();
+
+ // Allocate decompression window and input buffer
+ qtm.Window = new byte[window_size];
+ qtm.InputBuffer = new byte[input_buffer_size];
+
+ // Initialise decompression state
+ qtm.Sys = system;
+ qtm.Input = input;
+ qtm.Output = output;
+ qtm.InputBufferSize = (uint)input_buffer_size;
+ qtm.WindowSize = window_size;
+ qtm.WindowPosition = 0;
+ qtm.FrameTODO = QTM_FRAME_SIZE;
+ qtm.HeaderRead = 0;
+ qtm.Error = Error.MSPACK_ERR_OK;
+
+ qtm.InputPointer = qtm.InputLength = 0;
+ qtm.OutputPointer = qtm.OutputLength = 0;
+ qtm.InputEnd = 0;
+ qtm.BitsLeft = 0;
+ qtm.BitBuffer = 0;
+
+ // Initialise arithmetic coding models
+ // - model 4 depends on window size, ranges from 20 to 24
+ // - model 5 depends on window size, ranges from 20 to 36
+ // - model 6pos depends on window size, ranges from 20 to 42
+
+ int i = window_bits * 2;
+ InitModel(qtm.Model0, qtm.Model0Symbols, 0, 64);
+ InitModel(qtm.Model1, qtm.Model1Symbols, 64, 64);
+ InitModel(qtm.Model2, qtm.Model2Symbols, 128, 64);
+ InitModel(qtm.Model3, qtm.Model3Symbols, 192, 64);
+ InitModel(qtm.Model4, qtm.Model4Symbols, 0, (i > 24) ? 24 : i);
+ InitModel(qtm.Model5, qtm.Model5Symbols, 0, (i > 36) ? 36 : i);
+ InitModel(qtm.Model6, qtm.Model6Symbols, 0, i);
+ InitModel(qtm.Model6Len, qtm.Model6LenSymbols, 0, 27);
+ InitModel(qtm.Model7, qtm.Model7Symbols, 0, 7);
+
+ // All ok
+ return qtm;
+ }
+
+ ///
+ /// Decompresses, or decompresses more of, a Quantum stream.
+ ///
+ /// - out_bytes of data will be decompressed and the function will return
+ /// with an MSPACK_ERR_OK return code.
+ ///
+ /// - decompressing will stop as soon as out_bytes is reached. if the true
+ /// amount of bytes decoded spills over that amount, they will be kept for
+ /// a later invocation of qtmd_decompress().
+ ///
+ /// - the output bytes will be passed to the system.write() function given in
+ /// qtmd_init(), using the output file handle given in qtmd_init(). More
+ /// than one call may be made to system.write()
+ ///
+ /// - Quantum will read input bytes as necessary using the system.read()
+ /// function given in qtmd_init(), using the input file handle given in
+ /// qtmd_init(). This will continue until system.read() returns 0 bytes,
+ /// or an error.
+ ///
+ public static Error Decompress(object o, long out_bytes)
+ {
+ QTMDStream qtm = (QTMDStream)o;
+ if (qtm == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ uint frame_end, match_offset, range = 0, extra = 0;
+ int i_ptr = 0, i_end = 0, runsrc, rundest;
+ int i, j, selector = 0, sym = 0, match_length;
+ ushort symf = 0;
+
+ uint bit_buffer = 0, bits_left = 0;
+
+ // Easy answers
+ if (qtm == null || (out_bytes < 0))
+ return Error.MSPACK_ERR_ARGS;
+
+ if (qtm.Error != Error.MSPACK_ERR_OK)
+ return qtm.Error;
+
+ // Flush out any stored-up bytes before we begin
+ i = qtm.OutputLength - qtm.OutputPointer;
+ if (i > out_bytes)
+ i = (int)out_bytes;
+
+ if (i != 0)
+ {
+ if (qtm.Sys.Write(qtm.Output, qtm.Window, qtm.OutputPointer, i) != i)
+ return qtm.Error = Error.MSPACK_ERR_WRITE;
+
+ qtm.OutputPointer += i;
+ out_bytes -= i;
+ }
+
+ if (out_bytes == 0)
+ return Error.MSPACK_ERR_OK;
+
+ // Restore local state
+ qtm.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ byte[] window = qtm.Window;
+ uint window_posn = qtm.WindowPosition;
+ uint frame_todo = qtm.FrameTODO;
+
+ ushort high = qtm.High;
+ ushort low = qtm.Low;
+ ushort current = qtm.Current;
+
+ // While we do not have enough decoded bytes in reserve:
+ while ((qtm.OutputLength - qtm.OutputPointer) < out_bytes)
+ {
+ // Read header if necessary. Initialises H, L and C
+ if (qtm.HeaderRead == 0)
+ {
+ high = 0xFFFF;
+ low = 0;
+ int tempCurrent = current;
+ qtm.READ_BITS(ref tempCurrent, 16, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ current = (ushort)tempCurrent;
+ qtm.HeaderRead = 1;
+ }
+
+ // Decode more, up to the number of bytes needed, the frame boundary,
+ // or the window boundary, whichever comes first
+ frame_end = (uint)(window_posn + (out_bytes - (qtm.OutputLength - qtm.OutputPointer)));
+ if ((window_posn + frame_todo) < frame_end)
+ frame_end = window_posn + frame_todo;
+
+ if (frame_end > qtm.WindowSize)
+ frame_end = qtm.WindowSize;
+
+ while (window_posn < frame_end)
+ {
+ GET_SYMBOL(qtm, qtm.Model7, ref selector, ref range, ref symf, ref high, ref low, ref current, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (selector < 4)
+ {
+ // Literal byte
+ QTMDModel mdl;
+ switch (selector)
+ {
+ case 0: mdl = qtm.Model0; break;
+ case 1: mdl = qtm.Model1; break;
+ case 2: mdl = qtm.Model2; break;
+ default: mdl = qtm.Model3; break;
+ }
+
+ GET_SYMBOL(qtm, mdl, ref sym, ref range, ref symf, ref high, ref low, ref current, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ window[window_posn++] = (byte)sym;
+ frame_todo--;
+ }
+ else
+ {
+ // Match repeated string
+ switch (selector)
+ {
+ // Selector 4 = fixed length match (3 bytes)
+ case 4:
+ GET_SYMBOL(qtm, qtm.Model4, ref sym, ref range, ref symf, ref high, ref low, ref current, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ qtm.READ_MANY_BITS(ref extra, extra_bits[sym], ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ match_offset = position_base[sym] + extra + 1;
+ match_length = 3;
+ break;
+
+ // Selector 5 = fixed length match (4 bytes)
+ case 5:
+ GET_SYMBOL(qtm, qtm.Model5, ref sym, ref range, ref symf, ref high, ref low, ref current, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ qtm.READ_MANY_BITS(ref extra, extra_bits[sym], ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ match_offset = position_base[sym] + extra + 1;
+ match_length = 4;
+ break;
+
+ // Selector 6 = variable length match
+ case 6:
+ GET_SYMBOL(qtm, qtm.Model6Len, ref sym, ref range, ref symf, ref high, ref low, ref current, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ qtm.READ_MANY_BITS(ref extra, length_extra[sym], ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ match_length = (int)(length_base[sym] + extra + 5);
+
+ GET_SYMBOL(qtm, qtm.Model6, ref sym, ref range, ref symf, ref high, ref low, ref current, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ qtm.READ_MANY_BITS(ref extra, extra_bits[sym], ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ match_offset = position_base[sym] + extra + 1;
+ break;
+
+ default:
+ // Should be impossible, model7 can only return 0-6
+ Console.WriteLine("got %d from selector", selector);
+ return qtm.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ rundest = (int)window_posn;
+ frame_todo -= (uint)match_length;
+
+ // Does match destination wrap the window? This situation is possible
+ // where the window size is less than the 32k frame size, but matches
+ // must not go beyond a frame boundary */
+ if ((window_posn + match_length) > qtm.WindowSize)
+ {
+ /* copy first part of match, before window end */
+ i = (int)(qtm.WindowSize - window_posn);
+ j = (int)(window_posn - match_offset);
+ while (i-- != 0)
+ {
+ window[rundest++] = window[j++ & (qtm.WindowSize - 1)];
+ }
+
+ // Flush currently stored data
+ i = (int)(qtm.WindowSize - qtm.OutputPointer);
+
+ // This should not happen, but if it does then this code
+ // can't handle the situation (can't flush up to the end of
+ // the window, but can't break out either because we haven't
+ // finished writing the match). bail out in this case */
+ if (i > out_bytes)
+ {
+ Console.WriteLine($"during window-wrap match; {i} bytes to flush but only need {out_bytes}");
+ return qtm.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ if (qtm.Sys.Write(qtm.Output, qtm.Window, qtm.OutputPointer, i) != i)
+ return qtm.Error = Error.MSPACK_ERR_WRITE;
+
+ out_bytes -= i;
+ qtm.OutputPointer = 0;
+ qtm.OutputLength = 0;
+
+ // Copy second part of match, after window wrap
+ rundest = 0;
+ i = (int)(match_length - (qtm.WindowSize - window_posn));
+ while (i-- != 0)
+ {
+ window[rundest++] = window[j++ & (qtm.WindowSize - 1)];
+ }
+
+ window_posn = (uint)(window_posn + match_length - qtm.WindowSize);
+
+ break; // Because "window_posn < frame_end" has now failed
+ }
+ else
+ {
+ // Normal match - output won't wrap window or frame end
+ i = match_length;
+
+ // Does match _offset_ wrap the window?
+ if (match_offset > window_posn)
+ {
+ // j = length from match offset to end of window
+ j = (int)(match_offset - window_posn);
+ if (j > (int)qtm.WindowSize)
+ {
+ Console.WriteLine("match offset beyond window boundaries");
+ return qtm.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ runsrc = (int)(qtm.WindowSize - j);
+ if (j < i)
+ {
+ // If match goes over the window edge, do two copy runs
+ i -= j;
+ while (j-- > 0)
+ {
+ window[rundest++] = window[runsrc++];
+ }
+
+ runsrc = 0;
+ }
+
+ while (i-- > 0)
+ {
+ window[rundest++] = window[runsrc++];
+ }
+ }
+ else
+ {
+ runsrc = (int)(rundest - match_offset);
+ while (i-- > 0)
+ {
+ window[rundest++] = window[runsrc++];
+ }
+ }
+
+ window_posn += (uint)match_length;
+ }
+ }
+ }
+
+ qtm.OutputLength = (int)window_posn;
+
+ // If we subtracted too much from frame_todo, it will
+ // wrap around past zero and go above its max value */
+ if (frame_todo > QTM_FRAME_SIZE)
+ {
+ Console.WriteLine("overshot frame alignment");
+ return qtm.Error = Error.MSPACK_ERR_DECRUNCH;
+ }
+
+ // Another frame completed?
+ if (frame_todo == 0)
+ {
+ // Re-align input
+ if ((bits_left & 7) != 0)
+ qtm.REMOVE_BITS((int)bits_left & 7, ref bits_left, ref bit_buffer);
+
+ // Special Quantum hack -- cabd.c injects a trailer byte to allow the
+ // decompressor to realign itself. CAB Quantum blocks, unlike LZX
+ // blocks, can have anything from 0 to 4 trailing null bytes. */
+ do
+ {
+ qtm.READ_BITS(ref i, 8, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ } while (i != 0xFF);
+
+ qtm.HeaderRead = 0;
+
+ frame_todo = QTM_FRAME_SIZE;
+ }
+
+ // Window wrap?
+ if (window_posn == qtm.WindowSize)
+ {
+ // Flush all currently stored data
+ i = (qtm.OutputLength - qtm.OutputPointer);
+
+ // Break out if we have more than enough to finish this request
+ if (i >= out_bytes)
+ break;
+
+ if (qtm.Sys.Write(qtm.Output, qtm.Window, qtm.OutputPointer, i) != i)
+ return qtm.Error = Error.MSPACK_ERR_WRITE;
+
+ out_bytes -= i;
+ qtm.OutputPointer = 0;
+ qtm.OutputLength = 0;
+ window_posn = 0;
+ }
+ }
+
+ if (out_bytes != 0)
+ {
+ i = (int)out_bytes;
+ if (qtm.Sys.Write(qtm.Output, qtm.Window, qtm.OutputPointer, i) != i)
+ return qtm.Error = Error.MSPACK_ERR_WRITE;
+
+ qtm.OutputPointer += i;
+ }
+
+ // Store local state
+ qtm.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ qtm.WindowPosition = window_posn;
+ qtm.FrameTODO = frame_todo;
+ qtm.High = high;
+ qtm.Low = low;
+ qtm.Current = (ushort)current;
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ ///
+ /// Frees all state associated with a Quantum data stream
+ /// - calls system.free() using the system pointer given in qtmd_init()
+ ///
+ public static void Free(QTMDStream qtm)
+ {
+ if (qtm != null)
+ {
+ SystemImpl sys = qtm.Sys;
+ sys.Free(qtm.Window);
+ sys.Free(qtm.InputBuffer);
+ sys.Free(qtm);
+ }
+ }
+
+ ///
+ /// Arithmetic decoder:
+ ///
+ /// GET_SYMBOL(model, var) fetches the next symbol from the stated model
+ /// and puts it in var.
+ ///
+ /// If necessary, qtmd_update_model() is called.
+ ///
+ private static void GET_SYMBOL(QTMDStream qtm, QTMDModel model, ref int var, ref uint range, ref ushort symf, ref ushort high, ref ushort low, ref ushort current,
+ ref int i_ptr, ref int i_end, ref uint bit_buffer, ref uint bits_left)
+ {
+ range = (uint)(((high - low) & 0xFFFF) + 1);
+ symf = (ushort)(((((current - low + 1) * model.Syms[0].CumFreq) - 1) / range) & 0xFFFF);
+
+ int i;
+ for (i = 1; i < model.Entries; i++)
+ {
+ if (model.Syms[i].CumFreq <= symf)
+ break;
+ }
+
+ var = model.Syms[i - 1].Sym;
+
+ range = (ushort)((high - low) + 1);
+ symf = model.Syms[0].CumFreq;
+ high = (ushort)(low + ((model.Syms[i - 1].CumFreq * range) / symf) - 1);
+ low = (ushort)(low + ((model.Syms[i].CumFreq * range) / symf));
+
+ do
+ {
+ model.Syms[--i].CumFreq += 8;
+ } while (i > 0);
+
+ if (model.Syms[0].CumFreq > 3800)
+ UpdateModel(model);
+
+ while (true)
+ {
+ if ((low & 0x8000) != (high & 0x8000))
+ {
+ // Underflow case
+ if ((low & 0x4000) != 0 && (high & 0x4000) == 0)
+ {
+ current ^= 0x4000;
+ low &= 0x3FFF;
+ high |= 0x4000;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ low <<= 1;
+ high = (ushort)((high << 1) | 1);
+ qtm.ENSURE_BITS(1, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ current = (ushort)((current << 1) | qtm.PEEK_BITS(1, bit_buffer));
+ qtm.REMOVE_BITS(1, ref bits_left, ref bit_buffer);
+ }
+ }
+
+ private static void UpdateModel(QTMDModel model)
+ {
+ QTMDModelSym tmp;
+ int i, j;
+
+ if (--model.ShiftsLeft != 0)
+ {
+ for (i = model.Entries - 1; i >= 0; i--)
+ {
+ /* -1, not -2; the 0 entry saves this */
+ model.Syms[i].CumFreq >>= 1;
+ if (model.Syms[i].CumFreq <= model.Syms[i + 1].CumFreq)
+ {
+ model.Syms[i].CumFreq = (ushort)(model.Syms[i + 1].CumFreq + 1);
+ }
+ }
+ }
+ else
+ {
+ model.ShiftsLeft = 50;
+ for (i = 0; i < model.Entries; i++)
+ {
+ // No -1, want to include the 0 entry
+
+ // This converts CumFreqs into frequencies, then shifts right
+ model.Syms[i].CumFreq -= model.Syms[i + 1].CumFreq;
+ model.Syms[i].CumFreq++; // Avoid losing things entirely
+ model.Syms[i].CumFreq >>= 1;
+ }
+
+ // Now sort by frequencies, decreasing order -- this must be an
+ // inplace selection sort, or a sort with the same (in)stability
+ // characteristics
+ for (i = 0; i < model.Entries - 1; i++)
+ {
+ for (j = i + 1; j < model.Entries; j++)
+ {
+ if (model.Syms[i].CumFreq < model.Syms[j].CumFreq)
+ {
+ tmp = model.Syms[i];
+ model.Syms[i] = model.Syms[j];
+ model.Syms[j] = tmp;
+ }
+ }
+ }
+
+ // Then convert frequencies back to CumFreq
+ for (i = model.Entries - 1; i >= 0; i--)
+ {
+ model.Syms[i].CumFreq += model.Syms[i + 1].CumFreq;
+ }
+ }
+ }
+
+ private static void InitModel(QTMDModel model, QTMDModelSym[] syms, int start, int len)
+ {
+ model.ShiftsLeft = 4;
+ model.Entries = len;
+ model.Syms = syms;
+
+ for (int i = 0; i <= len; i++)
+ {
+ // Actual symbol
+ syms[i].Sym = (ushort)(start + i);
+
+ // Current frequency of that symbol
+ syms[i].CumFreq = (ushort)(len - i);
+ }
+ }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/QTMDModel.cs b/BurnOutSharp/External/libmspack/Compression/QTMDModel.cs
new file mode 100644
index 00000000..a4dc09c8
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/QTMDModel.cs
@@ -0,0 +1,23 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * The Quantum method was created by David Stafford, adapted by Microsoft
+ * Corporation.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.Compression
+{
+ public class QTMDModel
+ {
+ public int ShiftsLeft { get; set; }
+
+ public int Entries { get; set; }
+
+ public QTMDModelSym[] Syms { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/QTMDModelSym.cs b/BurnOutSharp/External/libmspack/Compression/QTMDModelSym.cs
new file mode 100644
index 00000000..b01da6e6
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/QTMDModelSym.cs
@@ -0,0 +1,21 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * The Quantum method was created by David Stafford, adapted by Microsoft
+ * Corporation.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.Compression
+{
+ public class QTMDModelSym
+ {
+ public ushort Sym { get; set; }
+
+ public ushort CumFreq { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Compression/QTMDStream.cs b/BurnOutSharp/External/libmspack/Compression/QTMDStream.cs
new file mode 100644
index 00000000..d11c09b5
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Compression/QTMDStream.cs
@@ -0,0 +1,142 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * The Quantum method was created by David Stafford, adapted by Microsoft
+ * Corporation.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.Compression
+{
+ public class QTMDStream : CompressionStream
+ {
+ ///
+ /// Decoding window
+ ///
+ public byte[] Window { get; set; }
+
+ ///
+ /// Window size
+ ///
+ public uint WindowSize { get; set; }
+
+ ///
+ /// Decompression offset within window
+ ///
+ public uint WindowPosition { get; set; }
+
+ ///
+ /// Bytes remaining for current frame
+ ///
+ public uint FrameTODO { get; set; }
+
+ ///
+ /// High: arith coding state
+ ///
+ public ushort High { get; set; }
+
+ ///
+ /// Low: arith coding state
+ ///
+ public ushort Low { get; set; }
+
+ ///
+ /// Current: arith coding state
+ ///
+ public ushort Current { get; set; }
+
+ ///
+ /// Have we started decoding a new frame?
+ ///
+ public byte HeaderRead { get; set; }
+
+ // Four literal models, each representing 64 symbols
+
+ ///
+ /// For literals from 0 to 63 (selector = 0)
+ ///
+ public QTMDModel Model0 { get; set; }
+
+ ///
+ /// For literals from 64 to 127 (selector = 1)
+ ///
+ public QTMDModel Model1 { get; set; }
+
+ ///
+ /// For literals from 128 to 191 (selector = 2)
+ ///
+ public QTMDModel Model2 { get; set; }
+
+ ///
+ /// For literals from 129 to 255 (selector = 3)
+ ///
+ public QTMDModel Model3 { get; set; }
+
+ // Three match models.
+
+ ///
+ /// For match with fixed length of 3 bytes
+ ///
+ public QTMDModel Model4 { get; set; }
+
+ ///
+ /// For match with fixed length of 4 bytes
+ ///
+ public QTMDModel Model5 { get; set; }
+
+ ///
+ /// For variable length match, encoded with model6len model
+ ///
+ public QTMDModel Model6 { get; set; }
+
+ public QTMDModel Model6Len { get; set; }
+
+ ///
+ /// Selector model. 0-6 to say literal (0,1,2,3) or match (4,5,6)
+ ///
+ public QTMDModel Model7 { get; set; }
+
+ // Symbol arrays for all models
+
+ public QTMDModelSym[] Model0Symbols { get; set; } = new QTMDModelSym[64 + 1];
+
+ public QTMDModelSym[] Model1Symbols { get; set; } = new QTMDModelSym[64 + 1];
+
+ public QTMDModelSym[] Model2Symbols { get; set; } = new QTMDModelSym[64 + 1];
+
+ public QTMDModelSym[] Model3Symbols { get; set; } = new QTMDModelSym[64 + 1];
+
+ public QTMDModelSym[] Model4Symbols { get; set; } = new QTMDModelSym[24 + 1];
+
+ public QTMDModelSym[] Model5Symbols { get; set; } = new QTMDModelSym[36 + 1];
+
+ public QTMDModelSym[] Model6Symbols { get; set; } = new QTMDModelSym[42 + 1];
+
+ public QTMDModelSym[] Model6LenSymbols { get; set; } = new QTMDModelSym[27 + 1];
+
+ public QTMDModelSym[] Model7Symbols { get; set; } = new QTMDModelSym[7 + 1];
+
+ public override Error READ_BYTES(ref int i_ptr, ref int i_end, ref uint bitsLeft, ref uint bitBuffer)
+ {
+ Error error = READ_IF_NEEDED(ref i_ptr, ref i_end);
+ if (error != Error.MSPACK_ERR_OK)
+ return error;
+
+ byte b0 = InputBuffer[i_ptr++];
+
+ error = READ_IF_NEEDED(ref i_ptr, ref i_end);
+ if (error != Error.MSPACK_ERR_OK)
+ return error;
+
+ byte b1 = InputBuffer[i_ptr++];
+ INJECT_BITS((uint)((b0 << 8) | b1), 16, ref bitsLeft, ref bitBuffer);
+ return Error.MSPACK_ERR_OK;
+ }
+
+ public override int HUFF_ERROR() => (int)Error.MSPACK_ERR_OK;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/DefaultFileImpl.cs b/BurnOutSharp/External/libmspack/DefaultFileImpl.cs
new file mode 100644
index 00000000..b51b3afd
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/DefaultFileImpl.cs
@@ -0,0 +1,20 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System.IO;
+
+namespace LibMSPackSharp
+{
+ public class DefaultFileImpl
+ {
+ public Stream FileHandle { get; set; }
+
+ public string Name { get; set; }
+ };
+}
diff --git a/BurnOutSharp/External/libmspack/Enums.cs b/BurnOutSharp/External/libmspack/Enums.cs
new file mode 100644
index 00000000..a3917055
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Enums.cs
@@ -0,0 +1,215 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp
+{
+ ///
+ /// All compressors and decompressors use the same set of error codes. Most
+ /// methods return an error code directly.For methods which do not
+ /// return error codes directly, the error code can be obtained with the
+ /// last_error() method.
+ ///
+ public enum Error
+ {
+ ///
+ /// Used to indicate success.
+ /// This error code is defined as zero, all other code are non-zero.
+ ///
+ MSPACK_ERR_OK = 0,
+
+ ///
+ /// A method was called with inappropriate arguments.
+ ///
+ MSPACK_ERR_ARGS = 1,
+
+ ///
+ /// Error opening file
+ ///
+ MSPACK_ERR_OPEN = 2,
+
+ ///
+ /// Error reading file
+ ///
+ MSPACK_ERR_READ = 3,
+
+ ///
+ /// Error writing file
+ ///
+ MSPACK_ERR_WRITE = 4,
+
+ ///
+ /// Seek error
+ ///
+ MSPACK_ERR_SEEK = 5,
+
+ ///
+ /// Out of memory
+ ///
+ MSPACK_ERR_NOMEMORY = 6,
+
+ ///
+ /// Bad "magic id" in file
+ ///
+ MSPACK_ERR_SIGNATURE = 7,
+
+ ///
+ /// Bad or corrupt file format
+ ///
+ MSPACK_ERR_DATAFORMAT = 8,
+
+ ///
+ /// Bad checksum or CRC
+ ///
+ MSPACK_ERR_CHECKSUM = 9,
+
+ ///
+ /// Error during compression
+ ///
+ MSPACK_ERR_CRUNCH = 10,
+
+ ///
+ /// Error during decompression
+ ///
+ MSPACK_ERR_DECRUNCH = 11,
+ }
+
+ ///
+ /// The interface to request current version of
+ ///
+ public enum Interfaces
+ {
+ ///
+ /// Pass to mspack_version() to get the overall library version
+ ///
+ MSPACK_VER_LIBRARY = 0,
+
+ ///
+ /// Pass to mspack_version() to get the mspack_system version
+ ///
+ MSPACK_VER_SYSTEM = 1,
+
+ ///
+ /// Pass to mspack_version() to get the mscab_decompressor version
+ ///
+ MSPACK_VER_MSCABD = 2,
+
+ ///
+ /// Pass to mspack_version() to get the mscab_compressor version
+ ///
+ MSPACK_VER_MSCABC = 3,
+
+ ///
+ /// Pass to mspack_version() to get the mschm_decompressor version
+ ///
+ MSPACK_VER_MSCHMD = 4,
+
+ ///
+ /// Pass to mspack_version() to get the mschm_compressor version
+ ///
+ MSPACK_VER_MSCHMC = 5,
+
+ ///
+ /// Pass to mspack_version() to get the mslit_decompressor version
+ ///
+ MSPACK_VER_MSLITD = 6,
+
+ ///
+ /// Pass to mspack_version() to get the mslit_compressor version
+ ///
+ MSPACK_VER_MSLITC = 7,
+
+ ///
+ /// Pass to mspack_version() to get the mshlp_decompressor version
+ ///
+ MSPACK_VER_MSHLPD = 8,
+
+ ///
+ /// Pass to mspack_version() to get the mshlp_compressor version
+ ///
+ MSPACK_VER_MSHLPC = 9,
+
+ ///
+ /// Pass to mspack_version() to get the msszdd_decompressor version
+ ///
+ MSPACK_VER_MSSZDDD = 10,
+
+ ///
+ /// Pass to mspack_version() to get the msszdd_compressor version
+ ///
+ MSPACK_VER_MSSZDDC = 11,
+
+ ///
+ /// Pass to mspack_version() to get the mskwaj_decompressor version
+ ///
+ MSPACK_VER_MSKWAJD = 12,
+
+ ///
+ /// Pass to mspack_version() to get the mskwaj_compressor version
+ ///
+ MSPACK_VER_MSKWAJC = 13,
+
+ ///
+ /// Pass to mspack_version() to get the msoab_decompressor version
+ ///
+ MSPACK_VER_MSOABD = 14,
+
+ ///
+ /// Pass to mspack_version() to get the msoab_compressor version
+ ///
+ MSPACK_VER_MSOABC = 15,
+ }
+
+ public enum OpenMode
+ {
+ ///
+ /// mspack_system::open() mode: open existing file for reading.
+ ///
+ MSPACK_SYS_OPEN_READ = 0,
+
+ ///
+ /// mspack_system::open() mode: open new file for writing
+ ///
+ MSPACK_SYS_OPEN_WRITE = 1,
+
+ ///
+ /// mspack_system::open() mode: open existing file for writing
+ ///
+ MSPACK_SYS_OPEN_UPDATE = 2,
+
+ ///
+ /// mspack_system::open() mode: open existing file for writing
+ ///
+ MSPACK_SYS_OPEN_APPEND = 3,
+ }
+
+ public enum SeekMode
+ {
+ ///
+ /// mspack_system::seek() mode: seek relative to start of file
+ ///
+ MSPACK_SYS_SEEK_START = 0,
+
+ ///
+ /// mspack_system::seek() mode: seek relative to current offset
+ ///
+ MSPACK_SYS_SEEK_CUR = 1,
+
+ ///
+ /// mspack_system::seek() mode: seek relative to end of file
+ ///
+ MSPACK_SYS_SEEK_END = 2,
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/HLP/Compressor.cs b/BurnOutSharp/External/libmspack/HLP/Compressor.cs
new file mode 100644
index 00000000..47ea0671
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/HLP/Compressor.cs
@@ -0,0 +1,23 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.HLP
+{
+ public class Compressor
+ {
+ public int Dummy { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/HLP/CompressorImpl.cs b/BurnOutSharp/External/libmspack/HLP/CompressorImpl.cs
new file mode 100644
index 00000000..9505c83a
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/HLP/CompressorImpl.cs
@@ -0,0 +1,18 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.HLP
+{
+ public class CompressorImpl : Compressor
+ {
+ public SystemImpl System { get; set; }
+
+ // TODO
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/HLP/Decompressor.cs b/BurnOutSharp/External/libmspack/HLP/Decompressor.cs
new file mode 100644
index 00000000..2b498d0b
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/HLP/Decompressor.cs
@@ -0,0 +1,23 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.HLP
+{
+ public class Decompressor
+ {
+ public int Dummy { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/HLP/DecompressorImpl.cs b/BurnOutSharp/External/libmspack/HLP/DecompressorImpl.cs
new file mode 100644
index 00000000..534edc6d
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/HLP/DecompressorImpl.cs
@@ -0,0 +1,18 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.HLP
+{
+ public class DecompressorImpl : Decompressor
+ {
+ public SystemImpl System { get; set; }
+
+ // TODO
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/HLP/Implementation.cs b/BurnOutSharp/External/libmspack/HLP/Implementation.cs
new file mode 100644
index 00000000..81d2717f
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/HLP/Implementation.cs
@@ -0,0 +1,15 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.HLP
+{
+ public class Implementation
+ {
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/KWAJ/Compressor.cs b/BurnOutSharp/External/libmspack/KWAJ/Compressor.cs
new file mode 100644
index 00000000..c86d55ff
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/KWAJ/Compressor.cs
@@ -0,0 +1,139 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+
+namespace LibMSPackSharp.KWAJ
+{
+ ///
+ /// A compressor for the KWAJ file format.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ ///
+ ///
+ public class Compressor
+ {
+ ///
+ /// Reads an input file and creates a compressed output file in the
+ /// KWAJ compressed file format.The KWAJ compression format is quick
+ /// but gives poor compression.It is possible for the compressed output
+ /// file to be larger than the input file.
+ ///
+ ///
+ /// a self-referential pointer to the Compressor
+ /// instance being called
+ ///
+ ///
+ /// the name of the file to compressed. This is passed
+ /// passed directly to mspack_system::open()
+ ///
+ ///
+ /// the name of the file to write compressed data to.
+ /// This is passed directly to mspack_system::open().
+ ///
+ ///
+ /// the length of the uncompressed file, or -1 to indicate
+ /// that this should be determined automatically by using
+ /// mspack_system::seek() on the input file.
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ ///
+ public Func Compress;
+
+ ///
+ /// Sets an KWAJ compression engine parameter.
+ ///
+ /// The following parameters are defined:
+ ///
+ /// - #MSKWAJC_PARAM_COMP_TYPE: the compression method to use. Must
+ /// be one of #MSKWAJC_COMP_NONE, #MSKWAJC_COMP_XOR, #MSKWAJ_COMP_SZDD
+ /// or #MSKWAJ_COMP_LZH. The default is #MSKWAJ_COMP_LZH.
+ ///
+ /// - #MSKWAJC_PARAM_INCLUDE_LENGTH: a boolean; should the compressed
+ /// output file should include the uncompressed length of the input
+ /// file in the header? This adds 4 bytes to the size of the output
+ /// file. A value of zero says "no", non-zero says "yes". The default
+ /// is "no".
+ ///
+ ///
+ /// a self-referential pointer to the Compressor
+ /// instance being called
+ ///
+ /// the parameter to set
+ /// the value to set the parameter to
+ /// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there
+ /// is a problem with either parameter or value.
+ ///
+ ///
+ public Func SetParam;
+
+ ///
+ /// Sets the original filename of the file before compression,
+ /// which will be stored in the header of the output file.
+ ///
+ /// The filename should be a null-terminated string, it must be an
+ /// MS-DOS "8.3" type filename (up to 8 bytes for the filename, then
+ /// optionally a "." and up to 3 bytes for a filename extension).
+ ///
+ /// If NULL is passed as the filename, no filename is included in the
+ /// header. This is the default.
+ ///
+ ///
+ /// a self-referential pointer to the Compressor
+ /// instance being called
+ ///
+ /// the original filename to use
+ ///
+ /// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if the
+ /// filename is too long
+ ///
+ public Func SetFilename;
+
+ ///
+ /// Sets arbitrary data that will be stored in the header of the
+ /// output file, uncompressed. It can be up to roughly 64 kilobytes,
+ /// as the overall size of the header must not exceed 65535 bytes.
+ /// The data can contain null bytes if desired.
+ ///
+ /// If NULL is passed as the data pointer, or zero is passed as the
+ /// length, no extra data is included in the header. This is the
+ /// default.
+ ///
+ ///
+ /// a self-referential pointer to the Compressor
+ /// instance being called
+ ///
+ /// a pointer to the data to be stored in the header
+ /// the length of the data in bytes
+ ///
+ /// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS extra data
+ /// is too long
+ ///
+ public Func SetExtraData;
+
+ ///
+ /// Returns the error code set by the most recently called method.
+ ///
+ ///
+ /// a self-referential pointer to the Compressor
+ /// instance being called
+ ///
+ /// the most recent error code
+ ///
+ public Func LastError;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/KWAJ/CompressorImpl.cs b/BurnOutSharp/External/libmspack/KWAJ/CompressorImpl.cs
new file mode 100644
index 00000000..65f0eaa5
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/KWAJ/CompressorImpl.cs
@@ -0,0 +1,25 @@
+/* This file is part of libmspack.
+ * (C) 2003-2010 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.KWAJ
+{
+ public class CompressorImpl : Compressor
+ {
+ public SystemImpl System { get; set; }
+
+ // TODO
+
+ ///
+ /// !!! MATCH THIS TO NUM OF PARAMS IN MSPACK.H !!!
+ ///
+ public int[] Param { get; set; } = new int[2];
+
+ public Error Error { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/KWAJ/Decompressor.cs b/BurnOutSharp/External/libmspack/KWAJ/Decompressor.cs
new file mode 100644
index 00000000..3ef07706
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/KWAJ/Decompressor.cs
@@ -0,0 +1,126 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+
+namespace LibMSPackSharp.KWAJ
+{
+ ///
+ /// A decompressor for KWAJ compressed files.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ ///
+ ///
+ public class Decompressor
+ {
+ ///
+ /// Opens a KWAJ file and reads the header.
+ ///
+ /// If the file opened is a valid KWAJ file, all headers will be read and
+ /// a mskwajd_header structure will be returned.
+ ///
+ /// In the case of an error occuring, NULL is returned and the error code
+ /// is available from last_error().
+ ///
+ /// The filename pointer should be considered "in use" until close() is
+ /// called on the KWAJ file.
+ ///
+ ///
+ /// a self-referential pointer to the mskwaj_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the KWAJ compressed file. This is
+ /// passed directly to mspack_system::open().
+ ///
+ /// a pointer to a mskwajd_header structure, or NULL on failure
+ ///
+ public Func Open;
+
+ ///
+ /// Closes a previously opened KWAJ file.
+ ///
+ /// This closes a KWAJ file and frees the mskwajd_header associated
+ /// with it. The KWAJ header pointer is now invalid and cannot be
+ /// used again.
+ ///
+ ///
+ /// a self-referential pointer to the mskwaj_decompressor
+ /// instance being called
+ ///
+ /// the KWAJ file to close
+ ///
+ public Action Close;
+
+ ///
+ /// Extracts the compressed data from a KWAJ file.
+ ///
+ /// This decompresses the compressed KWAJ data stream and writes it to
+ /// an output file.
+ ///
+ ///
+ /// a self-referential pointer to the mskwaj_decompressor
+ /// instance being called
+ ///
+ /// the KWAJ file to extract data from
+ ///
+ /// the filename to write the decompressed data to. This
+ /// is passed directly to mspack_system::open().
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func Extract;
+
+ ///
+ /// Decompresses an KWAJ file to an output file in one step.
+ ///
+ /// This opens an KWAJ file as input, reads the header, then decompresses
+ /// the compressed data immediately to an output file, finally closing
+ /// both the input and output file. It is more convenient to use than
+ /// open() then extract() then close(), if you do not need to know the
+ /// KWAJ output size or output filename.
+ ///
+ ///
+ /// a self-referential pointer to the mskwaj_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the input KWAJ file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ ///
+ /// the filename to write the decompressed data to. This
+ /// is passed directly to mspack_system::open().
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func Decompress;
+
+ ///
+ /// Returns the error code set by the most recently called method.
+ ///
+ /// This is useful for open() which does not return an
+ /// error code directly.
+ ///
+ ///
+ /// a self-referential pointer to the mskwaj_decompressor
+ /// instance being called
+ ///
+ /// the most recent error code
+ ///
+ ///
+ public Func LastError;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/KWAJ/DecompressorImpl.cs b/BurnOutSharp/External/libmspack/KWAJ/DecompressorImpl.cs
new file mode 100644
index 00000000..538cafa7
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/KWAJ/DecompressorImpl.cs
@@ -0,0 +1,18 @@
+/* This file is part of libmspack.
+ * (C) 2003-2010 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.KWAJ
+{
+ public class DecompressorImpl : Decompressor
+ {
+ public SystemImpl System { get; set; }
+
+ public Error Error { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/KWAJ/Enums.cs b/BurnOutSharp/External/libmspack/KWAJ/Enums.cs
new file mode 100644
index 00000000..d79f3856
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/KWAJ/Enums.cs
@@ -0,0 +1,88 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+
+namespace LibMSPackSharp.KWAJ
+{
+ public enum CompressionType
+ {
+ ///
+ /// no compression.
+ ///
+ MSKWAJ_COMP_NONE = 0,
+
+ ///
+ /// no compression, 0xFF XOR "encryption".
+ ///
+ MSKWAJ_COMP_XOR = 1,
+
+ ///
+ /// LZSS (same method as SZDD)
+ ///
+ MSKWAJ_COMP_SZDD = 2,
+
+ ///
+ /// LZ+Huffman compression
+ ///
+ MSKWAJ_COMP_LZH = 3,
+
+ ///
+ /// MSZIP
+ ///
+ MSKWAJ_COMP_MSZIP = 4,
+ }
+
+ [Flags]
+ public enum OptionalHeaderFlag : ushort
+ {
+ ///
+ /// decompressed file length is included
+ ///
+ MSKWAJ_HDR_HASLENGTH = 0x01,
+
+ ///
+ /// unknown 2-byte structure is included
+ ///
+ MSKWAJ_HDR_HASUNKNOWN1 = 0x02,
+
+ ///
+ /// unknown multi-sized structure is included
+ ///
+ MSKWAJ_HDR_HASUNKNOWN2 = 0x04,
+
+ ///
+ /// file name (no extension) is included
+ ///
+ MSKWAJ_HDR_HASFILENAME = 0x08,
+
+ ///
+ /// file extension is included
+ ///
+ MSKWAJ_HDR_HASFILEEXT = 0x10,
+
+ ///
+ /// extra text is included
+ ///
+ MSKWAJ_HDR_HASEXTRATEXT = 0x20,
+ }
+
+ public enum Parameters
+ {
+ ///
+ /// Compression type
+ ///
+ MSKWAJC_PARAM_COMP_TYPE = 0,
+
+ ///
+ /// Include the length of the uncompressed file in the header?
+ ///
+ MSKWAJC_PARAM_INCLUDE_LENGTH = 1,
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/KWAJ/Header.cs b/BurnOutSharp/External/libmspack/KWAJ/Header.cs
new file mode 100644
index 00000000..a762c615
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/KWAJ/Header.cs
@@ -0,0 +1,62 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.KWAJ
+{
+ ///
+ /// A structure which represents an KWAJ compressed file.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ public class Header
+ {
+ ///
+ /// The compression type
+ ///
+ public CompressionType CompressionType { get; set; }
+
+ ///
+ /// The offset in the file where the compressed data stream begins
+ ///
+ public long DataOffset { get; set; }
+
+ ///
+ /// Flags indicating which optional headers were included.
+ ///
+ public OptionalHeaderFlag Headers { get; set; }
+
+ ///
+ /// The amount of uncompressed data in the file, or 0 if not present.
+ ///
+ public long Length { get; set; }
+
+ ///
+ /// Output filename, or NULL if not present
+ ///
+ public string Filename { get; set; }
+
+ ///
+ /// Extra uncompressed data (usually text) in the header.
+ /// This data can contain nulls so use extra_length to get the size.
+ ///
+ public string Extra { get; set; }
+
+ ///
+ /// Length of extra uncompressed data in the header
+ ///
+ public ushort ExtraLength { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/KWAJ/HeaderImpl.cs b/BurnOutSharp/External/libmspack/KWAJ/HeaderImpl.cs
new file mode 100644
index 00000000..b64bb420
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/KWAJ/HeaderImpl.cs
@@ -0,0 +1,16 @@
+/* This file is part of libmspack.
+ * (C) 2003-2010 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.KWAJ
+{
+ public class HeaderImpl : Header
+ {
+ public object FileHandle { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/KWAJ/Implementation.cs b/BurnOutSharp/External/libmspack/KWAJ/Implementation.cs
new file mode 100644
index 00000000..36214146
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/KWAJ/Implementation.cs
@@ -0,0 +1,772 @@
+/* This file is part of libmspack.
+ * (C) 2003-2010 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+using System.Text;
+using LibMSPackSharp.Compression;
+
+namespace LibMSPackSharp.KWAJ
+{
+ public class Implementation
+ {
+ #region Generic KWAJ Definitions
+
+ private const byte kwajh_Signature1 = 0x00;
+ private const byte kwajh_Signature2 = 0x04;
+ private const byte kwajh_CompMethod = 0x08;
+ private const byte kwajh_DataOffset = 0x0a;
+ private const byte kwajh_Flags = 0x0c;
+ private const byte kwajh_SIZEOF = 0x0e;
+
+ #endregion
+
+ #region KWAJ Decompression Definitions
+
+ // Input buffer size during decompression - not worth parameterising IMHO
+ private const int KWAJ_INPUT_SIZE = (2048);
+
+ // Huffman codes that are 9 bits or less are decoded immediately
+ public const int KWAJ_TABLEBITS = (9);
+
+ // Number of codes in each huffman table
+
+ public const int KWAJ_MATCHLEN1_SYMS = (16);
+ public const int KWAJ_MATCHLEN2_SYMS = (16);
+ public const int KWAJ_LITLEN_SYMS = (32);
+ public const int KWAJ_OFFSET_SYMS = (64);
+ public const int KWAJ_LITERAL_SYMS = (256);
+
+ // Define decoding table sizes
+
+ public const int KWAJ_TABLESIZE = (1 << KWAJ_TABLEBITS);
+ public static int KWAJ_MATCHLEN1_TBLSIZE
+ {
+ get
+ {
+ if (KWAJ_TABLESIZE < (KWAJ_MATCHLEN1_SYMS * 2))
+ return (KWAJ_MATCHLEN1_SYMS * 4);
+ else
+ return (KWAJ_TABLESIZE + (KWAJ_MATCHLEN1_SYMS * 2));
+ }
+ }
+
+ public static int KWAJ_MATCHLEN2_TBLSIZE
+ {
+ get
+ {
+ if (KWAJ_TABLESIZE < (KWAJ_MATCHLEN2_SYMS * 2))
+ return (KWAJ_MATCHLEN2_SYMS * 4);
+ else
+ return (KWAJ_TABLESIZE + (KWAJ_MATCHLEN2_SYMS * 2));
+ }
+ }
+
+ public static int KWAJ_LITLEN_TBLSIZE
+ {
+ get
+ {
+ if (KWAJ_TABLESIZE < (KWAJ_LITLEN_SYMS * 2))
+ return (KWAJ_LITLEN_SYMS * 4);
+ else
+ return (KWAJ_TABLESIZE + (KWAJ_LITLEN_SYMS * 2));
+ }
+ }
+
+ public static int KWAJ_OFFSET_TBLSIZE
+ {
+ get
+ {
+ if (KWAJ_TABLESIZE < (KWAJ_OFFSET_SYMS * 2))
+ return (KWAJ_OFFSET_SYMS * 4);
+ else
+ return (KWAJ_TABLESIZE + (KWAJ_OFFSET_SYMS * 2));
+ }
+ }
+
+ public static int KWAJ_LITERAL_TBLSIZE
+ {
+ get
+ {
+ if (KWAJ_TABLESIZE < (KWAJ_LITERAL_SYMS * 2))
+ return (KWAJ_LITERAL_SYMS * 4);
+ else
+ return (KWAJ_TABLESIZE + (KWAJ_LITERAL_SYMS * 2));
+ }
+ }
+
+ #endregion
+
+ #region KWAJD_OPEN
+
+ ///
+ /// Opens a KWAJ file without decompressing, reads header
+ ///
+ public static Header Open(Decompressor d, string filename)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ if (self == null)
+ return null;
+
+ SystemImpl sys = self.System;
+
+ object fh = sys.Open(sys, filename, OpenMode.MSPACK_SYS_OPEN_READ);
+ HeaderImpl hdr = new HeaderImpl();
+ if (fh != null && hdr != null)
+ {
+ hdr.FileHandle = fh;
+ self.Error = ReadHeaders(sys, fh, hdr);
+ }
+ else
+ {
+ if (fh == null)
+ self.Error = Error.MSPACK_ERR_OPEN;
+ if (hdr == null)
+ self.Error = Error.MSPACK_ERR_NOMEMORY;
+ }
+
+ if (self.Error != Error.MSPACK_ERR_OK)
+ {
+ if (fh != null)
+ sys.Close(fh);
+
+ sys.Free(hdr);
+ hdr = null;
+ }
+
+ return hdr;
+ }
+
+ #endregion
+
+ #region KWAJD_CLOSE
+
+ ///
+ /// Closes a KWAJ file
+ ///
+ public static void Close(Decompressor d, Header hdr)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ HeaderImpl hdr_p = (HeaderImpl)hdr;
+
+ if (self == null || self.System == null)
+ return;
+
+ // Close the file handle associated
+ self.System.Close(hdr_p.FileHandle);
+
+ // Free the memory associated
+ self.System.Free(hdr);
+
+ self.Error = Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region KWAJD_READ_HEADERS
+
+ ///
+ /// Reads the headers of a KWAJ format file
+ ///
+ public static Error ReadHeaders(SystemImpl sys, object fh, Header hdr)
+ {
+ int i;
+
+ // Read in the header
+ byte[] buf = new byte[16];
+ if (sys.Read(fh, buf, 0, kwajh_SIZEOF) != kwajh_SIZEOF)
+ {
+ return Error.MSPACK_ERR_READ;
+ }
+
+ // Check for "KWAJ" signature
+ if ((BitConverter.ToUInt32(buf, kwajh_Signature1) != 0x4A41574B) ||
+ (BitConverter.ToUInt32(buf, kwajh_Signature2) != 0xD127F088))
+ {
+ return Error.MSPACK_ERR_SIGNATURE;
+ }
+
+ // Basic header fields
+ hdr.CompressionType = (CompressionType)BitConverter.ToUInt16(buf, kwajh_CompMethod);
+ hdr.DataOffset = BitConverter.ToUInt16(buf, kwajh_DataOffset);
+ hdr.Headers = (OptionalHeaderFlag)BitConverter.ToUInt16(buf, kwajh_Flags);
+ hdr.Length = 0;
+ hdr.Filename = null;
+ hdr.Extra = null;
+ hdr.ExtraLength = 0;
+
+ // Optional headers
+
+ // 4 bytes: length of unpacked file
+ if (hdr.Headers.HasFlag(OptionalHeaderFlag.MSKWAJ_HDR_HASLENGTH))
+ {
+ if (sys.Read(fh, buf, 0, 4) != 4)
+ return Error.MSPACK_ERR_READ;
+
+ hdr.Length = BitConverter.ToUInt32(buf, 0);
+ }
+
+ // 2 bytes: unknown purpose
+ if (hdr.Headers.HasFlag(OptionalHeaderFlag.MSKWAJ_HDR_HASUNKNOWN1))
+ {
+ if (sys.Read(fh, buf, 0, 2) != 2)
+ return Error.MSPACK_ERR_READ;
+ }
+
+ // 2 bytes: length of section, then [length] bytes: unknown purpose
+ if (hdr.Headers.HasFlag(OptionalHeaderFlag.MSKWAJ_HDR_HASUNKNOWN2))
+ {
+ if (sys.Read(fh, buf, 0, 2) != 2)
+ return Error.MSPACK_ERR_READ;
+
+ i = BitConverter.ToUInt16(buf, 0);
+ if (sys.Seek(fh, i, SeekMode.MSPACK_SYS_SEEK_CUR))
+ return Error.MSPACK_ERR_SEEK;
+ }
+
+ // Filename and extension
+ if (hdr.Headers.HasFlag(OptionalHeaderFlag.MSKWAJ_HDR_HASFILENAME) || hdr.Headers.HasFlag(OptionalHeaderFlag.MSKWAJ_HDR_HASFILEEXT))
+ {
+ int len;
+
+ // Allocate memory for maximum length filename
+ char[] fn = new char[13];
+ int fnPtr = 0;
+
+ // Copy filename if present
+ if (hdr.Headers.HasFlag(OptionalHeaderFlag.MSKWAJ_HDR_HASFILENAME))
+ {
+ // Read and copy up to 9 bytes of a null terminated string
+ if ((len = sys.Read(fh, buf, 0, 9)) < 2)
+ return Error.MSPACK_ERR_READ;
+
+ for (i = 0; i < len; i++)
+ {
+ if ((fn[fnPtr++] = (char)buf[i]) == '\0')
+ break;
+ }
+
+ // If string was 9 bytes with no null terminator, reject it
+ if (i == 9 && buf[8] != '\0')
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ // Seek to byte after string ended in file
+ if (sys.Seek(fh, i + 1 - len, SeekMode.MSPACK_SYS_SEEK_CUR))
+ return Error.MSPACK_ERR_SEEK;
+
+ fnPtr--; // Remove the null terminator
+ }
+
+ // Copy extension if present
+ if (hdr.Headers.HasFlag(OptionalHeaderFlag.MSKWAJ_HDR_HASFILEEXT))
+ {
+ fn[fnPtr++] = '.';
+
+ // Read and copy up to 4 bytes of a null terminated string
+ if ((len = sys.Read(fh, buf, 0, 4)) < 2)
+ return Error.MSPACK_ERR_READ;
+
+ for (i = 0; i < len; i++)
+ {
+ if ((fn[fnPtr++] = (char)buf[i]) == '\0')
+ break;
+ }
+
+ // If string was 4 bytes with no null terminator, reject it
+ if (i == 4 && buf[3] != '\0')
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ // Seek to byte after string ended in file
+ if (sys.Seek(fh, i + 1 - len, SeekMode.MSPACK_SYS_SEEK_CUR))
+ return Error.MSPACK_ERR_SEEK;
+
+ fnPtr--; // Remove the null terminator
+ }
+
+ fn[fnPtr] = '\0';
+ }
+
+ // 2 bytes: extra text length then [length] bytes of extra text data
+ if (hdr.Headers.HasFlag(OptionalHeaderFlag.MSKWAJ_HDR_HASEXTRATEXT))
+ {
+ if (sys.Read(fh, buf, 0, 2) != 2)
+ return Error.MSPACK_ERR_READ;
+
+ i = BitConverter.ToUInt16(buf, 0);
+ byte[] extra = new byte[i + 1];
+ if (sys.Read(fh, extra, 0, i) != i)
+ return Error.MSPACK_ERR_READ;
+
+ extra[i] = 0x00;
+ hdr.Extra = Encoding.ASCII.GetString(extra, 0, extra.Length);
+ hdr.ExtraLength = (ushort)i;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region KWAJD_EXTRACT
+
+ ///
+ /// Decompresses a KWAJ file
+ ///
+ public static Error Extract(Decompressor d, Header hdr, string filename)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+ if (hdr == null)
+ return self.Error = Error.MSPACK_ERR_ARGS;
+
+ SystemImpl sys = self.System;
+ object fh = ((HeaderImpl)hdr).FileHandle;
+
+ // Seek to the compressed data
+ if (sys.Seek(fh, hdr.DataOffset, SeekMode.MSPACK_SYS_SEEK_START))
+ return self.Error = Error.MSPACK_ERR_SEEK;
+
+ // Open file for output
+ object outfh;
+ if ((outfh = sys.Open(sys, filename, OpenMode.MSPACK_SYS_OPEN_WRITE)) == null)
+ return self.Error = Error.MSPACK_ERR_OPEN;
+
+ self.Error = Error.MSPACK_ERR_OK;
+
+ // Decompress based on format
+ if (hdr.CompressionType == CompressionType.MSKWAJ_COMP_NONE ||
+ hdr.CompressionType == CompressionType.MSKWAJ_COMP_XOR)
+ {
+ // NONE is a straight copy. XOR is a copy xored with 0xFF
+ byte[] buf = new byte[KWAJ_INPUT_SIZE];
+
+ int read, i;
+ while ((read = sys.Read(fh, buf, 0, KWAJ_INPUT_SIZE)) > 0)
+ {
+ if (hdr.CompressionType == CompressionType.MSKWAJ_COMP_XOR)
+ {
+ for (i = 0; i < read; i++)
+ {
+ buf[i] ^= 0xFF;
+ }
+ }
+
+ if (sys.Write(outfh, buf, 0, read) != read)
+ {
+ self.Error = Error.MSPACK_ERR_WRITE;
+ break;
+ }
+ }
+
+ if (read < 0)
+ self.Error = Error.MSPACK_ERR_READ;
+
+ sys.Free(buf);
+ }
+ else if (hdr.CompressionType == CompressionType.MSKWAJ_COMP_SZDD)
+ {
+ self.Error = LZSS.Decompress(sys, fh, outfh, KWAJ_INPUT_SIZE, LZSSMode.LZSS_MODE_EXPAND);
+ }
+ else if (hdr.CompressionType == CompressionType.MSKWAJ_COMP_LZH)
+ {
+ InternalStream lzh = LZHInit(sys, fh, outfh);
+ self.Error = (lzh != null) ? LZHDecompress(lzh) : Error.MSPACK_ERR_NOMEMORY;
+ LZHFree(lzh);
+ }
+ else if (hdr.CompressionType == CompressionType.MSKWAJ_COMP_MSZIP)
+ {
+ MSZIPDStream zip = MSZIP.Init(sys, fh, outfh, KWAJ_INPUT_SIZE, false);
+ self.Error = (zip != null) ? MSZIP.DecompressKWAJ(zip) : Error.MSPACK_ERR_NOMEMORY;
+ MSZIP.Free(zip);
+ }
+ else
+ {
+ self.Error = Error.MSPACK_ERR_DATAFORMAT;
+ }
+
+ // Close output file
+ sys.Close(outfh);
+
+ return self.Error;
+ }
+
+ #endregion
+
+ #region KWAJD_DECOMPRESS
+
+ ///
+ /// Unpacks directly from input to output
+ ///
+ public static Error Decompress(Decompressor d, string input, string output)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ Header hdr;
+ if ((hdr = Open(d, input)) == null)
+ return self.Error;
+
+ Error error = Extract(d, hdr, output);
+ Close(d, hdr);
+ return self.Error = error;
+ }
+
+ #endregion
+
+ #region KWAJD_ERROR
+
+ ///
+ /// Returns the last error that occurred
+ ///
+ public static Error LastError(Decompressor d)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ return (self != null) ? self.Error : Error.MSPACK_ERR_ARGS;
+ }
+
+ #endregion
+
+ #region LZH_INIT, LZH_DECOMPRESS, LZH_FREE
+
+ /* In the KWAJ LZH format, there is no special 'eof' marker, it just
+ * ends. Depending on how many bits are left in the final byte when
+ * the stream ends, that might be enough to start another literal or
+ * match. The only easy way to detect that we've come to an end is to
+ * guard all bit-reading. We allow fake bits to be read once we reach
+ * the end of the stream, but we check if we then consumed any of
+ * those fake bits, after doing the READ_BITS / READ_HUFFSYM. This
+ * isn't how the default readbits.h read_input() works (it simply lets
+ * 2 fake bytes in then stops), so we implement our own.
+ */
+
+ private static InternalStream LZHInit(SystemImpl sys, object input, object output)
+ {
+ if (sys == null || input == null || output == null)
+ return null;
+
+ return new InternalStream()
+ {
+ Sys = sys,
+ Input = input,
+ Output = output,
+ };
+ }
+
+ private static Error LZHDecompress(InternalStream lzh)
+ {
+ uint bit_buffer = 0, bits_left = 0, len = 0, j = 0;
+ int i;
+ ushort sym = 0;
+ int i_ptr = 0, i_end = 0;
+ bool lit_run = false;
+ int pos = 0, offset;
+ int[] types = new int[6];
+
+ // Reset global state
+ lzh.INIT_BITS();
+ lzh.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ for (i = 0; i < LZSS.LZSS_WINDOW_SIZE; i++)
+ {
+ lzh.Window[i] = LZSS.LZSS_WINDOW_FILL;
+ }
+
+ // Read 6 encoding types (for byte alignment) but only 5 are needed
+ for (i = 0; i < 6; i++)
+ {
+ //READ_BITS_SAFE(val, n)
+ lzh.READ_BITS(ref types[i], 4, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+ }
+
+ // Read huffman table symbol lengths and build huffman trees
+
+ //BUILD_TREE(tbl, type)
+ lzh.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ Error err = LZHReadLens(lzh, (uint)types[0], KWAJ_MATCHLEN1_SYMS, lzh.MATCHLEN1_len);
+ if (err != Error.MSPACK_ERR_OK)
+ return err;
+
+ lzh.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (InternalStream.MakeDecodeTable(KWAJ_MATCHLEN1_SYMS, (uint)KWAJ_MATCHLEN1_TBLSIZE, lzh.MATCHLEN1_len, lzh.MATCHLEN1_table) != 0)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ //BUILD_TREE(tbl, type)
+ lzh.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ err = LZHReadLens(lzh, (uint)types[1], KWAJ_MATCHLEN2_SYMS, lzh.MATCHLEN2_len);
+ if (err != Error.MSPACK_ERR_OK)
+ return err;
+
+ lzh.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (InternalStream.MakeDecodeTable(KWAJ_MATCHLEN2_SYMS, (uint)KWAJ_MATCHLEN2_TBLSIZE, lzh.MATCHLEN2_len, lzh.MATCHLEN2_table) != 0)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ //BUILD_TREE(tbl, type)
+ lzh.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ err = LZHReadLens(lzh, (uint)types[2], KWAJ_LITLEN_SYMS, lzh.LITLEN_len);
+ if (err != Error.MSPACK_ERR_OK)
+ return err;
+
+ lzh.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (InternalStream.MakeDecodeTable(KWAJ_LITLEN_SYMS, (uint)KWAJ_LITLEN_TBLSIZE, lzh.LITLEN_len, lzh.LITLEN_table) != 0)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ //BUILD_TREE(tbl, type)
+ lzh.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ err = LZHReadLens(lzh, (uint)types[3], KWAJ_OFFSET_SYMS, lzh.OFFSET_len);
+ if (err != Error.MSPACK_ERR_OK)
+ return err;
+
+ lzh.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (InternalStream.MakeDecodeTable(KWAJ_OFFSET_SYMS, (uint)KWAJ_OFFSET_TBLSIZE, lzh.OFFSET_len, lzh.OFFSET_table) != 0)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ //BUILD_TREE(tbl, type)
+ lzh.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+ err = LZHReadLens(lzh, (uint)types[4], KWAJ_LITERAL_SYMS, lzh.LITERAL_len);
+ if (err != Error.MSPACK_ERR_OK)
+ return err;
+
+ lzh.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (InternalStream.MakeDecodeTable(KWAJ_LITERAL_SYMS, (uint)KWAJ_LITERAL_TBLSIZE, lzh.LITERAL_len, lzh.LITERAL_table) != 0)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ while (lzh.InputEnd == 0)
+ {
+ if (lit_run)
+ {
+ //READ_HUFFSYM_SAFE(tbl, val)
+ lzh.READ_HUFFSYM(lzh.MATCHLEN2_table, ref len, KWAJ_MATCHLEN2_TBLSIZE, lzh.MATCHLEN2_len, KWAJ_MATCHLEN2_SYMS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+ }
+ else
+ {
+ //READ_HUFFSYM_SAFE(tbl, val)
+ lzh.READ_HUFFSYM(lzh.MATCHLEN1_table, ref len, KWAJ_MATCHLEN1_TBLSIZE, lzh.MATCHLEN1_len, KWAJ_MATCHLEN1_SYMS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+ }
+
+ if (len > 0)
+ {
+ len += 2;
+ lit_run = false; // Not the end of a literal run
+
+ //READ_HUFFSYM_SAFE(tbl, val)
+ lzh.READ_HUFFSYM(lzh.OFFSET_table, ref j, KWAJ_OFFSET_TBLSIZE, lzh.OFFSET_len, KWAJ_OFFSET_SYMS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ offset = (int)(j << 6);
+
+ //READ_BITS_SAFE(val, n)
+ int tempj = (int)j;
+ lzh.READ_BITS(ref tempj, 6, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ j = (uint)tempj;
+ offset |= tempj;
+
+ // Copy match as output and into the ring buffer
+ while (len-- > 0)
+ {
+ lzh.Window[pos] = lzh.Window[(pos + 4096 - offset) & 4095];
+ if (lzh.Sys.Write(lzh.Output, lzh.Window, pos, 1) != 1)
+ return Error.MSPACK_ERR_WRITE;
+
+ pos++; pos &= 4095;
+ }
+ }
+ else
+ {
+ //READ_HUFFSYM_SAFE(tbl, val)
+ lzh.READ_HUFFSYM(lzh.LITLEN_table, ref len, KWAJ_LITLEN_TBLSIZE, lzh.LITLEN_len, KWAJ_LITLEN_SYMS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ len++;
+
+ lit_run = (len == 32) ? false : true; // End of a literal run?
+ while (len-- > 0)
+ {
+ //READ_HUFFSYM_SAFE(tbl, val)
+ lzh.READ_HUFFSYM(lzh.LITERAL_table, ref j, KWAJ_LITERAL_TBLSIZE, lzh.LITERAL_len, KWAJ_LITERAL_SYMS, ref i, ref sym, ref i_ptr, ref i_end, ref bits_left, ref bit_buffer);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ // Copy as output and into the ring buffer
+ lzh.Window[pos] = (byte)j;
+ if (lzh.Sys.Write(lzh.Output, lzh.Window, pos, 1) != 1)
+ return Error.MSPACK_ERR_WRITE;
+
+ pos++;
+ pos &= 4095;
+ }
+ }
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ private static void LZHFree(InternalStream lzh)
+ {
+ if (lzh == null || lzh.Sys == null)
+ return;
+
+ SystemImpl sys = lzh.Sys;
+ sys.Free(lzh);
+ }
+
+ public static Error LZHReadLens(InternalStream lzh, uint type, uint numsyms, byte[] lens)
+ {
+ uint bit_buffer = 0, bits_left = 0;
+ int i_ptr = 0, i_end = 0;
+ uint i;
+ int c = 0, sel = 0;
+
+ lzh.RESTORE_BITS(ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ switch (type)
+ {
+ case 0:
+ i = numsyms;
+ c = (i == 16) ? 4 : (i == 32) ? 5 : (i == 64) ? 6 : (i == 256) ? 8 : 0;
+ for (i = 0; i < numsyms; i++)
+ {
+ lens[i] = (byte)c;
+ }
+
+ break;
+
+ case 1:
+ //READ_BITS_SAFE(val, n)
+ lzh.READ_BITS(ref c, 4, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ lens[0] = (byte)c;
+ for (i = 1; i < numsyms; i++)
+ {
+ //READ_BITS_SAFE(val, n)
+ lzh.READ_BITS(ref sel, 1, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ if (sel == 0)
+ {
+ lens[i] = (byte)c;
+ }
+ else
+ {
+ //READ_BITS_SAFE(val, n)
+ lzh.READ_BITS(ref sel, 1, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ if (sel == 0)
+ {
+ lens[i] = (byte)++c;
+ }
+ else
+ {
+ //READ_BITS_SAFE(val, n)
+ lzh.READ_BITS(ref c, 4, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ lens[i] = (byte)c;
+ }
+ }
+ }
+ break;
+
+ case 2:
+ //READ_BITS_SAFE(val, n)
+ lzh.READ_BITS(ref c, 4, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ lens[0] = (byte)c;
+ for (i = 1; i < numsyms; i++)
+ {
+ //READ_BITS_SAFE(val, n)
+ lzh.READ_BITS(ref sel, 2, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ if (sel == 3)
+ {
+ //READ_BITS_SAFE(val, n)
+ lzh.READ_BITS(ref c, 4, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+ }
+ else
+ {
+ c += (char)sel - 1;
+ }
+
+ lens[i] = (byte)c;
+ }
+
+ break;
+
+ case 3:
+ for (i = 0; i < numsyms; i++)
+ {
+ //READ_BITS_SAFE(val, n)
+ lzh.READ_BITS(ref c, 4, ref i_ptr, ref i_end, ref bit_buffer, ref bits_left);
+ if (lzh.InputEnd != 0 && bits_left < lzh.InputEnd)
+ return Error.MSPACK_ERR_OK;
+
+ lens[i] = (byte)c;
+ }
+
+ break;
+ }
+
+ lzh.STORE_BITS(i_ptr, i_end, bit_buffer, bits_left);
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ public static Error LZHReadInput(InternalStream lzh)
+ {
+ int read;
+ if (lzh.InputEnd != 0)
+ {
+ lzh.InputEnd += 8;
+ lzh.InputBuffer[0] = 0;
+ read = 1;
+ }
+ else
+ {
+ read = lzh.Sys.Read(lzh.Input, lzh.InputBuffer, 0, KWAJ_INPUT_SIZE);
+ if (read < 0)
+ return Error.MSPACK_ERR_READ;
+
+ if (read == 0)
+ {
+ lzh.InputLength = 8;
+ lzh.InputBuffer[0] = 0;
+ read = 1;
+ }
+ }
+
+ // Update InputPointer and InputLength
+ lzh.InputPointer = 0;
+ lzh.InputLength = read;
+ return Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/KWAJ/InternalStream.cs b/BurnOutSharp/External/libmspack/KWAJ/InternalStream.cs
new file mode 100644
index 00000000..d934b924
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/KWAJ/InternalStream.cs
@@ -0,0 +1,54 @@
+/* This file is part of libmspack.
+ * (C) 2003-2010 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using LibMSPackSharp.Compression;
+
+namespace LibMSPackSharp.KWAJ
+{
+ public class InternalStream : CompressionStream
+ {
+ // Huffman code lengths
+
+ public byte[] MATCHLEN1_len { get; set; } = new byte[Implementation.KWAJ_MATCHLEN1_SYMS];
+ public byte[] MATCHLEN2_len { get; set; } = new byte[Implementation.KWAJ_MATCHLEN2_SYMS];
+ public byte[] LITLEN_len { get; set; } = new byte[Implementation.KWAJ_LITLEN_SYMS];
+ public byte[] OFFSET_len { get; set; } = new byte[Implementation.KWAJ_OFFSET_SYMS];
+ public byte[] LITERAL_len { get; set; } = new byte[Implementation.KWAJ_LITERAL_SYMS];
+
+ // Huffman decoding tables
+
+ public ushort[] MATCHLEN1_table { get; set; } = new ushort[Implementation.KWAJ_MATCHLEN1_TBLSIZE];
+ public ushort[] MATCHLEN2_table { get; set; } = new ushort[Implementation.KWAJ_MATCHLEN2_TBLSIZE];
+ public ushort[] LITLEN_table { get; set; } = new ushort[Implementation.KWAJ_LITLEN_TBLSIZE];
+ public ushort[] OFFSET_table { get; set; } = new ushort[Implementation.KWAJ_OFFSET_TBLSIZE];
+ public ushort[] LITERAL_table { get; set; } = new ushort[Implementation.KWAJ_LITERAL_TBLSIZE];
+
+ // History window
+
+ public byte[] Window { get; set; } = new byte[LZSS.LZSS_WINDOW_SIZE];
+
+ public override Error READ_BYTES(ref int i_ptr, ref int i_end, ref uint bitsLeft, ref uint bitBuffer)
+ {
+ Error error = Error.MSPACK_ERR_OK;
+ if (i_ptr >= i_end)
+ {
+ if ((error = Implementation.LZHReadInput(this)) != Error.MSPACK_ERR_OK)
+ return error;
+
+ i_ptr = InputPointer;
+ i_end = InputLength;
+ }
+
+ INJECT_BITS(InputBuffer[i_ptr++], 8, ref bitsLeft, ref bitBuffer);
+ return error;
+ }
+
+ public override int HUFF_ERROR() => (int)Error.MSPACK_ERR_DATAFORMAT;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/LIT/Compressor.cs b/BurnOutSharp/External/libmspack/LIT/Compressor.cs
new file mode 100644
index 00000000..62ab902c
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/LIT/Compressor.cs
@@ -0,0 +1,24 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.LIT
+{
+ // TODO
+ public class Compressor
+ {
+ public int Dummy { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/LIT/CompressorImpl.cs b/BurnOutSharp/External/libmspack/LIT/CompressorImpl.cs
new file mode 100644
index 00000000..8eab67d0
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/LIT/CompressorImpl.cs
@@ -0,0 +1,18 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.LIT
+{
+ public class CompressorImpl : Compressor
+ {
+ public SystemImpl System { get; set; }
+
+ // TODO
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/LIT/Decompressor.cs b/BurnOutSharp/External/libmspack/LIT/Decompressor.cs
new file mode 100644
index 00000000..8c1b31e6
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/LIT/Decompressor.cs
@@ -0,0 +1,24 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.LIT
+{
+ // TODO
+ public class Decompressor
+ {
+ public int Dummy { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/LIT/DecompressorImpl.cs b/BurnOutSharp/External/libmspack/LIT/DecompressorImpl.cs
new file mode 100644
index 00000000..c8d5339a
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/LIT/DecompressorImpl.cs
@@ -0,0 +1,18 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.LIT
+{
+ public class DecompressorImpl : Decompressor
+ {
+ public SystemImpl System { get; set; }
+
+ // TODO
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/LIT/Implementation.cs b/BurnOutSharp/External/libmspack/LIT/Implementation.cs
new file mode 100644
index 00000000..06c384b7
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/LIT/Implementation.cs
@@ -0,0 +1,15 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.LIT
+{
+ public class Implementation
+ {
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/Library.cs b/BurnOutSharp/External/libmspack/Library.cs
new file mode 100644
index 00000000..0540b0b2
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/Library.cs
@@ -0,0 +1,564 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/** \mainpage
+ *
+ * \section intro Introduction
+ *
+ * libmspack is a library which provides compressors and decompressors,
+ * archivers and dearchivers for Microsoft compression formats.
+ *
+ * \section formats Formats supported
+ *
+ * The following file formats are supported:
+ * - SZDD files, which use LZSS compression
+ * - KWAJ files, which use LZSS, LZSS+Huffman or deflate compression
+ * - .HLP (MS Help) files, which use LZSS compression
+ * - .CAB (MS Cabinet) files, which use deflate, LZX or Quantum compression
+ * - .CHM (HTML Help) files, which use LZX compression
+ * - .LIT (MS EBook) files, which use LZX compression and DES encryption
+ * - .LZX (Exchange Offline Addressbook) files, which use LZX compression
+ *
+ * To determine the capabilities of the library, and the binary
+ * compatibility version of any particular compressor or decompressor, use
+ * the mspack_version() function. The UNIX library interface version is
+ * defined as the highest-versioned library component.
+ *
+ * \section starting Getting started
+ *
+ * The macro MSPACK_SYS_SELFTEST() should be used to ensure the library can
+ * be used. In particular, it checks if the caller is using 32-bit file I/O
+ * when the library is compiled for 64-bit file I/O and vice versa.
+ *
+ * If compiled normally, the library includes basic file I/O and memory
+ * management functionality using the standard C library. This can be
+ * customised and replaced entirely by creating a SystemImpl structure.
+ *
+ * A compressor or decompressor for the required format must be
+ * instantiated before it can be used. Each construction function takes
+ * one parameter, which is either a pointer to a custom SystemImpl
+ * structure, or null to use the default. The instantiation returned, if
+ * not null, contains function pointers (methods) to work with the given
+ * file format.
+ *
+ * For compression:
+ * - CreateCABCompressor() creates a mscab_compressor
+ * - CreateCHMCompressor() creates a mschm_compressor
+ * - CreateLITCompressor() creates a mslit_compressor
+ * - CreateHLPCompressor() creates a mshlp_compressor
+ * - CreateSZDDCompressor() creates a msszdd_compressor
+ * - CreateKWAJCompressor() creates a mskwaj_compressor
+ * - CreateOABCompressor() creates a msoab_compressor
+ *
+ * For decompression:
+ * - mspack_create_cab_decompressor() creates a mscab_decompressor
+ * - mspack_create_chm_decompressor() creates a mschm_decompressor
+ * - mspack_create_lit_decompressor() creates a mslit_decompressor
+ * - mspack_create_hlp_decompressor() creates a mshlp_decompressor
+ * - mspack_create_szdd_decompressor() creates a msszdd_decompressor
+ * - mspack_create_kwaj_decompressor() creates a mskwaj_decompressor
+ * - mspack_create_oab_decompressor() creates a msoab_decompressor
+ *
+ * Once finished working with a format, each kind of
+ * compressor/decompressor has its own specific destructor:
+ * - mspack_destroy_cab_compressor()
+ * - mspack_destroy_cab_decompressor()
+ * - mspack_destroy_chm_compressor()
+ * - mspack_destroy_chm_decompressor()
+ * - mspack_destroy_lit_compressor()
+ * - mspack_destroy_lit_decompressor()
+ * - mspack_destroy_hlp_compressor()
+ * - mspack_destroy_hlp_decompressor()
+ * - mspack_destroy_szdd_compressor()
+ * - mspack_destroy_szdd_decompressor()
+ * - mspack_destroy_kwaj_compressor()
+ * - mspack_destroy_kwaj_decompressor()
+ * - mspack_destroy_oab_compressor()
+ * - mspack_destroy_oab_decompressor()
+ *
+ * Destroying a compressor or decompressor does not destroy any objects,
+ * structures or handles that have been created using that compressor or
+ * decompressor. Ensure that everything created or opened is destroyed or
+ * closed before compressor/decompressor is itself destroyed.
+ *
+ * \section threading Multi-threading
+ *
+ * libmspack methods are reentrant and multithreading-safe when each
+ * thread has its own compressor or decompressor.
+ * You should not call multiple methods simultaneously on a single
+ * compressor or decompressor instance.
+ *
+ * If this may happen, you can either use one compressor or
+ * decompressor per thread, or you can use your preferred lock,
+ * semaphore or mutex library to ensure no more than one method on a
+ * compressor/decompressor is called simultaneously. libmspack will
+ * not do this locking for you.
+ *
+ * Example of incorrect behaviour:
+ * - thread 1 calls mspack_create_cab_decompressor()
+ * - thread 1 calls open()
+ * - thread 1 calls extract() for one file
+ * - thread 2 simultaneously calls extract() for another file
+ *
+ * Correct behaviour:
+ * - thread 1 calls mspack_create_cab_decompressor()
+ * - thread 2 calls mspack_create_cab_decompressor()
+ * - thread 1 calls its own open() / extract()
+ * - thread 2 simultaneously calls its own open() / extract()
+ *
+ * Also correct behaviour:
+ * - thread 1 calls mspack_create_cab_decompressor()
+ * - thread 1 locks a mutex for with the decompressor before
+ * calling any methods on it, and unlocks the mutex after each
+ * method returns.
+ * - thread 1 can share the results of open() with thread 2, and both
+ * can call extract(), provided they both guard against simultaneous
+ * use of extract(), and any other methods, with the mutex
+ */
+
+using LibMSPackSharp.Compression;
+
+namespace LibMSPackSharp
+{
+ public partial class Library
+ {
+ #region CAB
+
+ ///
+ /// Creates a new CAB compressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public CAB.Compressor CreateCABCompressor(SystemImpl sys)
+ {
+ // TODO
+ return null;
+ }
+
+ ///
+ /// Creates a new CAB decompressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public CAB.Decompressor CreateCABDecompressor(SystemImpl sys)
+ {
+ if (sys == null)
+ sys = SystemImpl.DefaultSystem;
+ if (!SystemImpl.ValidSystem(sys))
+ return null;
+
+ return new CAB.DecompressorImpl()
+ {
+ Open = CAB.Implementation.Open,
+ Close = CAB.Implementation.Close,
+ Search = CAB.Implementation.Search,
+ Extract = CAB.Implementation.Extract,
+ Prepend = CAB.Implementation.Prepend,
+ Append = CAB.Implementation.Append,
+ SetParam = CAB.Implementation.Param,
+ LastError = CAB.Implementation.LastError,
+ System = sys,
+ State = null,
+ Error = Error.MSPACK_ERR_OK,
+
+ SearchBufferSize = 32768,
+ FixMSZip = false,
+ BufferSize = 4096,
+ Salvage = false,
+ };
+ }
+
+ ///
+ /// Destroys an existing CAB compressor.
+ ///
+ /// the to destroy
+ public void DestroyCABCompressor(CAB.Compressor c)
+ {
+ // TODO
+ }
+
+ ///
+ /// Destroys an existing CAB decompressor.
+ ///
+ /// the to destroy
+ public void DestroyCABDecompressor(CAB.Decompressor d)
+ {
+ CAB.DecompressorImpl self = (CAB.DecompressorImpl)d;
+ if (self != null)
+ {
+ SystemImpl sys = self.System;
+ if (self.State != null)
+ {
+ if (self.State.InputFileHandle != null)
+ sys.Close(self.State.InputFileHandle);
+
+ CAB.Implementation.FreeDecompressionState(self);
+ sys.Free(self.State);
+ }
+
+ sys.Free(self);
+ }
+ }
+
+ #endregion
+
+ #region CHM
+
+ ///
+ /// Creates a new CHM compressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public CHM.Compressor CreateCHMCompressor(SystemImpl sys)
+ {
+ // TODO
+ return null;
+ }
+
+ ///
+ /// Creates a new CHM decompressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public CHM.Decompressor CreateCHMDecompressor(SystemImpl sys)
+ {
+ if (sys == null)
+ sys = SystemImpl.DefaultSystem;
+ if (!SystemImpl.ValidSystem(sys))
+ return null;
+
+ return new CHM.DecompressorImpl()
+ {
+ Open = CHM.Implementation.Open,
+ Close = CHM.Implementation.Close,
+ Extract = CHM.Implementation.Extract,
+ LastError = CHM.Implementation.LastError,
+ FastOpen = CHM.Implementation.FastOpen,
+ FastFind = CHM.Implementation.FastFind,
+ System = sys,
+ Error = Error.MSPACK_ERR_OK,
+ State = null,
+ };
+ }
+
+ ///
+ /// Destroys an existing CHM compressor.
+ ///
+ /// the to destroy
+ public void DestroyCHMCompressor(CHM.Compressor c)
+ {
+ // TODO
+ }
+
+ ///
+ /// Destroys an existing CHM decompressor.
+ ///
+ /// the to destroy
+ public void DestroyCHMDecompressor(CHM.Decompressor d)
+ {
+ CHM.DecompressorImpl self = (CHM.DecompressorImpl)d;
+ if (self != null)
+ {
+ SystemImpl sys = self.System;
+ if (self.State != null)
+ {
+ if (self.State.InputFileHandle != null)
+ sys.Close(self.State.InputFileHandle);
+
+ if (self.State.State != null)
+ LZX.Free(self.State.State);
+
+ sys.Free(self.State);
+ }
+ sys.Free(self);
+ }
+ }
+
+ #endregion
+
+ #region LIT
+
+ ///
+ /// Creates a new LIT compressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public LIT.Compressor CreateLITCompressor(SystemImpl sys)
+ {
+ // TODO
+ return null;
+ }
+
+ ///
+ /// Creates a new LIT decompressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public LIT.Decompressor CreateLITDecompressor(SystemImpl sys)
+ {
+ // TODO
+ return null;
+ }
+
+ ///
+ /// Destroys an existing LIT compressor.
+ ///
+ /// the to destroy
+ public void DestroyLITCompressor(LIT.Compressor c)
+ {
+ // TODO
+ }
+
+ ///
+ /// Destroys an existing LIT decompressor.
+ ///
+ /// the to destroy
+ public void DestroyLITDecompressor(LIT.Decompressor d)
+ {
+ // TODO
+ }
+
+ #endregion
+
+ #region HLP
+
+ ///
+ /// Creates a new HLP compressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public HLP.Compressor CreateHLPCompressor(SystemImpl sys)
+ {
+ // TODO
+ return null;
+ }
+
+ ///
+ /// Creates a new HLP decompressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public HLP.Decompressor CreateHLPDecompressor(SystemImpl sys)
+ {
+ // TODO
+ return null;
+ }
+
+ ///
+ /// Destroys an existing HLP compressor.
+ ///
+ /// the to destroy
+ public void DestroyHLPCompressor(HLP.Compressor c)
+ {
+ // TODO
+ }
+
+ ///
+ /// Destroys an existing HLP decompressor.
+ ///
+ /// the to destroy
+ public void DestroyHLPDecompressor(HLP.Decompressor d)
+ {
+ // TODO
+ }
+
+ #endregion
+
+ #region SZDD
+
+ ///
+ /// Creates a new SZDD compressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public SZDD.Compressor CreateSZDDCompressor(SystemImpl sys)
+ {
+ // TODO
+ return null;
+ }
+
+ ///
+ /// Creates a new SZDD decompressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public SZDD.Decompressor CreateSZDDDecompressor(SystemImpl sys)
+ {
+ if (sys == null)
+ sys = SystemImpl.DefaultSystem;
+ if (!SystemImpl.ValidSystem(sys))
+ return null;
+
+ return new SZDD.DecompressorImpl()
+ {
+ Open = SZDD.Implementation.Open,
+ Close = SZDD.Implementation.Close,
+ Extract = SZDD.Implementation.Extract,
+ Decompress = SZDD.Implementation.Decompress,
+ LastError = SZDD.Implementation.LastError,
+ System = sys,
+ Error = Error.MSPACK_ERR_OK,
+ };
+ }
+
+ ///
+ /// Destroys an existing SZDD compressor.
+ ///
+ /// the to destroy
+ public void DestroySZDDCompressor(SZDD.Compressor c)
+ {
+ // TODO
+ }
+
+ ///
+ /// Destroys an existing SZDD decompressor.
+ ///
+ /// the to destroy
+ public void DestroySZDDDecompressor(SZDD.Decompressor d)
+ {
+ SZDD.DecompressorImpl self = (SZDD.DecompressorImpl)d;
+ if (self != null)
+ {
+ SystemImpl sys = self.System;
+ sys.Free(self);
+ }
+ }
+
+ #endregion
+
+ #region KWAJ
+
+ ///
+ /// Creates a new KWAJ compressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public KWAJ.Compressor CreateKWAJCompressor(SystemImpl sys)
+ {
+ // TODO
+ return null;
+ }
+
+ ///
+ /// Creates a new KWAJ decompressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public KWAJ.Decompressor CreateKWAJDecompressor(SystemImpl sys)
+ {
+ if (sys == null)
+ sys = SystemImpl.DefaultSystem;
+ if (!SystemImpl.ValidSystem(sys))
+ return null;
+
+ return new KWAJ.DecompressorImpl()
+ {
+ Open = KWAJ.Implementation.Open,
+ Close = KWAJ.Implementation.Close,
+ Extract = KWAJ.Implementation.Extract,
+ Decompress = KWAJ.Implementation.Decompress,
+ LastError = KWAJ.Implementation.LastError,
+ System = sys,
+ Error = Error.MSPACK_ERR_OK,
+ };
+ }
+
+ ///
+ /// Destroys an existing KWAJ compressor.
+ ///
+ /// the to destroy
+ public void DestroyKWAJCompressor(KWAJ.Compressor c)
+ {
+ // TODO
+ }
+
+ ///
+ /// Destroys an existing KWAJ decompressor.
+ ///
+ /// the to destroy
+ public void DestroyKWAJDecompressor(KWAJ.Decompressor d)
+ {
+ KWAJ.DecompressorImpl self = (KWAJ.DecompressorImpl)d;
+ if (self != null)
+ {
+ SystemImpl sys = self.System;
+ sys.Free(self);
+ }
+ }
+
+ #endregion
+
+ #region OAB
+
+ ///
+ /// Creates a new OAB compressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public OAB.Compressor CreateOABCompressor(SystemImpl sys)
+ {
+ // TODO
+ return null;
+ }
+
+ ///
+ /// Creates a new OAB decompressor.
+ ///
+ /// a custom SystemImpl structure, or null to use the default
+ /// a or null
+ public OAB.Decompressor CreateOABDecompressor(SystemImpl sys)
+ {
+ if (sys == null)
+ sys = SystemImpl.DefaultSystem;
+ if (!SystemImpl.ValidSystem(sys))
+ return null;
+
+ return new OAB.DecompressorImpl()
+ {
+ Decompress = OAB.Implementation.Decompress,
+ DecompressIncremental = OAB.Implementation.DecompressIncremental,
+ SetParam = OAB.Implementation.Param,
+ System = sys,
+ BufferSize = 4096,
+ };
+ }
+
+ ///
+ /// Destroys an existing OAB compressor.
+ ///
+ /// the to destroy
+ public void DestroyOABCompressor(OAB.Compressor c)
+ {
+ // TODO
+ }
+
+ ///
+ /// Destroys an existing OAB decompressor.
+ ///
+ /// the to destroy
+ public void DestroyOABDecompressor(OAB.Decompressor d)
+ {
+ OAB.DecompressorImpl self = (OAB.DecompressorImpl)d;
+ if (self != null)
+ {
+ SystemImpl sys = self.System;
+ sys.Free(self);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/OAB/Compressor.cs b/BurnOutSharp/External/libmspack/OAB/Compressor.cs
new file mode 100644
index 00000000..6aba61a0
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/OAB/Compressor.cs
@@ -0,0 +1,80 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+
+namespace LibMSPackSharp.OAB
+{
+ ///
+ /// A compressor for the Offline Address Book (OAB) format.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ ///
+ ///
+ public class Compressor
+ {
+ ///
+ /// Compress a full OAB file.
+ ///
+ /// The input file will be read and the compressed contents written to the
+ /// output file.
+ ///
+ ///
+ /// a self-referential pointer to the msoab_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the input file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ ///
+ /// the filename of the output file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func Compress;
+
+ ///
+ /// Generate a compressed incremental OAB patch file.
+ ///
+ /// The two uncompressed files "input" and "base" will be read, and an
+ /// incremental patch to generate "input" from "base" will be written to
+ /// the output file.
+ ///
+ ///
+ /// a self-referential pointer to the msoab_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the input file containing the new
+ /// version of its contents. This is passed directly
+ /// to mspack_system::open().
+ ///
+ ///
+ /// the filename of the original base file containing
+ /// the old version of its contents, against which the
+ /// incremental patch shall generated. This is passed
+ /// directly to mspack_system::open().
+ ///
+ ///
+ /// the filename of the output file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func CompressIncremental;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/OAB/CompressorImpl.cs b/BurnOutSharp/External/libmspack/OAB/CompressorImpl.cs
new file mode 100644
index 00000000..8abb627a
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/OAB/CompressorImpl.cs
@@ -0,0 +1,16 @@
+/* This file is part of libmspack.
+ * © 2013 Intel Corporation
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.OAB
+{
+ public class CompressorImpl : Compressor
+ {
+ public SystemImpl System { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/OAB/Decompressor.cs b/BurnOutSharp/External/libmspack/OAB/Decompressor.cs
new file mode 100644
index 00000000..03862a04
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/OAB/Decompressor.cs
@@ -0,0 +1,106 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+
+namespace LibMSPackSharp.OAB
+{
+ ///
+ /// A decompressor for .LZX (Offline Address Book) files
+ ///
+ /// All fields are READ ONLY.
+ ///
+ ///
+ ///
+ public class Decompressor
+ {
+ ///
+ /// Decompresses a full Offline Address Book file.
+ ///
+ /// If the input file is a valid compressed Offline Address Book file,
+ /// it will be read and the decompressed contents will be written to
+ /// the output file.
+ ///
+ ///
+ /// a self-referential pointer to the msoab_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the input file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ ///
+ /// the filename of the output file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func Decompress;
+
+ ///
+ /// Decompresses an Offline Address Book with an incremental patch file.
+ ///
+ /// This requires both a full UNCOMPRESSED Offline Address Book file to
+ /// act as the "base", and a compressed incremental patch file as input.
+ /// If the input file is valid, it will be decompressed with reference to
+ /// the base file, and the decompressed contents will be written to the
+ /// output file.
+ ///
+ /// There is no way to tell what the right base file is for the given
+ /// incremental patch, but if you get it wrong, this will usually result
+ /// in incorrect data being decompressed, which will then fail a checksum
+ /// test.
+ ///
+ ///
+ /// a self-referential pointer to the msoab_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the input file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ ///
+ /// the filename of the base file to which the
+ /// incremental patch shall be applied. This is passed
+ /// directly to mspack_system::open().
+ ///
+ ///
+ /// the filename of the output file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func DecompressIncremental;
+
+ ///
+ /// Sets an OAB decompression engine parameter. Available only in OAB
+ /// decompressor version 2 and above.
+ ///
+ /// - #MSOABD_PARAM_DECOMPBUF: How many bytes should be used as an input
+ /// buffer by decompressors? The minimum value is 16. The default value
+ /// is 4096.
+ ///
+ ///
+ /// a self-referential pointer to the msoab_decompressor
+ /// instance being called
+ ///
+ /// the parameter to set
+ /// the value to set the parameter to
+ ///
+ /// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there
+ /// is a problem with either parameter or value.
+ ///
+ public Func SetParam;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/OAB/DecompressorImpl.cs b/BurnOutSharp/External/libmspack/OAB/DecompressorImpl.cs
new file mode 100644
index 00000000..4309d503
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/OAB/DecompressorImpl.cs
@@ -0,0 +1,20 @@
+/* This file is part of libmspack.
+ * © 2013 Intel Corporation
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.OAB
+{
+ public class DecompressorImpl : Decompressor
+ {
+ public SystemImpl System { get; set; }
+
+ public int BufferSize { get; set; }
+
+ // TODO
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/OAB/Enums.cs b/BurnOutSharp/External/libmspack/OAB/Enums.cs
new file mode 100644
index 00000000..b284c8cb
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/OAB/Enums.cs
@@ -0,0 +1,19 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.OAB
+{
+ public enum Parameters
+ {
+ ///
+ /// Size of decompression buffer
+ ///
+ MSOABD_PARAM_DECOMPBUF = 0,
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/OAB/Implementation.cs b/BurnOutSharp/External/libmspack/OAB/Implementation.cs
new file mode 100644
index 00000000..f0a2fb7f
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/OAB/Implementation.cs
@@ -0,0 +1,620 @@
+/* This file is part of libmspack.
+ * © 2013 Intel Corporation
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+/* The Exchange Online Addressbook (OAB or sometimes OAL) is distributed
+ * as a .LZX file in one of two forms. Either a "full download" containing
+ * the entire address list, or an incremental binary patch which should be
+ * applied to a previous version of the full decompressed data.
+ *
+ * The contents and format of the decompressed OAB are not handled here.
+ *
+ * For a complete description of the format, see the MSDN site:
+ *
+ * http://msdn.microsoft.com/en-us/library/cc463914 - [MS-OXOAB].pdf
+ * http://msdn.microsoft.com/en-us/library/cc483133 - [MS-PATCH].pdf
+ */
+
+using System;
+using LibMSPackSharp.Compression;
+
+namespace LibMSPackSharp.OAB
+{
+ public class Implementation
+ {
+ #region OAB decompression definitions
+
+ private const int oabhead_VersionHi = 0x0000;
+ private const int oabhead_VersionLo = 0x0004;
+ private const int oabhead_BlockMax = 0x0008;
+ private const int oabhead_TargetSize = 0x000c;
+ private const int oabhead_SIZEOF = 0x0010;
+
+ private const int oabblk_Flags = 0x0000;
+ private const int oabblk_CompSize = 0x0004;
+ private const int oabblk_UncompSize = 0x0008;
+ private const int oabblk_CRC = 0x000c;
+ private const int oabblk_SIZEOF = 0x0010;
+
+ private const int patchhead_VersionHi = 0x0000;
+ private const int patchhead_VersionLo = 0x0004;
+ private const int patchhead_BlockMax = 0x0008;
+ private const int patchhead_SourceSize = 0x000c;
+ private const int patchhead_TargetSize = 0x0010;
+ private const int patchhead_SourceCRC = 0x0014;
+ private const int patchhead_TargetCRC = 0x0018;
+ private const int patchhead_SIZEOF = 0x001c;
+
+ private const int patchblk_PatchSize = 0x0000;
+ private const int patchblk_TargetSize = 0x0004;
+ private const int patchblk_SourceSize = 0x0008;
+ private const int patchblk_CRC = 0x000c;
+ private const int patchblk_SIZEOF = 0x0010;
+
+ #endregion
+
+ #region OABD_SYS_READ
+
+ private static int SysRead(object baseFile, byte[] buf, int pointer, int size)
+ {
+ InternalFile file = (InternalFile)baseFile;
+ int bytes_read;
+
+ if (size > file.Available)
+ size = file.Available;
+
+ bytes_read = file.OrigSys.Read(file.OrigFile, buf, pointer, size);
+ if (bytes_read < 0)
+ return bytes_read;
+
+ file.Available -= bytes_read;
+ return bytes_read;
+ }
+
+ #endregion
+
+ #region OABD_SYS_WRITE
+
+ private static int SysWrite(object baseFile, byte[] buf, int pointer, int size)
+ {
+ InternalFile file = (InternalFile)baseFile;
+ int bytes_written = file.OrigSys.Write(file.OrigFile, buf, pointer, size);
+
+ if (bytes_written > 0)
+ file.CRC = Checksum.CRC32(buf, 0, bytes_written, file.CRC);
+
+ return bytes_written;
+ }
+
+ #endregion
+
+ #region OABD_DECOMPRESS
+
+ public static Error Decompress(Decompressor d, string input, string output)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ byte[] hdrbuf = new byte[oabhead_SIZEOF];
+ LZXDStream lzx = null;
+ Error ret = Error.MSPACK_ERR_OK;
+
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ SystemImpl sys = self.System;
+
+ object infh = sys.Open(sys, input, OpenMode.MSPACK_SYS_OPEN_READ);
+ if (infh == null)
+ {
+ ret = Error.MSPACK_ERR_OPEN;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (infh != null)
+ sys.Close(infh);
+
+ return ret;
+ }
+
+ if (sys.Read(infh, hdrbuf, 0, oabhead_SIZEOF) != oabhead_SIZEOF)
+ {
+ ret = Error.MSPACK_ERR_READ;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (infh != null)
+ sys.Close(infh);
+
+ return ret;
+ }
+
+ if (BitConverter.ToUInt32(hdrbuf, oabhead_VersionHi) != 3 ||
+ BitConverter.ToUInt32(hdrbuf, oabhead_VersionLo) != 1)
+ {
+ ret = Error.MSPACK_ERR_SIGNATURE;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (infh != null)
+ sys.Close(infh);
+
+ return ret;
+ }
+
+ uint block_max = BitConverter.ToUInt32(hdrbuf, oabhead_BlockMax);
+ uint target_size = BitConverter.ToUInt32(hdrbuf, oabhead_TargetSize);
+
+ object outfh = sys.Open(sys, output, OpenMode.MSPACK_SYS_OPEN_WRITE);
+ if (outfh == null)
+ {
+ ret = Error.MSPACK_ERR_OPEN;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ return ret;
+ }
+
+ byte[] buf = new byte[self.BufferSize];
+
+ SystemImpl oabd_sys = sys;
+ oabd_sys.Read = SysRead;
+ oabd_sys.Write = SysWrite;
+
+ InternalFile in_ofh = new InternalFile();
+ in_ofh.OrigSys = sys;
+ in_ofh.OrigFile = infh;
+
+ InternalFile out_ofh = new InternalFile();
+ out_ofh.OrigSys = sys;
+ out_ofh.OrigFile = outfh;
+
+ while (target_size != 0)
+ {
+ if (sys.Read(infh, buf, 0, oabblk_SIZEOF) != oabblk_SIZEOF)
+ {
+ ret = Error.MSPACK_ERR_READ;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ uint blk_flags = BitConverter.ToUInt32(buf, oabblk_Flags);
+ uint blk_csize = BitConverter.ToUInt32(buf, oabblk_CompSize);
+ uint blk_dsize = BitConverter.ToUInt32(buf, oabblk_UncompSize);
+ uint blk_crc = BitConverter.ToUInt32(buf, oabblk_CRC);
+
+ if (blk_dsize > block_max || blk_dsize > target_size || blk_flags > 1)
+ {
+ ret = Error.MSPACK_ERR_DATAFORMAT;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ if (blk_flags == 0)
+ {
+ // Uncompressed block
+ if (blk_dsize != blk_csize)
+ {
+ ret = Error.MSPACK_ERR_DATAFORMAT;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ ret = CopyFileHandle(sys, infh, outfh, (int)blk_dsize, buf, self.BufferSize);
+ if (ret != Error.MSPACK_ERR_OK)
+ {
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+ }
+ else
+ {
+ // LZX compressed block
+ int window_bits = 17;
+
+ while (window_bits < 25 && (1U << window_bits) < blk_dsize)
+ {
+ window_bits++;
+ }
+
+ in_ofh.Available = (int)blk_csize;
+ out_ofh.CRC = 0xffffffff;
+
+ lzx = LZX.Init(oabd_sys, in_ofh, out_ofh, window_bits, 0, self.BufferSize, blk_dsize, true);
+ if (lzx == null)
+ {
+ ret = Error.MSPACK_ERR_NOMEMORY;
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ ret = LZX.Decompress(lzx, blk_dsize);
+ if (ret != Error.MSPACK_ERR_OK)
+ {
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ LZX.Free(lzx);
+ lzx = null;
+
+ // Consume any trailing padding bytes before the next block
+ ret = CopyFileHandle(sys, infh, null, in_ofh.Available, buf, self.BufferSize);
+ if (ret != Error.MSPACK_ERR_OK)
+ {
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ if (out_ofh.CRC != blk_crc)
+ {
+ ret = Error.MSPACK_ERR_CHECKSUM;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+ }
+
+ target_size -= blk_dsize;
+ }
+
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ #endregion
+
+ #region OABD_DECOMPRESS_INCREMENTAL
+
+ public static Error DecompressIncremental(Decompressor d, string input, string basePath, string output)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ byte[] hdrbuf = new byte[patchhead_SIZEOF];
+ LZXDStream lzx = null;
+ int window_bits;
+ uint window_size;
+ Error ret = Error.MSPACK_ERR_OK;
+
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ SystemImpl sys = self.System;
+
+ object infh = sys.Open(sys, input, OpenMode.MSPACK_SYS_OPEN_READ);
+ if (infh == null)
+ {
+ ret = Error.MSPACK_ERR_OPEN;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (infh != null)
+ sys.Close(infh);
+
+ return ret;
+ }
+
+ if (sys.Read(infh, hdrbuf, 0, patchhead_SIZEOF) != patchhead_SIZEOF)
+ {
+ ret = Error.MSPACK_ERR_READ;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (infh != null)
+ sys.Close(infh);
+
+ return ret;
+ }
+
+ if (BitConverter.ToUInt32(hdrbuf, patchhead_VersionHi) != 3 ||
+ BitConverter.ToUInt32(hdrbuf, patchhead_VersionLo) != 2)
+ {
+ ret = Error.MSPACK_ERR_SIGNATURE;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (infh != null)
+ sys.Close(infh);
+
+ return ret;
+ }
+
+ uint block_max = BitConverter.ToUInt32(hdrbuf, patchhead_BlockMax);
+ uint target_size = BitConverter.ToUInt32(hdrbuf, patchhead_TargetSize);
+
+ // We use it for reading block headers too
+ if (block_max < patchblk_SIZEOF)
+ block_max = patchblk_SIZEOF;
+
+ object basefh = sys.Open(sys, basePath, OpenMode.MSPACK_SYS_OPEN_READ);
+ if (basefh == null)
+ {
+ ret = Error.MSPACK_ERR_OPEN;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ return ret;
+ }
+
+ object outfh = sys.Open(sys, output, OpenMode.MSPACK_SYS_OPEN_WRITE);
+ if (outfh == null)
+ {
+ ret = Error.MSPACK_ERR_OPEN;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ return ret;
+ }
+
+ byte[] buf = new byte[self.BufferSize];
+
+ SystemImpl oabd_sys = sys;
+ oabd_sys.Read = SysRead;
+ oabd_sys.Write = SysWrite;
+
+ InternalFile in_ofh = new InternalFile();
+ in_ofh.OrigSys = sys;
+ in_ofh.OrigFile = infh;
+
+ InternalFile out_ofh = new InternalFile();
+ out_ofh.OrigSys = sys;
+ out_ofh.OrigFile = outfh;
+
+ while (target_size != 0)
+ {
+ if (sys.Read(infh, buf, 0, patchblk_SIZEOF) != patchblk_SIZEOF)
+ {
+ ret = Error.MSPACK_ERR_READ;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ uint blk_csize = BitConverter.ToUInt32(buf, patchblk_PatchSize);
+ uint blk_dsize = BitConverter.ToUInt32(buf, patchblk_TargetSize);
+ uint blk_ssize = BitConverter.ToUInt32(buf, patchblk_SourceSize);
+ uint blk_crc = BitConverter.ToUInt32(buf, patchblk_CRC);
+
+ if (blk_dsize > block_max || blk_dsize > target_size || blk_ssize > block_max)
+ {
+ ret = Error.MSPACK_ERR_DATAFORMAT;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ window_size = (uint)((blk_ssize + 32767) & ~32767);
+ window_size += blk_dsize;
+ window_bits = 17;
+
+ while (window_bits < 25 && (1U << window_bits) < window_size)
+ window_bits++;
+
+ in_ofh.Available = (int)blk_csize;
+ out_ofh.CRC = 0xffffffff;
+
+ lzx = LZX.Init(oabd_sys, in_ofh, out_ofh, window_bits, 0, 4096, blk_dsize, true);
+ if (lzx == null)
+ {
+ ret = Error.MSPACK_ERR_NOMEMORY;
+ if (outfh != null)
+ sys.Close(outfh);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ ret = LZX.SetReferenceData(lzx, sys, basefh, blk_ssize);
+ if (ret != Error.MSPACK_ERR_OK)
+ {
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ ret = LZX.Decompress(lzx, blk_dsize);
+ if (ret != Error.MSPACK_ERR_OK)
+ {
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ LZX.Free(lzx);
+ lzx = null;
+
+ // Consume any trailing padding bytes before the next block
+ ret = CopyFileHandle(sys, infh, null, in_ofh.Available, buf, self.BufferSize);
+ if (ret != Error.MSPACK_ERR_OK)
+ {
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ if (out_ofh.CRC != blk_crc)
+ {
+ ret = Error.MSPACK_ERR_CHECKSUM;
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ target_size -= blk_dsize;
+ }
+
+ if (lzx != null)
+ LZX.Free(lzx);
+ if (outfh != null)
+ sys.Close(outfh);
+ if (basefh != null)
+ sys.Close(basefh);
+ if (infh != null)
+ sys.Close(infh);
+
+ sys.Free(buf);
+ return ret;
+ }
+
+ private static Error CopyFileHandle(SystemImpl sys, object infh, object outfh, int bytes_to_copy, byte[] buf, int buf_size)
+ {
+ while (bytes_to_copy != 0)
+ {
+ int run = buf_size;
+ if (run > bytes_to_copy)
+ run = bytes_to_copy;
+
+ if (sys.Read(infh, buf, 0, run) != run)
+ return Error.MSPACK_ERR_READ;
+
+ if (outfh != null && sys.Write(outfh, buf, 0, run) != run)
+ return Error.MSPACK_ERR_WRITE;
+
+ bytes_to_copy -= run;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region OABD_PARAM
+
+ public static Error Param(Decompressor d, Parameters param, int value)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ if (self != null && param == Parameters.MSOABD_PARAM_DECOMPBUF && value >= 16)
+ {
+ // must be at least 16 bytes (patchblk_SIZEOF, oabblk_SIZEOF)
+ self.BufferSize = value;
+ return Error.MSPACK_ERR_OK;
+ }
+
+ return Error.MSPACK_ERR_ARGS;
+ }
+
+ #endregion
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/OAB/InternalFile.cs b/BurnOutSharp/External/libmspack/OAB/InternalFile.cs
new file mode 100644
index 00000000..0a99a7f6
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/OAB/InternalFile.cs
@@ -0,0 +1,29 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.OAB
+{
+ public class InternalFile
+ {
+ public SystemImpl OrigSys { get; set; }
+
+ public object OrigFile { get; set; }
+
+ public uint CRC { get; set; }
+
+ public int Available { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/SZDD/Compressor.cs b/BurnOutSharp/External/libmspack/SZDD/Compressor.cs
new file mode 100644
index 00000000..18520701
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/SZDD/Compressor.cs
@@ -0,0 +1,110 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+
+namespace LibMSPackSharp.SZDD
+{
+ ///
+ /// A compressor for the SZDD file format.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ ///
+ ///
+ public class Compressor
+ {
+ ///
+ /// Reads an input file and creates a compressed output file in the
+ /// SZDD compressed file format. The SZDD compression format is quick
+ /// but gives poor compression. It is possible for the compressed output
+ /// file to be larger than the input file.
+ ///
+ /// Conventionally, SZDD compressed files have the final character in
+ /// their filename replaced with an underscore, to show they are
+ /// compressed. The missing character is stored in the compressed file
+ /// itself. This is due to the restricted filename conventions of MS-DOS,
+ /// most operating systems, such as UNIX, simply append another file
+ /// extension to the existing filename. As mspack does not deal with
+ /// filenames, this is left up to you. If you wish to set the missing
+ /// character stored in the file header, use set_param() with the
+ /// #MSSZDDC_PARAM_MISSINGCHAR parameter.
+ ///
+ /// "Stream" compression (where the length of the input data is not
+ /// known) is not possible. The length of the input data is stored in the
+ /// header of the SZDD file and must therefore be known before any data
+ /// is compressed. Due to technical limitations of the file format, the
+ /// maximum size of uncompressed file that will be accepted is 2147483647
+ /// bytes.
+ ///
+ ///
+ /// a self-referential pointer to the msszdd_compressor
+ /// instance being called
+ ///
+ ///
+ /// the name of the file to compressed. This is passed
+ /// passed directly to mspack_system::open()
+ ///
+ ///
+ /// the name of the file to write compressed data to.
+ /// This is passed directly to mspack_system::open().
+ ///
+ ///
+ /// the length of the uncompressed file, or -1 to indicate
+ /// that this should be determined automatically by using
+ /// mspack_system::seek() on the input file.
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ ///
+ public Func Compress;
+
+ ///
+ /// Sets an SZDD compression engine parameter.
+ ///
+ /// The following parameters are defined:
+ /// - #MSSZDDC_PARAM_CHARACTER: the "missing character", the last character
+ /// in the uncompressed file's filename, which is traditionally replaced
+ /// with an underscore to show the file is compressed. Traditionally,
+ /// this can only be a character that is a valid part of an MS-DOS,
+ /// filename, but libmspack permits any character between 0x00 and 0xFF
+ /// to be stored. 0x00 is the default, and it represents "no character
+ /// stored".
+ ///
+ ///
+ /// a self-referential pointer to the msszdd_compressor
+ /// instance being called
+ ///
+ /// the parameter to set
+ /// the value to set the parameter to
+ ///
+ /// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there
+ /// is a problem with either parameter or value.
+ ///
+ ///
+ public Func SetParam;
+
+ ///
+ /// Returns the error code set by the most recently called method.
+ ///
+ ///
+ /// a self-referential pointer to the msszdd_compressor
+ /// instance being called
+ ///
+ /// the most recent error code
+ ///
+ public Func LastError;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/SZDD/CompressorImpl.cs b/BurnOutSharp/External/libmspack/SZDD/CompressorImpl.cs
new file mode 100644
index 00000000..60b2495f
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/SZDD/CompressorImpl.cs
@@ -0,0 +1,18 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.SZDD
+{
+ public class CompressorImpl : Compressor
+ {
+ public SystemImpl System { get; set; }
+
+ public Error Error { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/SZDD/Decompressor.cs b/BurnOutSharp/External/libmspack/SZDD/Decompressor.cs
new file mode 100644
index 00000000..537ae425
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/SZDD/Decompressor.cs
@@ -0,0 +1,128 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+
+namespace LibMSPackSharp.SZDD
+{
+ ///
+ /// A decompressor for SZDD compressed files.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ ///
+ ///
+ public class Decompressor
+ {
+ ///
+ /// Opens a SZDD file and reads the header.
+ ///
+ /// If the file opened is a valid SZDD file, all headers will be read and
+ /// a msszddd_header structure will be returned.
+ ///
+ /// In the case of an error occuring, NULL is returned and the error code
+ /// is available from last_error().
+ ///
+ /// The filename pointer should be considered "in use" until close() is
+ /// called on the SZDD file.
+ ///
+ ///
+ /// a self-referential pointer to the msszdd_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the SZDD compressed file. This is
+ /// passed directly to mspack_system::open().
+ ///
+ /// a pointer to a msszddd_header structure, or NULL on failure
+ ///
+ public Func Open;
+
+ ///
+ /// Closes a previously opened SZDD file.
+ ///
+ /// This closes a SZDD file and frees the msszddd_header associated with
+ /// it.
+ ///
+ /// The SZDD header pointer is now invalid and cannot be used again.
+ ///
+ ///
+ /// a self-referential pointer to the msszdd_decompressor
+ /// instance being called
+ ///
+ /// the SZDD file to close
+ ///
+ public Action Close;
+
+ ///
+ /// Extracts the compressed data from a SZDD file.
+ ///
+ /// This decompresses the compressed SZDD data stream and writes it to
+ /// an output file.
+ ///
+ ///
+ /// a self-referential pointer to the msszdd_decompressor
+ /// instance being called
+ ///
+ /// the SZDD file to extract data from
+ ///
+ /// filename the filename to write the decompressed data to. This
+ /// is passed directly to mspack_system::open().
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func Extract;
+
+ ///
+ /// Decompresses an SZDD file to an output file in one step.
+ ///
+ /// This opens an SZDD file as input, reads the header, then decompresses
+ /// the compressed data immediately to an output file, finally closing
+ /// both the input and output file. It is more convenient to use than
+ /// open() then extract() then close(), if you do not need to know the
+ /// SZDD output size or missing character.
+ ///
+ ///
+ /// a self-referential pointer to the msszdd_decompressor
+ /// instance being called
+ ///
+ ///
+ /// the filename of the input SZDD file. This is passed
+ /// directly to mspack_system::open().
+ ///
+ ///
+ /// the filename to write the decompressed data to. This
+ /// is passed directly to mspack_system::open().
+ ///
+ /// an error code, or MSPACK_ERR_OK if successful
+ public Func Decompress;
+
+ ///
+ /// Returns the error code set by the most recently called method.
+ ///
+ /// This is useful for open() which does not return an
+ /// error code directly.
+ ///
+ ///
+ /// a self-referential pointer to the msszdd_decompressor
+ /// instance being called
+ ///
+ /// the most recent error code
+ ///
+ ///
+ ///
+ public Func LastError;
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/SZDD/DecompressorImpl.cs b/BurnOutSharp/External/libmspack/SZDD/DecompressorImpl.cs
new file mode 100644
index 00000000..161e8797
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/SZDD/DecompressorImpl.cs
@@ -0,0 +1,18 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.SZDD
+{
+ public class DecompressorImpl : Decompressor
+ {
+ public SystemImpl System { get; set; }
+
+ public Error Error { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/SZDD/Enums.cs b/BurnOutSharp/External/libmspack/SZDD/Enums.cs
new file mode 100644
index 00000000..d5d12882
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/SZDD/Enums.cs
@@ -0,0 +1,32 @@
+/* This file is part of libmspack.
+ * (C) 2003-2018 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.SZDD
+{
+ public enum Format
+ {
+ ///
+ /// a regular SZDD file
+ ///
+ MSSZDD_FMT_NORMAL = 0,
+
+ ///
+ /// a special QBasic SZDD file
+ ///
+ MSSZDD_FMT_QBASIC = 1,
+ }
+
+ public enum Parameters
+ {
+ ///
+ /// The missing character
+ ///
+ MSSZDDC_PARAM_MISSINGCHAR = 0,
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/SZDD/Header.cs b/BurnOutSharp/External/libmspack/SZDD/Header.cs
new file mode 100644
index 00000000..96767497
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/SZDD/Header.cs
@@ -0,0 +1,45 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+namespace LibMSPackSharp.SZDD
+{
+ ///
+ /// A structure which represents an SZDD compressed file.
+ ///
+ /// All fields are READ ONLY.
+ ///
+ public class Header
+ {
+ ///
+ /// The file format
+ ///
+ public Format Format { get; set; }
+
+ ///
+ /// The amount of data in the SZDD file once uncompressed.
+ ///
+ public long Length { get; set; }
+
+ ///
+ /// The last character in the filename, traditionally replaced with an
+ /// underscore to show the file is compressed. The null character is used
+ /// to show that this character has not been stored (e.g. because the
+ /// filename is not known). Generally, only characters that may appear in
+ /// an MS-DOS filename (except ".") are valid.
+ ///
+ public char MissingChar { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/SZDD/HeaderImpl.cs b/BurnOutSharp/External/libmspack/SZDD/HeaderImpl.cs
new file mode 100644
index 00000000..185d0699
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/SZDD/HeaderImpl.cs
@@ -0,0 +1,16 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+namespace LibMSPackSharp.SZDD
+{
+ public class HeaderImpl : Header
+ {
+ public object FileHandle { get; set; }
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/SZDD/Implementation.cs b/BurnOutSharp/External/libmspack/SZDD/Implementation.cs
new file mode 100644
index 00000000..e9b4f4bd
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/SZDD/Implementation.cs
@@ -0,0 +1,226 @@
+/* This file is part of libmspack.
+ * (C) 2003-2004 Stuart Caie.
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * For further details, see the file COPYING.LIB distributed with libmspack
+ */
+
+using System;
+using System.Linq;
+using LibMSPackSharp.Compression;
+
+namespace LibMSPackSharp.SZDD
+{
+ public class Implementation
+ {
+ ///
+ /// Input buffer size during decompression - not worth parameterising IMHO
+ ///
+ private const int SZDD_INPUT_SIZE = 2048;
+
+ #region SZDDD_OPEN
+
+ ///
+ /// Opens an SZDD file without decompressing, reads header
+ ///
+ public static Header Open(Decompressor d, string filename)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ if (self == null)
+ return null;
+
+ SystemImpl sys = self.System;
+
+ object fh = sys.Open(sys, filename, OpenMode.MSPACK_SYS_OPEN_READ);
+ HeaderImpl hdr = new HeaderImpl();
+ if (fh != null && hdr != null)
+ {
+ hdr.FileHandle = fh;
+ self.Error = ReadHeaders(sys, fh, hdr);
+ }
+ else
+ {
+ if (fh == null)
+ self.Error = Error.MSPACK_ERR_OPEN;
+ if (hdr == null)
+ self.Error = Error.MSPACK_ERR_NOMEMORY;
+ }
+
+ if (self.Error != Error.MSPACK_ERR_OK)
+ {
+ if (fh != null)
+ sys.Close(fh);
+
+ sys.Free(hdr);
+ hdr = null;
+ }
+
+ return hdr;
+ }
+
+ #endregion
+
+ #region SZDDD_CLOSE
+
+ ///
+ /// Closes an SZDD file
+ ///
+ public static void Close(Decompressor d, Header hdr)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ HeaderImpl hdr_p = (HeaderImpl)hdr;
+
+ if (self == null || self.System == null)
+ return;
+
+ // Close the file handle associated
+ self.System.Close(hdr_p.FileHandle);
+
+ // Free the memory associated
+ self.System.Free(hdr);
+
+ self.Error = Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region SZDDD_READ_HEADERS
+
+ private static byte[] expandSignature = new byte[8]
+ {
+ 0x53, 0x5A, 0x44, 0x44, 0x88, 0xF0, 0x27, 0x33
+ };
+
+ private static byte[] qbasicSignature = new byte[8]
+ {
+ 0x53, 0x5A, 0x20, 0x88, 0xF0, 0x27, 0x33, 0xD1
+ };
+
+ ///
+ /// Reads the headers of an SZDD format file
+ ///
+ public static Error ReadHeaders(SystemImpl sys, object fh, Header hdr)
+ {
+ // Read and check signature
+ byte[] buf = new byte[8];
+ if (sys.Read(fh, buf, 0, 8) != 8)
+ return Error.MSPACK_ERR_READ;
+
+ if (buf.SequenceEqual(expandSignature))
+ {
+ // Common SZDD
+ hdr.Format = Format.MSSZDD_FMT_NORMAL;
+
+ // Read the rest of the header
+ if (sys.Read(fh, buf, 0, 6) != 6)
+ return Error.MSPACK_ERR_READ;
+
+ if (buf[0] != 0x41)
+ return Error.MSPACK_ERR_DATAFORMAT;
+
+ hdr.MissingChar = (char)buf[1];
+ hdr.Length = BitConverter.ToUInt32(buf, 2);
+ }
+ if (buf.SequenceEqual(qbasicSignature))
+ {
+ // Special QBasic SZDD
+ hdr.Format = Format.MSSZDD_FMT_QBASIC;
+ if (sys.Read(fh, buf, 0, 4) != 4)
+ return Error.MSPACK_ERR_READ;
+
+ hdr.MissingChar = '\0';
+ hdr.Length = BitConverter.ToUInt32(buf, 0);
+ }
+ else
+ {
+ return Error.MSPACK_ERR_SIGNATURE;
+ }
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ #endregion
+
+ #region SZDDD_EXTRACT
+
+ ///
+ /// Decompresses an SZDD file
+ ///
+ public static Error Extract(Decompressor d, Header hdr, string filename)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+ if (hdr == null)
+ return self.Error = Error.MSPACK_ERR_ARGS;
+
+ SystemImpl sys = self.System;
+
+ object fh = ((HeaderImpl)hdr).FileHandle;
+
+ // Seek to the compressed data
+ long dataOffset = (hdr.Format == Format.MSSZDD_FMT_NORMAL) ? 14 : 12;
+ if (sys.Seek(fh, dataOffset, SeekMode.MSPACK_SYS_SEEK_START))
+ return self.Error = Error.MSPACK_ERR_SEEK;
+
+ // Open file for output
+ object outfh;
+ if ((outfh = sys.Open(sys, filename, OpenMode.MSPACK_SYS_OPEN_WRITE)) == null)
+ return self.Error = Error.MSPACK_ERR_OPEN;
+
+ // Decompress the data
+ self.Error = LZSS.Decompress(
+ sys,
+ fh,
+ outfh,
+ SZDD_INPUT_SIZE,
+ hdr.Format == Format.MSSZDD_FMT_NORMAL
+ ? LZSSMode.LZSS_MODE_EXPAND
+ : LZSSMode.LZSS_MODE_QBASIC);
+
+ // Close output file
+ sys.Close(outfh);
+
+ return self.Error;
+ }
+
+ #endregion
+
+ #region SZDDD_DECOMPRESS
+
+ ///
+ /// Unpacks directly from input to output
+ ///
+ public static Error Decompress(Decompressor d, string input, string output)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ if (self == null)
+ return Error.MSPACK_ERR_ARGS;
+
+ Header hdr;
+ if ((hdr = Open(d, input)) == null)
+ return self.Error;
+
+ Error error = Extract(d, hdr, output);
+ Close(d, hdr);
+ return self.Error = error;
+ }
+
+ #endregion
+
+ #region SZDDD_ERROR
+
+ ///
+ /// Returns the last error that occurred
+ ///
+ public static Error LastError(Decompressor d)
+ {
+ DecompressorImpl self = (DecompressorImpl)d;
+ return (self != null) ? self.Error : Error.MSPACK_ERR_ARGS;
+ }
+
+ #endregion
+ }
+}
diff --git a/BurnOutSharp/External/libmspack/SystemImpl.cs b/BurnOutSharp/External/libmspack/SystemImpl.cs
new file mode 100644
index 00000000..26cbaa7e
--- /dev/null
+++ b/BurnOutSharp/External/libmspack/SystemImpl.cs
@@ -0,0 +1,375 @@
+/* libmspack -- a library for working with Microsoft compression formats.
+ * (C) 2003-2019 Stuart Caie
+ *
+ * libmspack is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License (LGPL) version 2.1
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+using System;
+using System.IO;
+
+namespace LibMSPackSharp
+{
+ ///
+ /// A structure which abstracts file I/O and memory management.
+ ///
+ /// The library always uses the SystemImpl structure for interaction
+ /// with the file system and to allocate, free and copy all memory.It also
+ /// uses it to send literal messages to the library user.
+ ///
+ /// When the library is compiled normally, passing null to a compressor or
+ /// decompressor constructor will result in a default SystemImpl being
+ /// used, where all methods are implemented with the standard C library.
+ /// However, all constructors support being given a custom created
+ /// SystemImpl structure, with the library user's own methods. This
+ /// allows for more abstract interaction, such as reading and writing files
+ /// directly to memory, or from a network socket or pipe.
+ ///
+ /// Implementors of an SystemImpl structure should read all
+ /// documentation entries for every structure member, and write methods
+ /// which conform to those standards.
+ ///
+ public class SystemImpl
+ {
+ ///
+ /// Opens a file for reading, writing, appending or updating.
+ ///
+ ///
+ /// a self-referential pointer to the SystemImpl
+ /// structure whose Open() method is being called. If
+ /// this pointer is required by Close(), Read(), Write(),
+ /// Seek() or Tell(), it should be stored in the result
+ /// structure at this time.
+ ///
+ ///
+ /// the file to be opened. It is passed directly from the
+ /// library caller without being modified, so it is up to
+ /// the caller what this parameter actually represents.
+ ///
+ /// One of the values
+ ///
+ /// a pointer to a mspack_file structure. This structure officially
+ /// contains no members, its true contents are up to the
+ /// SystemImpl implementor. It should contain whatever is needed
+ /// for other SystemImpl methods to operate. Returning the null
+ /// pointer indicates an error condition.
+ ///
+ public Func Open;
+
+ ///
+ /// Closes a previously opened file. If any memory was allocated for this
+ /// particular file handle, it should be freed at this time.
+ ///
+ /// the file to close
+ ///
+ public Action Close;
+
+ ///
+ /// Reads a given number of bytes from an open file.
+ ///
+ /// the file to read from
+ /// the location where the read bytes should be stored
+ /// the number of bytes to read from the file.
+ ///
+ /// the number of bytes successfully read (this can be less than
+ /// the number requested), zero to mark the end of file, or less
+ /// than zero to indicate an error. The library does not "retry"
+ /// reads and assumes short reads are due to EOF, so you should
+ /// avoid returning short reads because of transient errors.
+ ///
+ ///
+ ///
+ public Func Read;
+
+ ///
+ /// Writes a given number of bytes to an open file.
+ ///
+ /// the file to write to
+ /// the location where the written bytes should be read from
+ /// the number of bytes to write to the file.
+ ///
+ /// the number of bytes successfully written, this can be less
+ /// than the number requested. Zero or less can indicate an error
+ /// where no bytes at all could be written. All cases where less
+ /// bytes were written than requested are considered by the library
+ /// to be an error.
+ ///
+ ///
+ ///
+ public Func Write;
+
+ ///
+ /// Seeks to a specific file offset within an open file.
+ ///
+ /// Sometimes the library needs to know the length of a file. It does
+ /// this by seeking to the end of the file with seek(file, 0,
+ /// MSPACK_SYS_SEEK_END), then calling Tell(). Implementations may want
+ /// to make a special case for this.
+ ///
+ /// Due to the potentially varying 32/64 bit datatype int on some
+ /// architectures, the #MSPACK_SYS_SELFTEST macro MUST be used before
+ /// using the library. If not, the error caused by the library passing an
+ /// inappropriate stackframe to Seek() is subtle and hard to trace.
+ ///
+ /// the file to be seeked
+ /// an offset to seek, measured in bytes
+ /// One of the values
+ /// zero for success, non-zero for an error
+ ///
+ ///
+ public Func Seek;
+
+ ///
+ /// Returns the current file position (in bytes) of the given file.
+ ///
+ /// the file whose file position is wanted
+ /// the current file position of the file
+ ///
+ ///
+ public Func Tell;
+
+ ///
+ /// Used to send messages from the library to the user.
+ ///
+ /// Occasionally, the library generates warnings or other messages in
+ /// plain english to inform the human user. These are informational only
+ /// and can be ignored if not wanted.
+ ///
+ ///
+ /// may be a file handle returned from Open() if this message
+ /// pertains to a specific open file, or null if not related to
+ /// a specific file.
+ ///
+ /// a printf() style format string. It does NOT include a trailing newline.
+ ///
+ public Action Message;
+
+ ///
+ /// Allocates memory.
+ ///
+ ///
+ /// a self-referential pointer to the SystemImpl
+ /// structure whose Alloc() method is being called.
+ ///
+ /// the number of bytes to allocate
+ ///
+ /// a pointer to the requested number of bytes, or null if
+ /// not enough memory is available
+ ///
+ ///
+ public Func Alloc;
+
+ ///
+ /// Frees memory.
+ ///
+ /// the memory to be freed. null is accepted and ignored.
+ ///
+ public Action Free;
+
+ ///
+ /// Copies from one region of memory to another.
+ ///
+ /// The regions of memory are guaranteed not to overlap, are usually less
+ /// than 256 bytes, and may not be aligned. Please note that the source
+ /// parameter comes before the destination parameter, unlike the standard
+ /// C function memcpy().
+ ///
+ /// the region of memory to copy from
+ /// the region of memory to copy to
+ /// the size of the memory region, in bytes
+ public Action Copy;
+
+ ///
+ /// A null pointer to mark the end of SystemImpl. It must equal null.
+ ///
+ /// Should the SystemImpl structure extend in the future, this null
+ /// will be seen, rather than have an invalid method pointer called.
+ ///
+ public readonly object NullPtr = null;
+
+ #region Helpers
+
+ ///
+ /// Returns the length of a file opened for reading
+ ///
+ public static Error GetFileLength(SystemImpl system, object file, out long length)
+ {
+ length = 0;
+ long current;
+
+ if (system == null || file == null)
+ return Error.MSPACK_ERR_OPEN;
+
+ // Get current offset
+ current = system.Tell(file);
+
+ // Seek to end of file
+ if (!system.Seek(file, 0, SeekMode.MSPACK_SYS_SEEK_END))
+ return Error.MSPACK_ERR_SEEK;
+
+ // Get offset of end of file
+ length = system.Tell(file);
+
+ // Seek back to original offset
+ if (!system.Seek(file, current, SeekMode.MSPACK_SYS_SEEK_START))
+ return Error.MSPACK_ERR_SEEK;
+
+ return Error.MSPACK_ERR_OK;
+ }
+
+ ///
+ /// Validates a system structure
+ ///
+ public static bool ValidSystem(SystemImpl sys)
+ {
+ return (sys != null) && (sys.Open != null) && (sys.Close != null) &&
+ (sys.Read != null) && (sys.Write != null) && (sys.Seek != null) &&
+ (sys.Tell != null) && (sys.Message != null) && (sys.Alloc != null) &&
+ (sys.Free != null) && (sys.Copy != null) && (sys.NullPtr == null);
+ }
+
+ #endregion
+
+ #region Default Implementation
+
+ public static SystemImpl DefaultSystem => new SystemImpl()
+ {
+ Open = DefaultOpen,
+ Close = DefaultClose,
+ Read = DefaultRead,
+ Write = DefaultWrite,
+ Seek = DefaultSeek,
+ Tell = DefaultTell,
+ Message = DefaultMessage,
+ Alloc = DefaultAlloc,
+ Free = DefaultFree,
+ Copy = DefaultCopy,
+ };
+
+ private static object DefaultOpen(SystemImpl self, string filename, OpenMode mode)
+ {
+ DefaultFileImpl fileHandle = new DefaultFileImpl();
+ switch (mode)
+ {
+ case OpenMode.MSPACK_SYS_OPEN_READ:
+ fileHandle.FileHandle = File.Open(filename, FileMode.Open, FileAccess.Read);
+ break;
+
+ case OpenMode.MSPACK_SYS_OPEN_WRITE:
+ fileHandle.FileHandle = File.Open(filename, FileMode.Open, FileAccess.Write);
+ break;
+
+ case OpenMode.MSPACK_SYS_OPEN_UPDATE:
+ fileHandle.FileHandle = File.Open(filename, FileMode.Open, FileAccess.ReadWrite);
+ break;
+
+ case OpenMode.MSPACK_SYS_OPEN_APPEND:
+ fileHandle.FileHandle = File.Open(filename, FileMode.Append);
+ break;
+
+ default:
+ return null;
+ }
+
+ return fileHandle;
+ }
+
+ private static void DefaultClose(object file)
+ {
+ DefaultFileImpl self = (DefaultFileImpl)file;
+ if (self != null)
+ self.FileHandle.Close();
+ }
+
+ private static int DefaultRead(object file, byte[] buffer, int pointer, int bytes)
+ {
+ DefaultFileImpl self = (DefaultFileImpl)file;
+ if (self != null && buffer != null && bytes >= 0)
+ {
+ try { return self.FileHandle.Read(buffer, pointer, bytes); }
+ catch { }
+ }
+
+ return -1;
+ }
+
+ private static int DefaultWrite(object file, byte[] buffer, int pointer, int bytes)
+ {
+ DefaultFileImpl self = (DefaultFileImpl)file;
+ if (self != null && buffer != null && bytes >= 0)
+ {
+ try { self.FileHandle.Write(buffer, pointer, bytes); }
+ catch { return -1; }
+ return bytes;
+ }
+ return -1;
+ }
+
+ private static bool DefaultSeek(object file, long offset, SeekMode mode)
+ {
+ DefaultFileImpl self = (DefaultFileImpl)file;
+ if (self != null)
+ {
+ switch (mode)
+ {
+ case SeekMode.MSPACK_SYS_SEEK_START:
+ try { self.FileHandle.Seek(offset, SeekOrigin.Begin); return true; }
+ catch { return false; }
+
+ case SeekMode.MSPACK_SYS_SEEK_CUR:
+ try { self.FileHandle.Seek(offset, SeekOrigin.Current); return true; }
+ catch { return false; }
+
+ case SeekMode.MSPACK_SYS_SEEK_END:
+ try { self.FileHandle.Seek(offset, SeekOrigin.End); return true; }
+ catch { return false; }
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ private static long DefaultTell(object file)
+ {
+ DefaultFileImpl self = (DefaultFileImpl)file;
+ return (self != null ? (int)self.FileHandle.Position : 0);
+ }
+
+ private static void DefaultMessage(object file, string format)
+ {
+ if (file != null)
+ Console.Error.Write($"{((DefaultFileImpl)file).Name}: ");
+
+ Console.Error.Write($"{format}\n");
+ }
+
+ private static byte[] DefaultAlloc(SystemImpl self, int bytes)
+ {
+ return new byte[bytes];
+ }
+
+ private static void DefaultFree(object buffer)
+ {
+ buffer = null;
+ }
+
+ private static void DefaultCopy(byte[] src, int srcPtr, byte[] dest, int destPtr, int bytes)
+ {
+ Array.Copy(src, srcPtr, dest, destPtr, bytes);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file