diff --git a/libmspack/Compressor.cs b/libmspack/Compressor.cs new file mode 100644 index 0000000..6c58243 --- /dev/null +++ b/libmspack/Compressor.cs @@ -0,0 +1,10 @@ +namespace SabreTools.Compression.libmspack +{ + /// + /// Base class for all compressor implementations + /// + public abstract class Compressor + { + public mspack_system system { get; set; } + } +} \ No newline at end of file diff --git a/libmspack/Decompressor.cs b/libmspack/Decompressor.cs new file mode 100644 index 0000000..1f8c949 --- /dev/null +++ b/libmspack/Decompressor.cs @@ -0,0 +1,10 @@ +namespace SabreTools.Compression.libmspack +{ + /// + /// Base class for all decompressor implementations + /// + public abstract class Decompressor + { + public mspack_system system { get; set; } + } +} \ No newline at end of file diff --git a/libmspack/cab.cs b/libmspack/cab.cs index a71792f..e3d91fb 100644 --- a/libmspack/cab.cs +++ b/libmspack/cab.cs @@ -1,6 +1,8 @@ +using System; + namespace SabreTools.Compression.libmspack { - public static class cab + public unsafe static class cab { /* structure offsets */ public const byte cfhead_Signature = 0x00; @@ -36,13 +38,6 @@ namespace SabreTools.Compression.libmspack /* flags */ public const ushort cffoldCOMPTYPE_MASK = 0x000f; - public const ushort cffoldCOMPTYPE_NONE = 0x0000; - public const ushort cffoldCOMPTYPE_MSZIP = 0x0001; - public const ushort cffoldCOMPTYPE_QUANTUM = 0x0002; - public const ushort cffoldCOMPTYPE_LZX = 0x0003; - public const ushort cfheadPREV_CABINET = 0x0001; - public const ushort cfheadNEXT_CABINET = 0x0002; - public const ushort cfheadRESERVE_PRESENT = 0x0004; public const ushort cffileCONTINUED_FROM_PREV = 0xFFFD; public const ushort cffileCONTINUED_TO_NEXT = 0xFFFE; public const ushort cffileCONTINUED_PREV_AND_NEXT = 0xFFFF; @@ -71,5 +66,91 @@ namespace SabreTools.Compression.libmspack */ public const int CAB_FOLDERMAX = 65535; public const int CAB_LENGTHMAX = CAB_BLOCKMAX * CAB_FOLDERMAX; + + #region decomp + + /// + /// cabd_free_decomp frees decompression state, according to which method + /// was used. + /// + public static MSPACK_ERR cabd_init_decomp(mscab_decompressor self, MSCAB_COMP ct) + { + mspack_file fh = self; + + self.d.comp_type = ct; + + switch ((MSCAB_COMP)((int)ct & cffoldCOMPTYPE_MASK)) + { + case MSCAB_COMP.MSCAB_COMP_NONE: + self.d = new mscabd_noned_decompress_state(); + self.d.state = noned_init(self.d.sys, fh, fh, self.buf_size); + break; + case MSCAB_COMP.MSCAB_COMP_MSZIP: + self.d = new mscabd_mszipd_decompress_state(); + self.d.state = mszipd_init(self.d.sys, fh, fh, self.buf_size, self.fix_mszip); + break; + case MSCAB_COMP.MSCAB_COMP_QUANTUM: + self.d = new mscabd_qtmd_decompress_state(); + self.d.state = qtmd_init(self.d.sys, fh, fh, ((int)ct >> 8) & 0x1f, self.buf_size); + break; + case MSCAB_COMP.MSCAB_COMP_LZX: + self.d = new mscabd_lzxd_decompress_state(); + self.d.state = lzxd_init(self.d.sys, fh, fh, ((int)ct >> 8) & 0x1f, 0, self.buf_size, 0, 0); + break; + default: + return self.error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + return self.error = (self.d.state != null) ? MSPACK_ERR.MSPACK_ERR_OK : MSPACK_ERR.MSPACK_ERR_NOMEMORY; + } + + /// + /// cabd_init_decomp initialises decompression state, according to which + /// decompression method was used. relies on self.d.folder being the same + /// as when initialised. + /// + public static void cabd_free_decomp(mscab_decompressor self) + { + if (self == null || self.d == null || self.d.state == null) return; + + switch ((MSCAB_COMP)((int)self.d.comp_type & cffoldCOMPTYPE_MASK)) + { + case MSCAB_COMP.MSCAB_COMP_NONE: noned_free((noned_state)self.d.state); break; + case MSCAB_COMP.MSCAB_COMP_MSZIP: mszipd_free((mszipd_stream)self.d.state); break; + case MSCAB_COMP.MSCAB_COMP_QUANTUM: qtmd_free((qtmd_stream)self.d.state); break; + case MSCAB_COMP.MSCAB_COMP_LZX: lzxd_free((lzxd_stream)self.d.state); break; + } + + //self.d.decompress = null; + self.d.state = null; + } + + #endregion + + #region noned_state + + public static noned_state noned_init(mspack_system sys, mspack_file @in, mspack_file @out, int bufsize) + { + noned_state state = new noned_state(); + + state.sys = sys; + state.i = @in; + state.o = @out; + state.buf = system.CreateArray(bufsize); + state.bufsize = bufsize; + return state; + } + + public static void noned_free(noned_state state) + { + mspack_system sys; + if (state != null) + { + sys = state.sys; + sys.free(state.buf); + //sys.free(state); + } + } + + #endregion } } \ No newline at end of file diff --git a/libmspack/kwajd_stream.cs b/libmspack/kwajd_stream.cs index 73eebb6..71def9f 100644 --- a/libmspack/kwajd_stream.cs +++ b/libmspack/kwajd_stream.cs @@ -3,28 +3,8 @@ using static SabreTools.Compression.libmspack.lzss; namespace SabreTools.Compression.libmspack { - public unsafe class kwajd_stream + public unsafe class kwajd_stream : readbits { - #region I/O buffering - - public mspack_system sys { get; set; } - - public mspack_file input { get; set; } - - public mspack_file output { get; set; } - - public byte* i_ptr { get; set; } - - public byte* i_end { get; set; } - - public uint bit_buffer { get; set; } - - public uint bits_left { get; set; } - - public int input_end { get; set; } - - #endregion - #region Huffman code lengths public byte[] MATCHLEN1_len { get; set; } = new byte[KWAJ_MATCHLEN1_SYMS]; @@ -55,7 +35,7 @@ namespace SabreTools.Compression.libmspack #region Input buffer - public byte[] inbuf { get; set; } = new byte[KWAJ_INPUT_SIZE]; + public new byte[] inbuf { get; set; } = new byte[KWAJ_INPUT_SIZE]; #endregion @@ -64,5 +44,17 @@ namespace SabreTools.Compression.libmspack public byte[] window { get; set; } = new byte[LZSS_WINDOW_SIZE]; #endregion + + public override void READ_BYTES() + { + if (i_ptr >= i_end) + { + if ((err = lzh_read_input(lzh))) + return err; + i_ptr = lzh.i_ptr; + i_end = lzh.i_end; + } + INJECT_BITS_MSB(*i_ptr++, 8); + } } } \ No newline at end of file diff --git a/libmspack/lzx.cs b/libmspack/lzx.cs index 3b9e0fa..ad2b907 100644 --- a/libmspack/lzx.cs +++ b/libmspack/lzx.cs @@ -34,7 +34,7 @@ namespace SabreTools.Compression.libmspack /// /// This routine uses system->alloc() to allocate memory. If memory /// allocation fails, or the parameters to this function are invalid, - /// NULL is returned. + /// null is returned. /// /// /// An mspack_system structure used to read from @@ -77,7 +77,7 @@ namespace SabreTools.Compression.libmspack /// non-zero for LZX DELTA encoded data. /// /// - /// A pointer to an initialised lzxd_stream structure, or NULL if + /// A pointer to an initialised lzxd_stream structure, or null if /// there was not enough memory or parameters to the function were wrong. /// public static lzxd_stream lzxd_init(mspack_system system, mspack_file input, mspack_file output, int window_bits, int reset_interval, int input_buffer_size, long output_length, char is_delta) => null; diff --git a/libmspack/lzxd_stream.cs b/libmspack/lzxd_stream.cs index db81352..1b1e972 100644 --- a/libmspack/lzxd_stream.cs +++ b/libmspack/lzxd_stream.cs @@ -2,23 +2,8 @@ using static SabreTools.Compression.libmspack.lzx; namespace SabreTools.Compression.libmspack { - public unsafe class lzxd_stream + public unsafe class lzxd_stream : readbits { - /// - /// I/O routines - /// - public mspack_system sys { get; set; } - - /// - /// Input file handle - /// - public mspack_file input { get; set; } - - /// - /// Output file handle - /// - public mspack_file output { get; set; } - /// /// Number of bytes actually output /// @@ -49,11 +34,6 @@ namespace SabreTools.Compression.libmspack /// public uint num_offsets { get; set; } - /// - /// Decompression offset within window - /// - public uint window_posn { get; set; } - /// /// Current frame offset within in window /// @@ -117,35 +97,13 @@ namespace SabreTools.Compression.libmspack /// /// Have we reached the end of input? /// - public byte input_end { get; set; } + public new byte input_end { get; set; } /// /// Does stream follow LZX DELTA spec? /// public byte is_delta { get; set; } - public MSPACK_ERR error { get; set; } - - #region I/O buffering - - public byte* inbuf { get; set; } - - public byte* i_ptr { get; set; } - - public byte* i_end { get; set; } - - public byte* o_ptr { get; set; } - - public byte* o_end { get; set; } - - public uint bit_buffer { get; set; } - - public uint bits_left { get; set; } - - public uint inbuf_size { get; set; } - - #endregion - #region Huffman code lengths public byte[] PRETREE_len { get; set; } = new byte[LZX_PRETREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; @@ -176,5 +134,15 @@ namespace SabreTools.Compression.libmspack /// This is used purely for doing the intel E8 transform /// public byte[] e8_buf { get; set; } = new byte[LZX_FRAME_SIZE]; + + public override void READ_BYTES() + { + byte b0, b1; + READ_IF_NEEDED(ref i_ptr, ref i_end); + b0 = *i_ptr++; + READ_IF_NEEDED(ref i_ptr, ref i_end); + b1 = *i_ptr++; + INJECT_BITS_MSB((b1 << 8) | b0, 16); + } } } \ No newline at end of file diff --git a/libmspack/mscab_compressor.cs b/libmspack/mscab_compressor.cs index f6429a6..d20c6fa 100644 --- a/libmspack/mscab_compressor.cs +++ b/libmspack/mscab_compressor.cs @@ -3,10 +3,8 @@ namespace SabreTools.Compression.libmspack /// /// TODO /// - public class mscab_compressor + public class mscab_compressor : Compressor { public int dummy { get; set; } - - public mspack_system system { get; set; } } } \ No newline at end of file diff --git a/libmspack/mscab_decompressor.cs b/libmspack/mscab_decompressor.cs index 00d380e..bfc95fb 100644 --- a/libmspack/mscab_decompressor.cs +++ b/libmspack/mscab_decompressor.cs @@ -1,3 +1,5 @@ +using static SabreTools.Compression.libmspack.cab; + namespace SabreTools.Compression.libmspack { /// @@ -7,12 +9,10 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract class mscab_decompressor + public unsafe class mscab_decompressor : Decompressor { public mscabd_decompress_state d { get; set; } - public mspack_system system { get; set; } - public int buf_size { get; set; } public int searchbuf_size { get; set; } @@ -32,7 +32,7 @@ namespace SabreTools.Compression.libmspack /// and a mscabd_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 + /// 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 @@ -42,11 +42,37 @@ namespace SabreTools.Compression.libmspack /// The filename of the cabinet file. This is passed /// directly to mspack_system::open(). /// - /// A pointer to a mscabd_cabinet structure, or NULL on failure + /// A pointer to a mscabd_cabinet structure, or null on failure /// /// /// - public abstract mscabd_cabinet open(in string filename); + public mscabd_cabinet open(in string filename) + { + mscabd_cabinet cab = null; + mspack_file fh; + MSPACK_ERR error; + + mspack_system sys = this.system; + if ((fh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ)) != null) + { + cab = new mscabd_cabinet(); + cab.filename = filename; + error = cabd_read_headers(sys, fh, cab, 0, this.salvage, 0); + if (error != MSPACK_ERR.MSPACK_ERR_OK) + { + close(cab); + cab = null; + } + this.error = error; + sys.close(fh); + } + else + { + this.error = MSPACK_ERR.MSPACK_ERR_OPEN; + } + + return cab; + } /// /// Closes a previously opened cabinet or cabinet set. @@ -77,7 +103,374 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract void close(mscabd_cabinet cab); + public void close(mscabd_cabinet origcab) + { + mscabd_folder_data dat, ndat; + mscabd_cabinet cab, ncab; + mscabd_folder fol, nfol; + mscabd_file fi, nfi; + + mspack_system sys = this.system; + + this.error = MSPACK_ERR.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 (this.d != null && (this.d.folder == fol)) + { + if (this.d.infh != null) sys.close(this.d.infh); + cabd_free_decomp(); + //sys.free(this.d); + this.d = null; + } + + // Free folder data segments + for (dat = 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.prevcab; + //sys.free(cab.prevname); + //sys.free(cab.nextname); + //sys.free(cab.previnfo); + //sys.free(cab.nextinfo); + //if (cab != origcab) sys.free(cab); + } + + // Free successor cabinets + for (cab = origcab.nextcab; cab != null; cab = ncab) + { + ncab = cab.nextcab; + //sys.free(cab.prevname); + //sys.free(cab.nextname); + //sys.free(cab.previnfo); + //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; + } + } + + /// + /// Reads the cabinet file header, folder list and file list. + /// Fills out a pre-existing mscabd_cabinet structure, allocates memory + /// for folders and files as necessary + /// + private static MSPACK_ERR cabd_read_headers(mspack_system sys, mspack_file fh, mscabd_cabinet cab, long offset, int salvage, int quiet) + { + int num_folders, num_files, folder_resv, i, x, fidx; + MSPACK_ERR err; + mscabd_folder fol, linkfol = null; + mscabd_file file, linkfile = null; + byte[] buf = new byte[64]; + + // Initialise pointers + cab.next = null; + cab.files = null; + cab.folders = null; + cab.prevcab = cab.nextcab = null; + cab.prevname = cab.nextname = null; + cab.previnfo = cab.nextinfo = null; + + cab.base_offset = offset; + + // Seek to CFHEADER + if (sys.seek(fh, offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0) + { + return MSPACK_ERR.MSPACK_ERR_SEEK; + } + + // Read in the CFHEADER + if (sys.read(fh, libmspack.system.GetArrayPointer(buf), cfhead_SIZEOF) != cfhead_SIZEOF) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + + // Check for "MSCF" signature + if (EndGetI32((byte*)buf[cfhead_Signature]) != 0x4643534D) + { + return MSPACK_ERR.MSPACK_ERR_SIGNATURE; + } + + // Some basic header fields + cab.length = EndGetI32((byte*)buf[cfhead_CabinetSize]); + cab.set_id = EndGetI16((byte*)buf[cfhead_SetID]); + cab.set_index = EndGetI16((byte*)buf[cfhead_CabinetIndex]); + + // Get the number of folders + num_folders = EndGetI16((byte*)buf[cfhead_NumFolders]); + if (num_folders == 0) + { + if (quiet == 0) sys.message(fh, "No folders in cabinet."); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // Get the number of files + num_files = EndGetI16((byte*)buf[cfhead_NumFiles]); + if (num_files == 0) + { + if (quiet == 0) sys.message(fh, "no files in cabinet."); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // Check cabinet version + if ((buf[cfhead_MajorVersion] != 1) && (buf[cfhead_MinorVersion] != 3)) + { + if (quiet == 0) sys.message(fh, "WARNING; cabinet version is not 1.3"); + } + + // Read the reserved-sizes part of header, if present + cab.flags = EndGetI16((byte*)buf[cfhead_Flags]); + + if (cab.flags.HasFlag(MSCAB_HDR.MSCAB_HDR_RESV)) + { + if (sys.read(fh, libmspack.system.GetArrayPointer(buf), cfheadext_SIZEOF) != cfheadext_SIZEOF) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + cab.header_resv = EndGetI16((byte*)buf[cfheadext_HeaderReserved]); + folder_resv = buf[cfheadext_FolderReserved]; + cab.block_resv = buf[cfheadext_DataReserved]; + + if (cab.header_resv > 60000) + { + if (quiet == 0) sys.message(fh, "WARNING; reserved header > 60000."); + } + + // Skip the reserved header + if (cab.header_resv != 0) + { + if (sys.seek(fh, cab.header_resv, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0) + { + return MSPACK_ERR.MSPACK_ERR_SEEK; + } + } + } + else + { + cab.header_resv = 0; + folder_resv = 0; + cab.block_resv = 0; + } + + // Read name and info of preceeding cabinet in set, if present + if (cab.flags.HasFlag(MSCAB_HDR.MSCAB_HDR_PREVCAB)) + { + cab.prevname = cabd_read_string(sys, fh, 0, out err); + if (err != MSPACK_ERR.MSPACK_ERR_OK) return err; + cab.previnfo = cabd_read_string(sys, fh, 1, out err); + if (err != MSPACK_ERR.MSPACK_ERR_OK) return err; + } + + // Read name and info of next cabinet in set, if present + if (cab.flags.HasFlag(MSCAB_HDR.MSCAB_HDR_NEXTCAB)) + { + cab.nextname = cabd_read_string(sys, fh, 0, out err); + if (err != MSPACK_ERR.MSPACK_ERR_OK) return err; + cab.nextinfo = cabd_read_string(sys, fh, 1, out err); + if (err != MSPACK_ERR.MSPACK_ERR_OK) return err; + } + + // Read folders + for (i = 0; i < num_folders; i++) + { + if (sys.read(fh, libmspack.system.GetArrayPointer(buf), cffold_SIZEOF) != cffold_SIZEOF) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + if (folder_resv != 0) + { + if (sys.seek(fh, folder_resv, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0) + { + return MSPACK_ERR.MSPACK_ERR_SEEK; + } + } + + fol = new mscabd_folder(); + + fol.next = null; + fol.comp_type = EndGetI16((byte*)buf[cffold_CompType]); + fol.num_blocks = EndGetI16((byte*)buf[cffold_NumBlocks]); + fol.data.next = null; + fol.data.cab = cab; + fol.data.offset = offset + (long)((uint)EndGetI32((byte*)buf[cffold_DataOffset])); + fol.merge_prev = null; + fol.merge_next = null; + + // 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, libmspack.system.GetArrayPointer(buf), cffile_SIZEOF) != cffile_SIZEOF) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + + file = new mscabd_file(); + + file.next = null; + file.length = EndGetI32((byte*)buf[cffile_UncompressedSize]); + file.attribs = EndGetI16((byte*)buf[cffile_Attribs]); + file.offset = EndGetI32((byte*)buf[cffile_FolderOffset]); + + // Set folder pointer + fidx = EndGetI16((byte*)buf[cffile_FolderIndex]); + if (fidx < cffileCONTINUED_FROM_PREV) + { + /* normal folder index; count up to the correct folder */ + if (fidx < num_folders) + { + mscabd_folder ifol = cab.folders; + while (fidx-- > 0) if (ifol != null) ifol = ifol.next; + file.folder = ifol; + } + else + { + System.Console.Error.WriteLine("Invalid folder index"); + file.folder = null; + } + } + else + { + // either CONTINUED_TO_NEXT, CONTINUED_FROM_PREV or CONTINUED_PREV_AND_NEXT + if ((fidx == cffileCONTINUED_TO_NEXT) || (fidx == cffileCONTINUED_PREV_AND_NEXT)) + { + // Get last folder + mscabd_folder ifol = cab.folders; + while (ifol.next != null) ifol = ifol.next; + file.folder = ifol; + + // Set "merge next" pointer + fol = ifol; + if (fol.merge_next == null) fol.merge_next = file; + } + + if ((fidx == cffileCONTINUED_FROM_PREV) || (fidx == cffileCONTINUED_PREV_AND_NEXT)) + { + // Get first folder + file.folder = cab.folders; + + // Set "merge prev" pointer + fol = file.folder; + if (fol.merge_prev == null) fol.merge_prev = file; + } + } + + // Get time + x = EndGetI16((byte*)buf[cffile_Time]); + file.time_h = (char)(x >> 11); + file.time_m = (char)((x >> 5) & 0x3F); + file.time_s = (char)((x << 1) & 0x3E); + + // Get date + x = EndGetI16((byte*)buf[cffile_Date]); + file.date_d = (char)(x & 0x1F); + file.date_m = (char)((x >> 5) & 0xF); + file.date_y = (x >> 9) + 1980; + + // Get filename + file.filename = cabd_read_string(sys, fh, 0, out err); + + // If folder index or filename are bad, either skip it or fail + if (err != MSPACK_ERR.MSPACK_ERR_OK || file.folder == null) + { + //sys.free(file.filename); + //sys.free(file); + if (salvage != 0) continue; + return err != MSPACK_ERR.MSPACK_ERR_OK ? err : MSPACK_ERR.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 + + System.Console.Error.WriteLine($"No files found, even though header claimed to have {num_files} files"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + return MSPACK_ERR.MSPACK_ERR_OK; + } + + private static string cabd_read_string(mspack_system sys, mspack_file fh, int permit_empty, out MSPACK_ERR error) + { + long @base = sys.tell(fh); + byte[] buf = new byte[256]; + string str; + int len, i, ok; + + // Read up to 256 bytes */ + if ((len = sys.read(fh, libmspack.system.GetArrayPointer(buf), 256)) <= 0) + { + error = MSPACK_ERR.MSPACK_ERR_READ; + return null; + } + + // Search for a null terminator in the buffer + for (i = 0, ok = 0; i < len; i++) if (buf[i] == 0) { ok = 1; break; } + /* optionally reject empty strings */ + if (i == 0 && permit_empty == 0) ok = 0; + + if (ok == 0) + { + error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + return null; + } + + len = i + 1; + + /* set the data stream to just after the string and return */ + if (sys.seek(fh, @base + len, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0) + { + error = MSPACK_ERR.MSPACK_ERR_SEEK; + return null; + } + + char[] strchr = new char[len]; + sys.copy(libmspack.system.GetArrayPointer(buf), libmspack.system.GetArrayPointer(strchr), len); + str = new string(strchr); + error = MSPACK_ERR.MSPACK_ERR_OK; + return str; + } /// /// Searches a regular file for embedded cabinets. @@ -94,11 +487,11 @@ namespace SabreTools.Compression.libmspack /// using the mscabd_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 + /// 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. + /// If no error occurs, but no cabinets can be found in the file, null is + /// returned and last_error() returns MSPACK_ERR.MSPACK_ERR_OK. /// /// The filename pointer should be considered in use until close() is /// called on the cabinet. @@ -110,7 +503,7 @@ namespace SabreTools.Compression.libmspack /// The filename of the file to search for cabinets. This /// is passed directly to mspack_system::open(). /// - /// A pointer to a mscabd_cabinet structure, or NULL + /// A pointer to a mscabd_cabinet structure, or null /// /// /// @@ -121,7 +514,7 @@ namespace SabreTools.Compression.libmspack /// set. /// /// This will attempt to append one cabinet to another such that - /// (cab->nextcab == nextcab) && (nextcab->prevcab == cab) and + /// (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 @@ -149,7 +542,7 @@ namespace SabreTools.Compression.libmspack /// /// 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 + /// An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful /// /// /// @@ -160,13 +553,13 @@ namespace SabreTools.Compression.libmspack /// cabinet set. /// /// This will attempt to prepend one cabinet to another, such that - /// (cab->prevcab == prevcab) && (prevcab->nextcab == cab). In + /// (cab.prevcab == prevcab) && (prevcab.nextcab == cab). In /// all other respects, it is identical to append(). See append() for the /// full documentation. /// /// The cabinet which will be prepended to, successor of prevcab /// The cabinet which will be prepended, predecessor of cab - /// An error code, or MSPACK_ERR_OK if successful + /// An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful /// /// /// @@ -190,7 +583,7 @@ namespace SabreTools.Compression.libmspack /// /// The file to be decompressed /// The filename of the file being written to - /// An error code, or MSPACK_ERR_OK if successful + /// An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful public abstract MSPACK_ERR extract(mscabd_file file, in string filename); /// @@ -210,12 +603,35 @@ namespace SabreTools.Compression.libmspack /// The parameter to set /// The value to set the parameter to /// - /// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there + /// MSPACK_ERR.MSPACK_ERR_OK if all is OK, or MSPACK_ERR.MSPACK_ERR_ARGS if there /// is a problem with either parameter or value. /// /// /// - public abstract MSPACK_ERR set_param(MSCABD_PARAM param, int value); + public MSPACK_ERR set_param(MSCABD_PARAM param, int value) + { + switch (param) + { + case MSCABD_PARAM.MSCABD_PARAM_SEARCHBUF: + if (value < 4) return MSPACK_ERR.MSPACK_ERR_ARGS; + this.searchbuf_size = value; + break; + case MSCABD_PARAM.MSCABD_PARAM_FIXMSZIP: + this.fix_mszip = value; + break; + case MSCABD_PARAM.MSCABD_PARAM_DECOMPBUF: + if (value < 4) return MSPACK_ERR.MSPACK_ERR_ARGS; + this.buf_size = value; + break; + case MSCABD_PARAM.MSCABD_PARAM_SALVAGE: + this.salvage = value; + break; + default: + return MSPACK_ERR.MSPACK_ERR_ARGS; + } + + return MSPACK_ERR.MSPACK_ERR_OK; + } /// /// Returns the error code set by the most recently called method. @@ -226,6 +642,9 @@ namespace SabreTools.Compression.libmspack /// The most recent error code /// /// - public abstract MSPACK_ERR last_error(); + public MSPACK_ERR last_error() + { + return this.error; + } } } \ No newline at end of file diff --git a/libmspack/mscabd_cabinet.cs b/libmspack/mscabd_cabinet.cs index 5806e92..dfb5588 100644 --- a/libmspack/mscabd_cabinet.cs +++ b/libmspack/mscabd_cabinet.cs @@ -15,7 +15,7 @@ namespace SabreTools.Compression.libmspack { /// /// 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 + /// mscab_decompressor::search(). May be null to mark the end of the /// list. /// public mscabd_cabinet next { get; set; } @@ -38,34 +38,34 @@ namespace SabreTools.Compression.libmspack public uint length { get; set; } /// - /// The previous cabinet in a cabinet set, or NULL. + /// The previous cabinet in a cabinet set, or null. /// public mscabd_cabinet prevcab { get; set; } /// - /// The next cabinet in a cabinet set, or NULL. + /// The next cabinet in a cabinet set, or null. /// public mscabd_cabinet nextcab { get; set; } /// - /// The filename of the previous cabinet in a cabinet set, or NULL. + /// The filename of the previous cabinet in a cabinet set, or null. /// public string prevname { get; set; } /// - /// The filename of the next cabinet in a cabinet set, or NULL. + /// 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 - /// set, or NULL. + /// set, or null. /// public string previnfo { get; set; } /// /// The name of the disk containing the next cabinet in a cabinet set, - /// or NULL. + /// or null. /// public string nextinfo { get; set; } diff --git a/libmspack/mscabd_decompress_state.cs b/libmspack/mscabd_decompress_state.cs index 39e27bf..c1ebcc0 100644 --- a/libmspack/mscabd_decompress_state.cs +++ b/libmspack/mscabd_decompress_state.cs @@ -42,15 +42,12 @@ namespace SabreTools.Compression.libmspack /// /// Decompressor code /// - /// - /// - /// - public abstract int decompress(void* data, long offset); + public abstract MSPACK_ERR decompress(object data, long offset); /// /// Decompressor state /// - public void* state { get; set; } + public object state { get; set; } /// /// Cabinet where input data comes from @@ -82,4 +79,25 @@ namespace SabreTools.Compression.libmspack /// public byte[] input { get; set; } = new byte[CAB_INPUTBUF]; } + + public unsafe class mscabd_noned_decompress_state : mscabd_decompress_state + { + /// + public override unsafe MSPACK_ERR decompress(object data, long bytes) + { + noned_state s = data as noned_state; + while (bytes > 0) + { + int run = (bytes > s.bufsize) ? s.bufsize : (int)bytes; + { + if (s.sys.read(s.i, &s.buf[0], run) != run) return MSPACK_ERR.MSPACK_ERR_READ; + if (s.sys.write(s.o, &s.buf[0], run) != run) return MSPACK_ERR.MSPACK_ERR_WRITE; + bytes -= run; + } + return MSPACK_ERR.MSPACK_ERR_OK; + } + + return MSPACK_ERR.MSPACK_ERR_DECRUNCH; + } + } } \ No newline at end of file diff --git a/libmspack/mscabd_file.cs b/libmspack/mscabd_file.cs index e16aa75..57682ed 100644 --- a/libmspack/mscabd_file.cs +++ b/libmspack/mscabd_file.cs @@ -8,7 +8,7 @@ namespace SabreTools.Compression.libmspack public class mscabd_file { /// - /// The next file in the cabinet or cabinet set, or NULL if this is the + /// The next file in the cabinet or cabinet set, or null if this is the /// final file. /// public mscabd_file next { get; set; } diff --git a/libmspack/mscabd_folder.cs b/libmspack/mscabd_folder.cs index 33ccdaf..31d17ff 100644 --- a/libmspack/mscabd_folder.cs +++ b/libmspack/mscabd_folder.cs @@ -12,7 +12,7 @@ namespace SabreTools.Compression.libmspack public class mscabd_folder { /// - /// A pointer to the next folder in this cabinet or cabinet set, or NULL + /// A pointer to the next folder in this cabinet or cabinet set, or null /// if this is the final folder. /// public mscabd_folder next { get; set; } diff --git a/libmspack/mschm_compressor.cs b/libmspack/mschm_compressor.cs index 04f8529..19d8820 100644 --- a/libmspack/mschm_compressor.cs +++ b/libmspack/mschm_compressor.cs @@ -7,10 +7,8 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract class mschm_compressor + public abstract class mschm_compressor : Compressor { - public mspack_system system { get; set; } - public string temp_file { get; set; } public int use_temp_file { get; set; } diff --git a/libmspack/mschm_decompressor.cs b/libmspack/mschm_decompressor.cs index 315ff74..4015593 100644 --- a/libmspack/mschm_decompressor.cs +++ b/libmspack/mschm_decompressor.cs @@ -7,10 +7,8 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract class mschm_decompressor + public abstract class mschm_decompressor : Decompressor { - public mspack_system system { get; set; } - public mschmd_decompress_state d { get; set; } public MSPACK_ERR error { get; set; } @@ -22,7 +20,7 @@ namespace SabreTools.Compression.libmspack /// 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 + /// 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 @@ -32,7 +30,7 @@ namespace SabreTools.Compression.libmspack /// 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 + /// A pointer to a mschmd_header structure, or null on failure /// public abstract mschmd_header open(in string filename); @@ -86,11 +84,11 @@ namespace SabreTools.Compression.libmspack /// /// 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 + /// 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 + /// 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 @@ -100,7 +98,7 @@ namespace SabreTools.Compression.libmspack /// 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 + /// A pointer to a mschmd_header structure, or null on failure /// /// /// @@ -123,14 +121,14 @@ namespace SabreTools.Compression.libmspack /// - 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 + /// - 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 + /// - section: null /// - offset: 0 /// - length: 0 - /// - all other structure elements: NULL or 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 diff --git a/libmspack/mschmd_file.cs b/libmspack/mschmd_file.cs index e5a62fe..0b92633 100644 --- a/libmspack/mschmd_file.cs +++ b/libmspack/mschmd_file.cs @@ -8,7 +8,7 @@ namespace SabreTools.Compression.libmspack public class mschmd_file { /// - /// A pointer to the next file in the list, or NULL if this is the final + /// A pointer to the next file in the list, or null if this is the final /// file. /// public mschmd_file next { get; set; } diff --git a/libmspack/mshlp_compressor.cs b/libmspack/mshlp_compressor.cs index 9d1bd1d..0c4b44f 100644 --- a/libmspack/mshlp_compressor.cs +++ b/libmspack/mshlp_compressor.cs @@ -3,10 +3,8 @@ namespace SabreTools.Compression.libmspack /// /// TODO /// - public class mshlp_compressor + public class mshlp_compressor : Compressor { public int dummy { get; set; } - - public mspack_system system { get; set; } } } \ No newline at end of file diff --git a/libmspack/mshlp_decompressor.cs b/libmspack/mshlp_decompressor.cs index 18b2933..4b9947a 100644 --- a/libmspack/mshlp_decompressor.cs +++ b/libmspack/mshlp_decompressor.cs @@ -3,10 +3,8 @@ namespace SabreTools.Compression.libmspack /// /// TODO /// - public class mshlp_decompressor + public class mshlp_decompressor : Decompressor { public int dummy { get; set; } - - public mspack_system system { get; set; } } } \ No newline at end of file diff --git a/libmspack/mskwaj_compressor.cs b/libmspack/mskwaj_compressor.cs index 57f0a49..e7de9d2 100644 --- a/libmspack/mskwaj_compressor.cs +++ b/libmspack/mskwaj_compressor.cs @@ -7,7 +7,7 @@ namespace SabreTools.Compression.libmspack /// /// /// - public unsafe abstract class mskwaj_compressor + public unsafe abstract class mskwaj_compressor : Compressor { public mspack_system system { get; set; } @@ -70,7 +70,7 @@ namespace SabreTools.Compression.libmspack /// 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 + /// If null is passed as the filename, no filename is included in the /// header. This is the default. /// /// The original filename to use @@ -86,7 +86,7 @@ namespace SabreTools.Compression.libmspack /// 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 + /// 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. /// diff --git a/libmspack/mskwaj_decompressor.cs b/libmspack/mskwaj_decompressor.cs index 1e2962a..45478f4 100644 --- a/libmspack/mskwaj_decompressor.cs +++ b/libmspack/mskwaj_decompressor.cs @@ -7,10 +7,8 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract class mskwaj_decompressor + public abstract class mskwaj_decompressor : Decompressor { - public mspack_system system { get; set; } - public MSPACK_ERR error { get; set; } /// @@ -19,7 +17,7 @@ namespace SabreTools.Compression.libmspack /// 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 + /// 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 @@ -29,7 +27,7 @@ namespace SabreTools.Compression.libmspack /// 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 + /// A pointer to a mskwajd_header structure, or null on failure /// public abstract mskwajd_header open(in string filename); diff --git a/libmspack/mskwajd_header.cs b/libmspack/mskwajd_header.cs index fda8fff..0740d3b 100644 --- a/libmspack/mskwajd_header.cs +++ b/libmspack/mskwajd_header.cs @@ -28,7 +28,7 @@ namespace SabreTools.Compression.libmspack public long length { get; set; } /// - /// Output filename, or NULL if not present + /// Output filename, or null if not present /// public string filename { get; set; } diff --git a/libmspack/mslit_compressor.cs b/libmspack/mslit_compressor.cs index 4c423f3..9d40235 100644 --- a/libmspack/mslit_compressor.cs +++ b/libmspack/mslit_compressor.cs @@ -3,10 +3,8 @@ namespace SabreTools.Compression.libmspack /// /// TODO /// - public class mslit_compressor + public class mslit_compressor : Compressor { public int dummy { get; set; } - - public mspack_system system { get; set; } } } \ No newline at end of file diff --git a/libmspack/mslit_decompressor.cs b/libmspack/mslit_decompressor.cs index d9c5bce..5d8d609 100644 --- a/libmspack/mslit_decompressor.cs +++ b/libmspack/mslit_decompressor.cs @@ -3,10 +3,8 @@ namespace SabreTools.Compression.libmspack /// /// TODO /// - public class mslit_decompressor + public class mslit_decompressor : Decompressor { public int dummy { get; set; } - - public mspack_system system { get; set; } } } \ No newline at end of file diff --git a/libmspack/msoab_compressor.cs b/libmspack/msoab_compressor.cs index bbe93ca..dba4489 100644 --- a/libmspack/msoab_compressor.cs +++ b/libmspack/msoab_compressor.cs @@ -7,10 +7,8 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract class msoab_compressor + public abstract class msoab_compressor : Compressor { - public mspack_system system { get; set; } - /// /// Compress a full OAB file. /// diff --git a/libmspack/msoab_decompressor.cs b/libmspack/msoab_decompressor.cs index 933a114..b9bb32e 100644 --- a/libmspack/msoab_decompressor.cs +++ b/libmspack/msoab_decompressor.cs @@ -9,10 +9,8 @@ namespace SabreTools.Compression.libmspack /// /// /// - public unsafe class msoab_decompressor + public unsafe class msoab_decompressor : Decompressor { - public mspack_system system { get; set; } - public int buf_size { get; set; } /// @@ -33,56 +31,54 @@ namespace SabreTools.Compression.libmspack /// An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful public MSPACK_ERR decompress(in string input, in string output) { - msoab_decompressor_p* self = (msoab_decompressor_p*)_self; - mspack_system* sys; - mspack_file* infh = NULL; - mspack_file* outfh = NULL; - byte* buf = NULL; + mspack_system sys; + mspack_file infh = null; + mspack_file outfh = null; + byte* buf = null; byte[] hdrbuf = new byte[oabhead_SIZEOF]; uint block_max, target_size; - lzxd_stream* lzx = NULL; - mspack_system oabd_sys; + lzxd_stream lzx = null; + mspack_oab_system oabd_sys; oabd_file in_ofh, out_ofh; uint window_bits; - int ret = MSPACK_ERR_OK; + MSPACK_ERR ret = MSPACK_ERR.MSPACK_ERR_OK; - if (!self) return MSPACK_ERR_ARGS; - sys = self->system; + sys = this.system; - infh = sys->open(sys, input, MSPACK_SYS_OPEN_READ); + infh = sys.open(sys, input, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ); if (!infh) { - ret = MSPACK_ERR_OPEN; + ret = MSPACK_ERR.MSPACK_ERR_OPEN; goto outlbl; } - if (sys->read(infh, hdrbuf, oabhead_SIZEOF) != oabhead_SIZEOF) + if (sys.read(infh, hdrbuf, oabhead_SIZEOF) != oabhead_SIZEOF) { - ret = MSPACK_ERR_READ; + ret = MSPACK_ERR.MSPACK_ERR_READ; goto outlbl; } if (EndGetI32(&hdrbuf[oabhead_VersionHi]) != 3 || EndGetI32(&hdrbuf[oabhead_VersionLo]) != 1) { - ret = MSPACK_ERR_SIGNATURE; + ret = MSPACK_ERR.MSPACK_ERR_SIGNATURE; goto outlbl; } block_max = EndGetI32(&hdrbuf[oabhead_BlockMax]); target_size = EndGetI32(&hdrbuf[oabhead_TargetSize]); - outfh = sys->open(sys, output, MSPACK_SYS_OPEN_WRITE); + outfh = sys.open(sys, output, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE); if (!outfh) { - ret = MSPACK_ERR_OPEN; + ret = MSPACK_ERR.MSPACK_ERR_OPEN; goto outlbl; } - buf = sys->alloc(sys, self->buf_size); + buf = sys.alloc(sys, this.buf_size); if (!buf) { - ret = MSPACK_ERR_NOMEMORY; + ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; goto outlbl; } @@ -100,9 +96,9 @@ namespace SabreTools.Compression.libmspack { uint blk_csize, blk_dsize, blk_crc, blk_flags; - if (sys->read(infh, buf, oabblk_SIZEOF) != oabblk_SIZEOF) + if (sys.read(infh, buf, oabblk_SIZEOF) != oabblk_SIZEOF) { - ret = MSPACK_ERR_READ; + ret = MSPACK_ERR.MSPACK_ERR_READ; goto outlbl; } blk_flags = EndGetI32(&buf[oabblk_Flags]); @@ -112,7 +108,7 @@ namespace SabreTools.Compression.libmspack if (blk_dsize > block_max || blk_dsize > target_size || blk_flags > 1) { - ret = MSPACK_ERR_DATAFORMAT; + ret = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; goto outlbl; } @@ -121,10 +117,10 @@ namespace SabreTools.Compression.libmspack /* Uncompressed block */ if (blk_dsize != blk_csize) { - ret = MSPACK_ERR_DATAFORMAT; + ret = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; goto outlbl; } - ret = copy_fh(sys, infh, outfh, blk_dsize, buf, self->buf_size); + ret = copy_fh(sys, infh, outfh, blk_dsize, buf, this.buf_size); if (ret) goto outlbl; } else @@ -139,27 +135,27 @@ namespace SabreTools.Compression.libmspack out_ofh.crc = 0xffffffff; lzx = lzxd_init(&oabd_sys, (void*)&in_ofh, (void*)&out_ofh, window_bits, - 0, self->buf_size, blk_dsize, 1); + 0, this.buf_size, blk_dsize, 1); if (!lzx) { - ret = MSPACK_ERR_NOMEMORY; + ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; goto outlbl; } ret = lzxd_decompress(lzx, blk_dsize); - if (ret != MSPACK_ERR_OK) + if (ret != MSPACK_ERR.MSPACK_ERR_OK) goto outlbl; lzxd_free(lzx); - lzx = NULL; + lzx = null; /* Consume any trailing padding bytes before the next block */ - ret = copy_fh(sys, infh, NULL, in_ofh.available, buf, self->buf_size); + ret = copy_fh(sys, infh, null, in_ofh.available, buf, this.buf_size); if (ret) goto outlbl; if (out_ofh.crc != blk_crc) { - ret = MSPACK_ERR_CHECKSUM; + ret = MSPACK_ERR.MSPACK_ERR_CHECKSUM; goto outlbl; } } @@ -168,9 +164,9 @@ namespace SabreTools.Compression.libmspack outlbl: if (lzx) lzxd_free(lzx); - if (outfh) sys->close(outfh); - if (infh) sys->close(infh); - sys->free(buf); + if (outfh) sys.close(outfh); + if (infh) sys.close(infh); + sys.free(buf); return ret; } diff --git a/libmspack/mspack.cs b/libmspack/mspack.cs index d45839a..05981d9 100644 --- a/libmspack/mspack.cs +++ b/libmspack/mspack.cs @@ -8,19 +8,18 @@ namespace SabreTools.Compression.libmspack /// /// Creates a new CAB compressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mscab_compressor mspack_create_cab_compressor(mspack_system sys) => null; /// /// Creates a new CAB decompressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mscab_decompressor mspack_create_cab_decompressor(mspack_system sys) { - if (sys == null) - return null; + if (sys == null) sys = new mspack_mscab_system(); var self = new mscab_decompressor { @@ -66,15 +65,15 @@ namespace SabreTools.Compression.libmspack /// /// Creates a new CHM compressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mschm_compressor mspack_create_chm_compressor(mspack_system sys) => null; /// /// Creates a new CHM decompressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mschm_decompressor mspack_create_chm_decompressor(mspack_system sys) => throw new NotImplementedException(); /// @@ -92,15 +91,15 @@ namespace SabreTools.Compression.libmspack /// /// Creates a new LIT compressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mslit_compressor mspack_create_lit_compressor(mspack_system sys) => null; /// /// Creates a new LIT decompressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mslit_decompressor mspack_create_lit_decompressor(mspack_system sys) => null; /// @@ -118,15 +117,15 @@ namespace SabreTools.Compression.libmspack /// /// Creates a new HLP compressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mshlp_compressor mspack_create_hlp_compressor(mspack_system sys) => null; /// /// Creates a new HLP decompressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mshlp_decompressor mspack_create_hlp_decompressor(mspack_system sys) => null; /// @@ -144,15 +143,15 @@ namespace SabreTools.Compression.libmspack /// /// Creates a new SZDD compressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static msszdd_compressor mspack_create_szdd_compressor(mspack_system sys) => null; /// /// Creates a new SZDD decompressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static msszdd_decompressor mspack_create_szdd_decompressor(mspack_system sys) { msszdd_decompressor self = null; @@ -188,15 +187,15 @@ namespace SabreTools.Compression.libmspack /// /// Creates a new KWAJ compressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mskwaj_compressor mspack_create_kwaj_compressor(mspack_system sys) => null; /// /// Creates a new KWAJ decompressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL + /// A custom mspack_system structure, or null to use the default + /// A or null public static mskwaj_decompressor mspack_create_kwaj_decompressor(mspack_system sys) => throw new NotImplementedException(); /// @@ -214,22 +213,37 @@ namespace SabreTools.Compression.libmspack /// /// Creates a new OAB compressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL - public static msoab_compressor mspack_create_oab_compressor(mspack_system sys) => null; + /// A custom mspack_system structure, or null to use the default + /// A or null + public static msoab_compressor mspack_create_oab_compressor(mspack_system sys) => throw new NotImplementedException(); /// /// Creates a new OAB decompressor. /// - /// A custom mspack_system structure, or NULL to use the default - /// A or NULL - public static msoab_decompressor mspack_create_oab_decompressor(mspack_system sys) => throw new NotImplementedException(); + /// A custom mspack_system structure, or null to use the default + /// A or null + public static msoab_decompressor mspack_create_oab_decompressor(mspack_system sys) + { + if (sys == null) sys = new mspack_oab_system(); + + msoab_decompressor self = new msoab_decompressor(); + self.system = sys; + self.buf_size = 4096; + return self; + } /// /// Destroys an existing OAB compressor. /// /// The to destroy - public static void mspack_destroy_oab_compressor(msoab_compressor self) { } + public static void mspack_destroy_oab_compressor(msoab_compressor self) + { + if (self != null) + { + mspack_system sys = self.system; + //sys.free(self); + } + } /// /// Destroys an existing OAB decompressor. diff --git a/libmspack/mspack_mscab_system.cs b/libmspack/mspack_mscab_system.cs new file mode 100644 index 0000000..1fc822b --- /dev/null +++ b/libmspack/mspack_mscab_system.cs @@ -0,0 +1,230 @@ +using static SabreTools.Compression.libmspack.cab; + +namespace SabreTools.Compression.libmspack +{ + public unsafe class mspack_mscab_system : mspack_default_system + { + /// + /// cabd_sys_read is 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 + /// + public override unsafe int read(mspack_file file, void* buffer, int bytes) + { + mscab_decompressor self = (mscab_decompressor)file; + byte* buf = (byte*)buffer; + mspack_system sys = self.system; + int avail, todo, outlen, ignore_cksum, ignore_blocksize; + + ignore_cksum = self.salvage != 0 || (self.fix_mszip != 0 && (((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == cffoldCOMPTYPE_MSZIP)) == true ? 1 : 0; + ignore_blocksize = self.salvage; + + todo = bytes; + while (todo > 0) + { + avail = (int)(self.d.i_end - self.d.i_ptr); + + // 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.d.i_ptr, buf, avail); + self.d.i_ptr += avail; + buf += avail; + todo -= avail; + } + else + { + // Out of data, read a new block + + // Check if we're out of input blocks, advance block counter + if (self.d.block++ >= self.d.folder.num_blocks) + { + if (self.salvage == 0) + { + self.read_error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + else + { + System.Console.Error.WriteLine("Ran out of CAB input blocks prematurely"); + } + break; + } + + // Read a block + self.read_error = cabd_sys_read_block(sys, self.d, ref outlen, ignore_cksum, ignore_blocksize); + if (self.read_error != MSPACK_ERR.MSPACK_ERR_OK) return -1; + self.d.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 (((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == cffoldCOMPTYPE_QUANTUM) + { + *self.d.i_end++ = 0xFF; + } + + // Is this the last block? + if (self.d.block >= self.d.folder.num_blocks) + { + if (((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == cffoldCOMPTYPE_LZX) + { + /* special LZX hack -- on the last block, inform LZX of the + * size of the output data stream. */ + lzxd_set_output_length((lzxd_stream)self.d.state, self.d.outlen); + } + } + } /* if (avail) */ + } /* while (todo > 0) */ + return bytes - todo; + } + + /// + /// cabd_sys_write is the internal writer function which the decompressors + /// use. it either writes data to disk (self.d.outfh) with the real + /// sys.write() function, or does nothing with the data when + /// self.d.outfh == null. advances self.d.offset + /// + public override unsafe int write(mspack_file file, void* buffer, int bytes) + { + mscab_decompressor self = (mscab_decompressor)file; + self.d.offset += (uint)bytes; + if (self.d.outfh != null) + { + return self.system.write(self.d.outfh, buffer, bytes); + } + return bytes; + } + + /// + /// 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 MSPACK_ERR cabd_sys_read_block(mspack_system sys, mscabd_decompress_state d, ref int @out, int ignore_cksum, int 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.i_ptr = d.i_end = system.GetArrayPointer(d.input); + + do + { + // Read the block header + if (sys.read(d.infh, system.GetArrayPointer(hdr), cfdata_SIZEOF) != cfdata_SIZEOF) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + + // Skip any reserved block headers + if (d.data.cab.block_resv != 0 && + sys.seek(d.infh, d.data.cab.block_resv, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0) + { + return MSPACK_ERR.MSPACK_ERR_SEEK; + } + + // Blocks must not be over CAB_INPUTMAX in size + len = EndGetI16(&hdr[cfdata_CompressedSize]); + full_len = (int)(d.i_end - d.i_ptr + len); // Include cab-spanning blocks */ + if (full_len > CAB_INPUTMAX) + { + System.Console.Error.WriteLine($"Block size {full_len} > CAB_INPUTMAX"); + // In salvage mode, blocks can be 65535 bytes but no more than that + if (ignore_blocksize == 0 || full_len > CAB_INPUTMAX_SALVAGE) + { + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + } + + // Blocks must not expand to more than CAB_BLOCKMAX + if (EndGetI16(&hdr[cfdata_UncompressedSize]) > CAB_BLOCKMAX) + { + System.Console.Error.WriteLine("block size > CAB_BLOCKMAX"); + if (ignore_blocksize == 0) return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // Read the block data + if (sys.read(d.infh, d.i_end, len) != len) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + + // Perform checksum test on the block (if one is stored) + if ((cksum = EndGetI32(&hdr[cfdata_CheckSum]))) + { + uint sum2 = cabd_checksum(d.i_end, (uint)len, 0); + if (cabd_checksum(&hdr[4], 4, sum2) != cksum) + { + if (ignore_cksum == 0) return MSPACK_ERR.MSPACK_ERR_CHECKSUM; + sys.message(d.infh, "WARNING; bad block checksum found"); + } + } + + // Advance end of block pointer to include newly read data + d.i_end += 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 ((@out = EndGetI16(&hdr[cfdata_UncompressedSize]))) + { + return MSPACK_ERR.MSPACK_ERR_OK; + } + + // Otherwise, advance to next cabinet + + // Close current file handle + sys.close(d.infh); + d.infh = null; + + // Aadvance to next member in the cabinet set + if ((d.data = d.data.next) == null) + { + sys.message(d.infh, "WARNING; ran out of cabinets in set. Are any missing?"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // Open next cab file + d.incab = d.data.cab; + if ((d.infh = sys.open(d.incab.filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ)) == null) + { + return MSPACK_ERR.MSPACK_ERR_OPEN; + } + + // Seek to start of data blocks + if (sys.seek(d.infh, d.data.offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0) + { + return MSPACK_ERR.MSPACK_ERR_SEEK; + } + } while (true); + + // Not reached + return MSPACK_ERR.MSPACK_ERR_OK; + } + + private static uint cabd_checksum(byte* data, uint bytes, uint cksum) + { + uint len, ul = 0; + + for (len = bytes >> 2; len-- > 0; data += 4) + { + cksum ^= EndGetI32(data); + } + + switch (bytes & 3) + { + case 3: ul |= (uint)(*data++ << 16); goto case 2; + case 2: ul |= (uint)(*data++ << 8); goto case 1; + case 1: ul |= *data; break; + } + cksum ^= ul; + + return cksum; + } + } +} \ No newline at end of file diff --git a/libmspack/mspack_system.cs b/libmspack/mspack_system.cs index 8578cd4..b8a2ed3 100644 --- a/libmspack/mspack_system.cs +++ b/libmspack/mspack_system.cs @@ -7,7 +7,7 @@ namespace SabreTools.Compression.libmspack /// 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 + /// When the library is compiled normally, passing null to a compressor or /// decompressor constructor will result in a default mspack_system being /// used, where all methods are implemented with the standard C library. /// @@ -35,7 +35,7 @@ namespace SabreTools.Compression.libmspack /// A pointer to a mspack_file structure. This structure officially /// contains no members, its true contents are up to the /// mspack_system implementor. It should contain whatever is needed - /// for other mspack_system methods to operate. Returning the NULL + /// for other mspack_system methods to operate. Returning the null /// pointer indicates an error condition. /// /// @@ -127,7 +127,7 @@ namespace SabreTools.Compression.libmspack /// /// /// May be a file handle returned from open() if this message - /// pertains to a specific open file, or NULL if not related to + /// pertains to a specific open file, or null if not related to /// a specific file. /// /// @@ -141,7 +141,7 @@ namespace SabreTools.Compression.libmspack /// /// The number of bytes to allocate /// - /// A pointer to the requested number of bytes, or NULL if + /// A pointer to the requested number of bytes, or null if /// not enough memory is available /// /// @@ -150,7 +150,7 @@ namespace SabreTools.Compression.libmspack /// /// Frees memory /// - /// The memory to be freed. NULL is accepted and ignored. + /// The memory to be freed. null is accepted and ignored. /// public abstract void free(void* ptr); diff --git a/libmspack/msszdd_compressor.cs b/libmspack/msszdd_compressor.cs index 6ddc6f2..4653317 100644 --- a/libmspack/msszdd_compressor.cs +++ b/libmspack/msszdd_compressor.cs @@ -7,10 +7,8 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract class msszdd_compressor + public abstract class msszdd_compressor : Compressor { - public mspack_system system { get; set; } - public MSPACK_ERR error { get; set; } /// diff --git a/libmspack/msszdd_decompressor.cs b/libmspack/msszdd_decompressor.cs index 10d377a..e546f3b 100644 --- a/libmspack/msszdd_decompressor.cs +++ b/libmspack/msszdd_decompressor.cs @@ -10,10 +10,8 @@ namespace SabreTools.Compression.libmspack /// /// /// - public unsafe class msszdd_decompressor + public unsafe class msszdd_decompressor : Decompressor { - public mspack_system system { get; set; } - public MSPACK_ERR error { get; set; } /// diff --git a/libmspack/mszip.cs b/libmspack/mszip.cs index 2946c35..8182b59 100644 --- a/libmspack/mszip.cs +++ b/libmspack/mszip.cs @@ -32,7 +32,7 @@ namespace SabreTools.Compression.libmspack /// /// - uses system->alloc() to allocate memory /// - /// - returns NULL if not enough memory + /// - returns null if not enough memory /// /// - input_buffer_size is how many bytes to use as an input bitstream buffer /// diff --git a/libmspack/mszipd_stream.cs b/libmspack/mszipd_stream.cs index 7056b52..1f01490 100644 --- a/libmspack/mszipd_stream.cs +++ b/libmspack/mszipd_stream.cs @@ -3,28 +3,8 @@ using static SabreTools.Compression.libmspack.mszip; namespace SabreTools.Compression.libmspack { - public unsafe class mszipd_stream + public unsafe class mszipd_stream : readbits { - /// - /// I/O routines - /// - public mspack_system sys { get; set; } - - /// - /// Input file handle - /// - public mspack_file input { get; set; } - - /// - /// Output file handle - /// - public mspack_file output { get; set; } - - /// - /// Offset within window - /// - public uint window_posn { get; set; } - /// /// inflate() will call this whenever the window should be emptied. /// @@ -32,34 +12,10 @@ namespace SabreTools.Compression.libmspack /// public int flush_window(uint val) => 0; - public MSPACK_ERR error { get; set; } - public int repair_mode { get; set; } public int bytes_output { get; set; } - #region I/O buffering - - public byte* inbuf { get; set; } - - public byte* i_ptr { get; set; } - - public byte* i_end { get; set; } - - public byte* o_ptr { get; set; } - - public byte* o_end { get; set; } - - public byte input_end { get; set; } - - public uint bit_buffer { get; set; } - - public uint bits_left { get; set; } - - public uint inbuf_size { get; set; } - - #endregion - #region Huffman code lengths public byte[] LITERAL_len { get; set; } = new byte[MSZIP_LITERAL_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; @@ -80,5 +36,11 @@ namespace SabreTools.Compression.libmspack /// 32kb history window /// public byte[] window { get; set; } = new byte[MSZIP_FRAME_SIZE]; + + public override void READ_BYTES() + { + READ_IF_NEEDED; + INJECT_BITS_LSB(*i_ptr++, 8); + } } } \ No newline at end of file diff --git a/libmspack/noned_state.cs b/libmspack/noned_state.cs new file mode 100644 index 0000000..d946d7c --- /dev/null +++ b/libmspack/noned_state.cs @@ -0,0 +1,18 @@ +namespace SabreTools.Compression.libmspack +{ + /// + /// The "not compressed" method decompressor + /// + public unsafe class noned_state + { + public mspack_system sys { get; set; } + + public mspack_file i { get; set; } + + public mspack_file o { get; set; } + + public byte* buf { get; set; } + + public int bufsize { get; set; } + } +} \ No newline at end of file diff --git a/libmspack/qtm.cs b/libmspack/qtm.cs index df1c3a2..024f04e 100644 --- a/libmspack/qtm.cs +++ b/libmspack/qtm.cs @@ -1,17 +1,142 @@ namespace SabreTools.Compression.libmspack { - public static class qtm + public unsafe static class qtm { public const int QTM_FRAME_SIZE = 32768; + #region 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: + * unsigned int 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 + }; + + #endregion + + private static void qtmd_update_model(qtmd_model model) + { + qtmd_modelsym 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; + } + } + } + + /// + /// Initialises a model to decode symbols from [start] to [start]+[len]-1 + /// + private static void qtmd_init_model(qtmd_model model, qtmd_modelsym* syms, int start, int len) + { + model.shiftsleft = 4; + model.entries = len; + model.syms = syms; + + for (int i = 0; i <= len; i++) + { + syms[i].sym = (ushort)(start + i); // Actual symbol + syms[i].cumfreq = (ushort)(len - i); // Current frequency of that symbol + } + } + /// /// Allocates Quantum decompression state for decoding the given stream. /// - /// - returns NULL if window_bits is outwith the range 10 to 21 (inclusive). + /// - returns null if window_bits is outwith the range 10 to 21 (inclusive). /// - /// - uses system->alloc() to allocate memory + /// - uses system.alloc() to allocate memory /// - /// - returns NULL if not enough memory + /// - returns null if not enough memory /// /// - window_bits is the size of the Quantum window, from 1Kb (10) to 2Mb (21). /// @@ -23,8 +148,70 @@ namespace SabreTools.Compression.libmspack /// /// /// - public static qtmd_stream qtmd_init(mspack_system system, mspack_file input, mspack_file output, int window_bits, int input_buffer_size) => null; - + public static qtmd_stream qtmd_init(mspack_system system, mspack_file input, mspack_file output, int window_bits, int input_buffer_size) + { + uint window_size = (uint)(1 << window_bits); + int i; + + 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 + qtmd_stream qtm = new qtmd_stream(); + + // Allocate decompression window and input buffer + qtm.window = (byte*)system.alloc((int)window_size); + qtm.inbuf = (byte*)system.alloc((int)input_buffer_size); + if (qtm.window == null || qtm.inbuf == null) + { + system.free(qtm.window); + system.free(qtm.inbuf); + //system.free(qtm); + return null; + } + + // Initialise decompression state + qtm.sys = system; + qtm.input = input; + qtm.output = output; + qtm.inbuf_size = (uint)input_buffer_size; + qtm.window_size = window_size; + qtm.window_posn = 0; + qtm.frame_todo = QTM_FRAME_SIZE; + qtm.header_read = 0; + qtm.error = MSPACK_ERR.MSPACK_ERR_OK; + + qtm.i_ptr = qtm.i_end = &qtm.inbuf[0]; + qtm.o_ptr = qtm.o_end = &qtm.window[0]; + qtm.input_end = 0; + qtm.bits_left = 0; + qtm.bit_buffer = 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 + i = window_bits * 2; + qtmd_init_model(qtm.model0, qtm.m0sym, 0, 64); + qtmd_init_model(qtm.model1, qtm.m1sym, 64, 64); + qtmd_init_model(qtm.model2, qtm.m2sym, 128, 64); + qtmd_init_model(qtm.model3, qtm.m3sym, 192, 64); + qtmd_init_model(qtm.model4, qtm.m4sym, 0, (i > 24) ? 24 : i); + qtmd_init_model(qtm.model5, qtm.m5sym, 0, (i > 36) ? 36 : i); + qtmd_init_model(qtm.model6, qtm.m6sym, 0, i); + qtmd_init_model(qtm.model6len, qtm.m6lsym, 0, 27); + qtmd_init_model(qtm.model7, qtm.m7sym, 0, 7); + + // All ok + return qtm; + } + /// /// Decompresses, or decompresses more of, a Quantum stream. /// @@ -35,13 +222,13 @@ namespace SabreTools.Compression.libmspack /// 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 + /// - 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() + /// than one call may be made to system.write() /// - /// - Quantum will read input bytes as necessary using the system->read() + /// - 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, + /// qtmd_init(). This will continue until system.read() returns 0 bytes, /// or an error. /// /// @@ -52,9 +239,19 @@ namespace SabreTools.Compression.libmspack /// /// Frees all state associated with a Quantum data stream /// - /// - calls system->free() using the system pointer given in qtmd_init() + /// - calls system.free() using the system pointer given in qtmd_init() /// /// - public static void qtmd_free(qtmd_stream qtm) { } + public static void qtmd_free(qtmd_stream qtm) + { + mspack_system sys; + if (qtm != null) + { + sys = qtm.sys; + //sys.free(qtm.window); + //sys.free(qtm.inbuf); + //sys.free(qtm); + } + } } } \ No newline at end of file diff --git a/libmspack/qtmd_model.cs b/libmspack/qtmd_model.cs index e331261..1068f90 100644 --- a/libmspack/qtmd_model.cs +++ b/libmspack/qtmd_model.cs @@ -6,6 +6,6 @@ namespace SabreTools.Compression.libmspack public int entries { get; set; } - public qtmd_modelsym** syms { get; set; } + public qtmd_modelsym* syms { get; set; } } } \ No newline at end of file diff --git a/libmspack/qtmd_stream.cs b/libmspack/qtmd_stream.cs index 0c063f3..2424b2a 100644 --- a/libmspack/qtmd_stream.cs +++ b/libmspack/qtmd_stream.cs @@ -1,22 +1,7 @@ namespace SabreTools.Compression.libmspack { - public unsafe class qtmd_stream + public unsafe class qtmd_stream : readbits { - /// - /// I/O routines - /// - public mspack_system sys { get; set; } - - /// - /// Input file handle - /// - public mspack_file input { get; set; } - - /// - /// Output file handle - /// - public mspack_file output { get; set; } - /// /// Decoding window /// @@ -27,11 +12,6 @@ namespace SabreTools.Compression.libmspack /// public uint window_size { get; set; } - /// - /// Decompression offset within window - /// - public uint window_posn { get; set; } - /// /// Bytes remaining for current frame /// @@ -57,30 +37,6 @@ namespace SabreTools.Compression.libmspack /// public byte header_read { get; set; } - public MSPACK_ERR error { get; set; } - - #region I/O buffering - - public byte* inbuf { get; set; } - - public byte* i_ptr { get; set; } - - public byte* i_end { get; set; } - - public byte* o_ptr { get; set; } - - public byte* o_end { get; set; } - - public uint bit_buffer { get; set; } - - public uint inbuf_size { get; set; } - - public byte bits_left { get; set; } - - public byte input_end { get; set; } - - #endregion - #region Models #region Four literal models, each representing 64 symbols @@ -156,5 +112,15 @@ namespace SabreTools.Compression.libmspack public qtmd_modelsym[] m7sym { get; set; } = new qtmd_modelsym[7 + 1]; #endregion + + public override void READ_BYTES() + { + byte b0, b1; + READ_IF_NEEDED; + b0 = *i_ptr++; + READ_IF_NEEDED; + b1 = *i_ptr++; + INJECT_BITS_MSB((b0 << 8) | b1, 16); + } } } \ No newline at end of file diff --git a/libmspack/readbits.cs b/libmspack/readbits.cs new file mode 100644 index 0000000..a1b0f9e --- /dev/null +++ b/libmspack/readbits.cs @@ -0,0 +1,245 @@ +namespace SabreTools.Compression.libmspack +{ + public unsafe abstract class readbits + { + /// + /// I/O routines + /// + public mspack_system sys { get; set; } + + /// + /// Input file handle + /// + public mspack_file input { get; set; } + + /// + /// Output file handle + /// + public mspack_file output { get; set; } + + /// + /// Decompression offset within window + /// + public uint window_posn { get; set; } + + #region I/O buffering + + public byte* inbuf { get; set; } + + public byte* i_ptr { get; set; } + + public byte* i_end { get; set; } + + public byte* o_ptr { get; set; } + + public byte* o_end { get; set; } + + public int input_end { get; set; } + + public uint bit_buffer { get; set; } + + public uint bits_left { get; set; } + + public uint inbuf_size { get; set; } + + #endregion + + public MSPACK_ERR error { get; set; } + + /// + #region readbits.h + + private const int BITBUF_WIDTH = 64; + + 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 void INIT_BITS() + { + this.i_ptr = inbuf; + this.i_end = inbuf; + this.bit_buffer = 0; + this.bits_left = 0; + this.input_end = 0; + } + + public void STORE_BITS(byte* i_ptr, byte* i_end, uint bit_buffer, uint bits_left) + { + this.i_ptr = i_ptr; + this.i_end = i_end; + this.bit_buffer = bit_buffer; + this.bits_left = bits_left; + } + + public void RESTORE_BITS(out byte* i_ptr, out byte* i_end, out uint bit_buffer, out uint bits_left) + { + i_ptr = this.i_ptr; + i_end = this.i_end; + bit_buffer = this.bit_buffer; + bits_left = this.bits_left; + } + + public void ENSURE_BITS(byte nbits, ref uint bits_left) + { + while (bits_left < nbits) + { + this.READ_BYTES(); + } + } + + #region MSB + + public void READ_BITS_MSB(out int val, byte nbits, ref uint bit_buffer, ref uint bits_left) + { + this.ENSURE_BITS(nbits, ref bits_left); + val = PEEK_BITS_MSB(nbits, bit_buffer); + REMOVE_BITS_MSB(nbits, ref bit_buffer, ref bits_left); + } + + public void READ_MANY_BITS_MSB(out int val, byte bits, ref uint bit_buffer, ref uint bits_left) + { + byte needed = bits; + byte bitrun; + val = 0; + while (needed > 0) + { + if (bits_left < (int)(BITBUF_WIDTH - 16)) + this.READ_BYTES(); + + bitrun = (bits_left < needed) ? (byte)bits_left : needed; + val = (val << bitrun) | PEEK_BITS_MSB(bitrun, bit_buffer); + REMOVE_BITS_MSB(bitrun, ref bit_buffer, ref bits_left); + needed -= bitrun; + } + } + + public int PEEK_BITS_MSB(byte nbits, uint bit_buffer) + { + return (int)(bit_buffer >> (BITBUF_WIDTH - nbits)); + } + + public void REMOVE_BITS_MSB(byte nbits, ref uint bit_buffer, ref uint bits_left) + { + bit_buffer <<= nbits; + bits_left -= nbits; + } + + public void INJECT_BITS_MSB(uint bitdata, byte nbits, ref uint bit_buffer, ref uint bits_left) + { + bit_buffer |= (uint)(int)(bitdata << (int)(BITBUF_WIDTH - nbits - bits_left)); + bits_left += nbits; + } + + #endregion + + #region LSB + + public void READ_BITS_LSB(out int val, byte nbits, ref uint bit_buffer, ref uint bits_left) + { + this.ENSURE_BITS(nbits, ref bits_left); + val = PEEK_BITS_LSB(nbits, bit_buffer); + REMOVE_BITS_LSB(nbits, ref bit_buffer, ref bits_left); + } + + public void READ_MANY_BITS_LSB(out int val, byte bits, ref uint bit_buffer, ref uint bits_left) + { + byte needed = bits; + byte bitrun; + val = 0; + while (needed > 0) + { + if (bits_left < (int)(BITBUF_WIDTH - 16)) + this.READ_BYTES(); + + bitrun = (bits_left < needed) ? (byte)bits_left : needed; + val = (val << bitrun) | PEEK_BITS_LSB(bitrun, bit_buffer); + REMOVE_BITS_LSB(bitrun, ref bit_buffer, ref bits_left); + needed -= bitrun; + } + } + + public int PEEK_BITS_LSB(byte nbits, uint bit_buffer) + { + return (int)(bit_buffer & ((uint)(1 << nbits) - 1)); + } + + public void REMOVE_BITS_LSB(byte nbits, ref uint bit_buffer, ref uint bits_left) + { + bit_buffer >>= nbits; + bits_left -= nbits; + } + + public void INJECT_BITS_LSB(uint bitdata, byte nbits, ref uint bit_buffer, ref uint bits_left) + { + bit_buffer |= bitdata << (int)bits_left; + bits_left += nbits; + } + + #endregion + + #region LSB_T + + public int PEEK_BITS_LSB_T(byte nbits, uint bit_buffer) + { + return (int)(bit_buffer & lsb_bit_mask[nbits]); + } + + public void READ_BITS_LSB_T(out int val, byte nbits, ref uint bit_buffer, ref uint bits_left) + { + this.ENSURE_BITS(nbits, ref bits_left); + val = PEEK_BITS_LSB_T(nbits, bit_buffer); + REMOVE_BITS_LSB(nbits, ref bit_buffer, ref bits_left); + } + + #endregion + + public abstract void READ_BYTES(); + + public MSPACK_ERR READ_IF_NEEDED(ref byte* i_ptr, ref byte* i_end) + { + if (i_ptr >= i_end) + { + if (read_input() != MSPACK_ERR.MSPACK_ERR_OK) + return this.error; + + i_ptr = this.i_ptr; + i_end = this.i_end; + } + + return MSPACK_ERR.MSPACK_ERR_OK; + } + + private MSPACK_ERR read_input() + { + int read = this.sys.read(this.input, this.inbuf, (int)this.inbuf_size); + if (read < 0) return this.error = MSPACK_ERR.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 (this.input_end != 0) + { + System.Console.Error.WriteLine("Out of input bytes"); + return this.error = MSPACK_ERR.MSPACK_ERR_READ; + } + else + { + read = 2; + this.inbuf[0] = this.inbuf[1] = 0; + this.input_end = 1; + } + } + + // Update i_ptr and i_end + this.i_ptr = this.inbuf; + this.i_end = this.inbuf + read; + return MSPACK_ERR.MSPACK_ERR_OK; + } + + #endregion + } +} \ No newline at end of file diff --git a/libmspack/readhuff.cs b/libmspack/readhuff.cs new file mode 100644 index 0000000..7d44321 --- /dev/null +++ b/libmspack/readhuff.cs @@ -0,0 +1,7 @@ +namespace SabreTools.Compression.libmspack +{ + public unsafe abstract class readhuff + { + public const int HUFF_MAXBITS = 16; + } +} \ No newline at end of file