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