diff --git a/libmspack/BaseDecompressor.cs b/libmspack/BaseDecompressor.cs index c064774..1de341a 100644 --- a/libmspack/BaseDecompressor.cs +++ b/libmspack/BaseDecompressor.cs @@ -3,7 +3,7 @@ namespace SabreTools.Compression.libmspack /// /// Base class for all decompressor implementations /// - public abstract class Decompressor : mspack_file + public abstract class BaseDecompressor : mspack_file { #if NET48 public mspack_system system { get; set; } diff --git a/libmspack/mspack_mscab_system.cs b/libmspack/CAB/CABSystem.cs similarity index 82% rename from libmspack/mspack_mscab_system.cs rename to libmspack/CAB/CABSystem.cs index 1fc822b..333c33d 100644 --- a/libmspack/mspack_mscab_system.cs +++ b/libmspack/CAB/CABSystem.cs @@ -1,8 +1,8 @@ -using static SabreTools.Compression.libmspack.cab; +using static SabreTools.Compression.libmspack.CAB.Constants; -namespace SabreTools.Compression.libmspack +namespace SabreTools.Compression.libmspack.CAB { - public unsafe class mspack_mscab_system : mspack_default_system + public unsafe class CABSystem : mspack_default_system { /// /// cabd_sys_read is the internal reader function which the decompressors @@ -11,12 +11,12 @@ namespace SabreTools.Compression.libmspack /// public override unsafe int read(mspack_file file, void* buffer, int bytes) { - mscab_decompressor self = (mscab_decompressor)file; + Decompressor self = (Decompressor)file; byte* buf = (byte*)buffer; mspack_system sys = self.system; - int avail, todo, outlen, ignore_cksum, ignore_blocksize; + int avail, todo, outlen = 0, 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_cksum = self.salvage != 0 || (self.fix_mszip != 0 && ((MSCAB_COMP)((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == MSCAB_COMP.MSCAB_COMP_MSZIP)) == true ? 1 : 0; ignore_blocksize = self.salvage; todo = bytes; @@ -53,14 +53,14 @@ namespace SabreTools.Compression.libmspack } // Read a block - self.read_error = cabd_sys_read_block(sys, self.d, ref outlen, ignore_cksum, ignore_blocksize); + self.read_error = ReadBlock(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) + if ((MSCAB_COMP)((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == MSCAB_COMP.MSCAB_COMP_QUANTUM) { *self.d.i_end++ = 0xFF; } @@ -68,10 +68,10 @@ namespace SabreTools.Compression.libmspack // Is this the last block? if (self.d.block >= self.d.folder.num_blocks) { - if (((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == cffoldCOMPTYPE_LZX) + if ((MSCAB_COMP)((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == MSCAB_COMP.MSCAB_COMP_LZX) { - /* special LZX hack -- on the last block, inform LZX of the - * size of the output data stream. */ + // 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); } } @@ -88,7 +88,7 @@ namespace SabreTools.Compression.libmspack /// public override unsafe int write(mspack_file file, void* buffer, int bytes) { - mscab_decompressor self = (mscab_decompressor)file; + Decompressor self = (Decompressor)file; self.d.offset += (uint)bytes; if (self.d.outfh != null) { @@ -101,7 +101,7 @@ namespace SabreTools.Compression.libmspack /// 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) + private static MSPACK_ERR ReadBlock(mspack_system sys, mscabd_decompress_state d, ref int @out, int ignore_cksum, int ignore_blocksize) { byte[] hdr = new byte[cfdata_SIZEOF]; uint cksum; @@ -126,7 +126,7 @@ namespace SabreTools.Compression.libmspack } // Blocks must not be over CAB_INPUTMAX in size - len = EndGetI16(&hdr[cfdata_CompressedSize]); + len = System.BitConverter.ToUInt16(hdr, cfdata_CompressedSize); full_len = (int)(d.i_end - d.i_ptr + len); // Include cab-spanning blocks */ if (full_len > CAB_INPUTMAX) { @@ -139,7 +139,7 @@ namespace SabreTools.Compression.libmspack } // Blocks must not expand to more than CAB_BLOCKMAX - if (EndGetI16(&hdr[cfdata_UncompressedSize]) > CAB_BLOCKMAX) + if (System.BitConverter.ToUInt16(hdr, cfdata_UncompressedSize) > CAB_BLOCKMAX) { System.Console.Error.WriteLine("block size > CAB_BLOCKMAX"); if (ignore_blocksize == 0) return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; @@ -152,10 +152,10 @@ namespace SabreTools.Compression.libmspack } // Perform checksum test on the block (if one is stored) - if ((cksum = EndGetI32(&hdr[cfdata_CheckSum]))) + if ((cksum = System.BitConverter.ToUInt32(hdr, cfdata_CheckSum)) != 0) { - uint sum2 = cabd_checksum(d.i_end, (uint)len, 0); - if (cabd_checksum(&hdr[4], 4, sum2) != cksum) + uint sum2 = Checksum(d.i_end, (uint)len, 0); + if (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"); @@ -171,7 +171,7 @@ namespace SabreTools.Compression.libmspack // reading needs to be done. // EXIT POINT OF LOOP -- uncompressed size != 0 - if ((@out = EndGetI16(&hdr[cfdata_UncompressedSize]))) + if ((@out = System.BitConverter.ToInt16(hdr, cfdata_UncompressedSize)) != 0) { return MSPACK_ERR.MSPACK_ERR_OK; } @@ -207,13 +207,13 @@ namespace SabreTools.Compression.libmspack return MSPACK_ERR.MSPACK_ERR_OK; } - private static uint cabd_checksum(byte* data, uint bytes, uint cksum) + private static uint Checksum(byte* data, uint bytes, uint cksum) { uint len, ul = 0; for (len = bytes >> 2; len-- > 0; data += 4) { - cksum ^= EndGetI32(data); + cksum ^= System.BitConverter.ToInt32(data, 0); } switch (bytes & 3) diff --git a/libmspack/CAB/Decompressor.cs b/libmspack/CAB/Decompressor.cs new file mode 100644 index 0000000..5643ac8 --- /dev/null +++ b/libmspack/CAB/Decompressor.cs @@ -0,0 +1,1217 @@ +using static SabreTools.Compression.libmspack.cab; +using static SabreTools.Compression.libmspack.CAB.Constants; + +namespace SabreTools.Compression.libmspack.CAB +{ + /// + /// A decompressor for .CAB (Microsoft Cabinet) files + /// + /// All fields are READ ONLY. + /// + /// + /// + public unsafe class Decompressor : BaseDecompressor + { + public mscabd_decompress_state d { get; set; } + + public int buf_size { get; set; } + + public int searchbuf_size { get; set; } + + public int fix_mszip { get; set; } + + public int salvage { get; set; } + + public MSPACK_ERR read_error { get; set; } + + /// + /// Opens a cabinet file and reads its contents. + /// + /// If the file opened is a valid cabinet file, all headers will be read + /// 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 + /// is available from last_error(). + /// + /// The filename pointer should be considered "in use" until close() is + /// called on the cabinet. + /// + /// + /// 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 + /// + /// + /// + public mscabd_cabinet open(in string filename) + { + mscabd_cabinet cab = null; + mspack_file fh; + + if ((fh = this.system.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ)) != null) + { + cab = new mscabd_cabinet(); + cab.filename = filename; + MSPACK_ERR error = ReadHeaders(this.system, fh, cab, 0, this.salvage, 0); + if (error != MSPACK_ERR.MSPACK_ERR_OK) + { + close(cab); + cab = null; + } + this.error = error; + this.system.close(fh); + } + else + { + this.error = MSPACK_ERR.MSPACK_ERR_OPEN; + } + + return cab; + } + + /// + /// Closes a previously opened cabinet or cabinet set. + /// + /// This closes a cabinet, all cabinets associated with it via the + /// mscabd_cabinet::next, mscabd_cabinet::prevcab and + /// mscabd_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 + /// mscabd_folder and mscabd_file 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 mscabd_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 mscabd_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. + /// + /// The cabinet to close + /// + /// + /// + /// + 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(this); + //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 ReadHeaders(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 (System.BitConverter.ToInt32(buf, cfhead_Signature) != 0x4643534D) + { + return MSPACK_ERR.MSPACK_ERR_SIGNATURE; + } + + // Some basic header fields + cab.length = System.BitConverter.ToUInt32(buf, cfhead_CabinetSize); + cab.set_id = System.BitConverter.ToUInt16(buf, cfhead_SetID); + cab.set_index = System.BitConverter.ToUInt16(buf, cfhead_CabinetIndex); + + // Get the number of folders + num_folders = System.BitConverter.ToInt16(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 = System.BitConverter.ToInt16(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 = (MSCAB_HDR)System.BitConverter.ToInt16(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 = System.BitConverter.ToUInt16(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 = ReadString(sys, fh, 0, out err); + if (err != MSPACK_ERR.MSPACK_ERR_OK) return err; + cab.previnfo = ReadString(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 = ReadString(sys, fh, 0, out err); + if (err != MSPACK_ERR.MSPACK_ERR_OK) return err; + cab.nextinfo = ReadString(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 = (MSCAB_COMP)System.BitConverter.ToInt16(buf, cffold_CompType); + fol.num_blocks = (uint)System.BitConverter.ToInt16(buf, cffold_NumBlocks); + fol.data.next = null; + fol.data.cab = cab; + fol.data.offset = offset + (long)((uint)System.BitConverter.ToInt32(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 = System.BitConverter.ToUInt32(buf, cffile_UncompressedSize); + file.attribs = (MSCAB_ATTRIB)System.BitConverter.ToInt16(buf, cffile_Attribs); + file.offset = System.BitConverter.ToUInt32(buf, cffile_FolderOffset); + + // Set folder pointer + fidx = System.BitConverter.ToInt16(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 = System.BitConverter.ToInt16(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 = System.BitConverter.ToInt16(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 = ReadString(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 ReadString(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. + /// + /// 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 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 + /// last_error(). + /// + /// 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. + /// + /// close() should only be called on the result of search(), not on any + /// subsequent cabinets in the mscabd_cabinet::next chain. + /// + /// + /// 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 + /// + /// + /// + public mscabd_cabinet search(in string filename) + { + mscabd_cabinet cab = null; + mspack_system sys; + byte* search_buf; + mspack_file fh; + long filelen, firstlen = 0; + + sys = this.system; + + // Allocate a search buffer + search_buf = (byte*)sys.alloc(this.searchbuf_size); + if (search_buf == null) + { + this.error = MSPACK_ERR.MSPACK_ERR_NOMEMORY; + return null; + } + + // Open file and get its full file length + if ((fh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ)) != null) + { + if ((this.error = libmspack.system.mspack_sys_filelen(sys, fh, &filelen)) == MSPACK_ERR.MSPACK_ERR_OK) + { + this.error = Find(search_buf, fh, filename, filelen, ref firstlen, cab); + } + + // Truncated / extraneous data warning: + if (firstlen != 0 && (firstlen != filelen) && cab == null || (cab.base_offset == 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 + { + this.error = MSPACK_ERR.MSPACK_ERR_OPEN; + } + + // Free the search buffer + sys.free(search_buf); + + return cab; + } + + /// + /// Find is the inner loop of , to make it easier to + /// break out of the loop and be sure that all resources are freed + /// + private MSPACK_ERR Find(byte* buf, mspack_file fh, in string filename, long flen, ref long firstlen, mscabd_cabinet firstcab) + { + mscabd_cabinet cab, link = null; + long caboff, offset, length; + mspack_system sys = this.system; + byte* 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 > this.searchbuf_size) + { + length = this.searchbuf_size; + } + + // Fill the search buffer with data from disk */ + if (sys.read(fh, buf, (int)length) != (int)length) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + + // FAQ avoidance strategy + if ((offset == 0) && (System.BitConverter.ToInt32(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 = &buf[0], pend = &buf[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 && *p != 0x4D) p++; + // If we found tht 'M', advance state + if (p++ < pend) state = 1; + break; + + // Verify that the next 3 bytes are 'S', 'C' and 'F' + case 1: state = (byte)((*p++ == 0x53) ? 2 : 0); break; + case 2: state = (byte)((*p++ == 0x43) ? 3 : 0); break; + case 3: state = (byte)((*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 = *p++; state++; break; + case 9: cablen_u32 |= (uint)(*p++ << 8); state++; break; + case 10: cablen_u32 |= (uint)(*p++ << 16); state++; break; + case 11: cablen_u32 |= (uint)(*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 = *p++; state++; break; + case 17: foffset_u32 |= (uint)(*p++ << 8); state++; break; + case 18: foffset_u32 |= (uint)(*p++ << 16); state++; break; + case 19: + foffset_u32 |= (uint)(*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 - &buf[0]) - 20; + + // Should reading cabinet fail, restart search just after 'MSCF' + offset = caboff + 4; + + // Capture 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 + (long)foffset_u32) < (flen + 32)) && + (((caboff + (long)cablen_u32) < (flen + 32)) || this.salvage != 0)) + { + // Likely cabinet found -- try reading it + cab = new mscabd_cabinet(); + cab.filename = filename; + if (ReadHeaders(sys, fh, cab, caboff, this.salvage, 1) != MSPACK_ERR.MSPACK_ERR_OK) + { + // Destroy the failed cabinet + close(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 + (long)cablen_u32; + } + } + + // Restart search + if (offset >= flen) return MSPACK_ERR.MSPACK_ERR_OK; + if (sys.seek(fh, offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0) + { + return MSPACK_ERR.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; + } /* switch(state) */ + } /* for (... p < pend ...) */ + } /* for (... offset < length ...) */ + + if (false_cabs != 0) + { + System.Console.Error.WriteLine("%d false cabinets found", false_cabs); + } + + return MSPACK_ERR.MSPACK_ERR_OK; + } + + /// + /// Appends one mscabd_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 mscabd_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 mscabd_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 mscabd_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 mscabd_folder and mscabd_file + /// structures in either cabinet must be discarded and re-obtained after + /// merging. + /// + /// 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.MSPACK_ERR_OK if successful + /// + /// + /// + public MSPACK_ERR append(mscabd_cabinet cab, mscabd_cabinet nextcab) + { + return Merge(cab, nextcab); + } + + /// + /// Prepends one mscabd_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. + /// + /// 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.MSPACK_ERR_OK if successful + /// + /// + /// + public MSPACK_ERR prepend(mscabd_cabinet cab, mscabd_cabinet prevcab) + { + return Merge(prevcab, cab); + } + + /// + /// 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. + /// + private MSPACK_ERR Merge(mscabd_cabinet lcab, mscabd_cabinet rcab) + { + mscabd_folder_data data, ndata; + mscabd_folder lfol, rfol; + mscabd_file fi, rfi, lfi; + mscabd_cabinet cab; + + mspack_system sys = this.system; + + // Basic args check + if (lcab == null || rcab == null || (lcab == rcab)) + { + System.Console.Error.WriteLine("lcab null, rcab null or lcab = rcab"); + return this.error = MSPACK_ERR.MSPACK_ERR_ARGS; + } + + // Check there's not already a cabinet attached + if (lcab.nextcab != null || rcab.prevcab != null) + { + System.Console.Error.WriteLine("cabs already joined"); + return this.error = MSPACK_ERR.MSPACK_ERR_ARGS; + } + + // Do not create circular cabinet chains + for (cab = lcab.prevcab; cab != null; cab = cab.prevcab) + { + if (cab == rcab) + { + System.Console.Error.WriteLine("circular!"); + return this.error = MSPACK_ERR.MSPACK_ERR_ARGS; + } + } + for (cab = rcab.nextcab; cab != null; cab = cab.nextcab) + { + if (cab == lcab) + { + System.Console.Error.WriteLine("circular!"); + return this.error = MSPACK_ERR.MSPACK_ERR_ARGS; + } + } + + // Warn about odd set IDs or indices + if (lcab.set_id != rcab.set_id) + { + sys.message(null, "WARNING; merged cabinets with differing Set IDs."); + } + + if (lcab.set_index > rcab.set_index) + { + sys.message(null, "WARNING; merged cabinets with odd order."); + } + + // Merging the last folder in lcab with the first folder in rcab + lfol = lcab.folders; + rfol = rcab.folders; + while (lfol.next != null) + lfol = lfol.next; + + // Do we need to merge folders? + if (lfol.merge_next == null && rfol.merge_prev == null) + { + // No, at least one of the folders is not for merging + + // Attach cabs + lcab.nextcab = rcab; + rcab.prevcab = 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) == 0) + { + return this.error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // Allocate a new folder data structure + data = new mscabd_folder_data(); + + // Attach cabs + lcab.nextcab = rcab; + rcab.prevcab = 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.merge_next is going to be deleted, so keep lfol's version + // instead + lfol.num_blocks += rfol.num_blocks - 1; + if ((rfol.merge_next == null) || (rfol.merge_next.folder != rfol)) + { + lfol.merge_next = rfol.merge_next; + } + + // Attach the rfol's folder (except the merge folder) + while (lfol.next != null) + lfol = 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 all cabs so they all + // point to the same list + for (cab = lcab.prevcab; cab != null; cab = cab.prevcab) + { + cab.files = lcab.files; + cab.folders = lcab.folders; + } + + for (cab = lcab.nextcab; cab != null; cab = cab.nextcab) + { + cab.files = lcab.files; + cab.folders = lcab.folders; + } + + return this.error = MSPACK_ERR.MSPACK_ERR_OK; + } + + /// + /// Decides if two folders are OK to merge + /// + private int CanMergeFolders(mspack_system sys, mscabd_folder lfol, mscabd_folder rfol) + { + mscabd_file lfi, rfi, l, r; + int matching = 1; + + // Check that both folders use the same compression method/settings + if (lfol.comp_type != rfol.comp_type) + { + System.Console.Error.WriteLine("folder merge: compression type mismatch"); + return 0; + } + + /* check there are not too many data blocks after merging */ + if ((lfol.num_blocks + rfol.num_blocks) > CAB_FOLDERMAX) + { + System.Console.Error.WriteLine("folder merge: too many data blocks in merged folders"); + return 0; + } + + if ((lfi = lfol.merge_next) == null || (rfi = rfol.merge_prev) == null) + { + System.Console.Error.WriteLine("folder merge: one cabinet has no files to merge"); + return 0; + } + + // 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 = 0; + break; + } + } + + if (matching != 0) + return 1; + + // 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 = 0; + 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 = 1; + else + sys.message(null, $"WARNING; merged file {l.filename} not listed in both cabinets"); + } + return matching; + } + + /// + /// 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, mscabd_file::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 mspack_system::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. + /// + /// The file to be decompressed + /// The filename of the file being written to + /// An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful + public MSPACK_ERR extract(mscabd_file file, in string filename) + { + mspack_file fh; + uint filelen; + + if (file == null) + return this.error = MSPACK_ERR.MSPACK_ERR_ARGS; + + mspack_system sys = this.system; + mscabd_folder fol = file.folder; + + // If offset is beyond 2GB, nothing can be extracted + if (file.offset > CAB_LENGTHMAX) + { + return this.error = MSPACK_ERR.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 + */ + filelen = file.length; + if (filelen > (CAB_LENGTHMAX - file.offset)) + { + if (this.salvage != 0) + { + filelen = CAB_LENGTHMAX - file.offset; + } + else + { + return this.error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + } + + // Extraction impossible if no folder, or folder needs predecessor + if (fol == null || fol.merge_prev != null) + { + sys.message(null, $"ERROR; file \"{file.filename}\" cannot be extracted, cabinet set is incomplete"); + return this.error = MSPACK_ERR.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 (this.salvage == 0) + { + uint maxlen = fol.num_blocks * CAB_BLOCKMAX; + if (file.offset > maxlen || filelen > (maxlen - file.offset)) + { + sys.message(null, $"ERROR; file \"{file.filename}\" cannot be extracted, cabinet set is incomplete"); + return this.error = MSPACK_ERR.MSPACK_ERR_DECRUNCH; + } + } + + // Allocate generic decompression state + if (this.d == null) + { + this.d = new mscabd_decompress_state(); + this.d.folder = null; + this.d.data = null; + this.d.sys = sys as CABSystem; + this.d.state = null; + this.d.infh = null; + this.d.incab = null; + } + + // Do we need to change folder or reset the current folder? + if ((this.d.folder != fol) || (this.d.offset > file.offset) || this.d.state == null) + { + // Free any existing decompressor + cabd_free_decomp(this); + + // Do we need to open a new cab file? + if (this.d.infh == null || (fol.data.cab != this.d.incab)) + { + // Close previous file handle if from a different cab + if (this.d.infh != null) + sys.close(this.d.infh); + this.d.incab = fol.data.cab; + this.d.infh = sys.open(fol.data.cab.filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ); + if (this.d.infh == null) + return this.error = MSPACK_ERR.MSPACK_ERR_OPEN; + } + // Seek to start of data blocks + if (sys.seek(this.d.infh, fol.data.offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0) + { + return this.error = MSPACK_ERR.MSPACK_ERR_SEEK; + } + + // Set up decompressor + if (cabd_init_decomp(this, fol.comp_type) != MSPACK_ERR.MSPACK_ERR_OK) + { + return this.error; + } + + // Initialise new folder state + this.d.folder = fol; + this.d.data = fol.data; + this.d.offset = 0; + this.d.block = 0; + this.d.outlen = 0; + this.d.i_ptr = this.d.i_end = libmspack.system.GetArrayPointer(d.input); + + // Read_error lasts for the lifetime of a decompressor + this.read_error = MSPACK_ERR.MSPACK_ERR_OK; + } + + // Open file for output + if ((fh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE)) == null) + { + return this.error = MSPACK_ERR.MSPACK_ERR_OPEN; + } + + this.error = MSPACK_ERR.MSPACK_ERR_OK; + + // If file has more than 0 bytes + if (filelen != 0) + { + long bytes; + MSPACK_ERR 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 this.read_error + // and pass back MSPACK_ERR_READ + this.d.outfh = null; + if ((bytes = file.offset - this.d.offset) != 0) + { + error = this.d.decompress(this.d.state, bytes); + this.error = (error == MSPACK_ERR.MSPACK_ERR_READ) ? this.read_error : error; + } + + // If getting to the correct offset was error free, unpack file + if (this.error == MSPACK_ERR.MSPACK_ERR_OK) + { + this.d.outfh = fh; + error = this.d.decompress(this.d.state, filelen); + this.error = (error == MSPACK_ERR.MSPACK_ERR_READ) ? this.read_error : error; + } + } + + // Close output file + sys.close(fh); + this.d.outfh = null; + + return this.error; + } + + /// + /// 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. + /// + /// The parameter to set + /// The value to set the parameter to + /// + /// 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 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. + /// + /// This is useful for open() and search(), which do not return an error + /// code directly. + /// + /// The most recent error code + /// + /// + public MSPACK_ERR last_error() + { + return this.error; + } + } +} \ No newline at end of file diff --git a/libmspack/cab.cs b/libmspack/cab.cs index 9e1b318..a41ede1 100644 --- a/libmspack/cab.cs +++ b/libmspack/cab.cs @@ -1,3 +1,5 @@ +using static SabreTools.Compression.libmspack.CAB.Constants; + namespace SabreTools.Compression.libmspack { public unsafe static class cab @@ -8,7 +10,7 @@ namespace SabreTools.Compression.libmspack /// 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) + public static MSPACK_ERR cabd_init_decomp(CAB.Decompressor self, MSCAB_COMP ct) { mspack_file fh = self; @@ -43,7 +45,7 @@ namespace SabreTools.Compression.libmspack /// decompression method was used. relies on self.d.folder being the same /// as when initialised. /// - public static void cabd_free_decomp(mscab_decompressor self) + public static void cabd_free_decomp(CAB.Decompressor self) { if (self == null || self.d == null || self.d.state == null) return; diff --git a/libmspack/mscab_decompressor.cs b/libmspack/mscab_decompressor.cs deleted file mode 100644 index 5ecef2c..0000000 --- a/libmspack/mscab_decompressor.cs +++ /dev/null @@ -1,649 +0,0 @@ -using static SabreTools.Compression.libmspack.cab; -using static SabreTools.Compression.libmspack.CAB.Constants; - -namespace SabreTools.Compression.libmspack -{ - /// - /// A decompressor for .CAB (Microsoft Cabinet) files - /// - /// All fields are READ ONLY. - /// - /// - /// - public unsafe class mscab_decompressor : Decompressor - { - public mscabd_decompress_state d { get; set; } - - public int buf_size { get; set; } - - public int searchbuf_size { get; set; } - - public int fix_mszip { get; set; } - - public int salvage { get; set; } - - public MSPACK_ERR read_error { get; set; } - - /// - /// Opens a cabinet file and reads its contents. - /// - /// If the file opened is a valid cabinet file, all headers will be read - /// 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 - /// is available from last_error(). - /// - /// The filename pointer should be considered "in use" until close() is - /// called on the cabinet. - /// - /// - /// 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 - /// - /// - /// - 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. - /// - /// This closes a cabinet, all cabinets associated with it via the - /// mscabd_cabinet::next, mscabd_cabinet::prevcab and - /// mscabd_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 - /// mscabd_folder and mscabd_file 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 mscabd_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 mscabd_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. - /// - /// The cabinet to close - /// - /// - /// - /// - 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(this); - //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. - /// - /// 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 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 - /// last_error(). - /// - /// 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. - /// - /// close() should only be called on the result of search(), not on any - /// subsequent cabinets in the mscabd_cabinet::next chain. - /// - /// - /// 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 - /// - /// - /// - public mscabd_cabinet search(in string filename) => null; - - /// - /// Appends one mscabd_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 mscabd_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 mscabd_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 mscabd_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 mscabd_folder and mscabd_file - /// structures in either cabinet must be discarded and re-obtained after - /// merging. - /// - /// 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.MSPACK_ERR_OK if successful - /// - /// - /// - public MSPACK_ERR append(mscabd_cabinet cab, mscabd_cabinet nextcab) => MSPACK_ERR.MSPACK_ERR_OK; - - /// - /// Prepends one mscabd_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. - /// - /// 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.MSPACK_ERR_OK if successful - /// - /// - /// - public MSPACK_ERR prepend(mscabd_cabinet cab, mscabd_cabinet prevcab) => MSPACK_ERR.MSPACK_ERR_OK; - - /// - /// 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, mscabd_file::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 mspack_system::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. - /// - /// The file to be decompressed - /// The filename of the file being written to - /// An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful - public MSPACK_ERR extract(mscabd_file file, in string filename) => MSPACK_ERR.MSPACK_ERR_OK; - - /// - /// 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. - /// - /// The parameter to set - /// The value to set the parameter to - /// - /// 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 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. - /// - /// This is useful for open() and search(), which do not return an error - /// code directly. - /// - /// The most recent error code - /// - /// - public MSPACK_ERR last_error() - { - return this.error; - } - } -} \ No newline at end of file diff --git a/libmspack/mscabd_decompress_state.cs b/libmspack/mscabd_decompress_state.cs index c1ebcc0..47badf8 100644 --- a/libmspack/mscabd_decompress_state.cs +++ b/libmspack/mscabd_decompress_state.cs @@ -1,8 +1,9 @@ -using static SabreTools.Compression.libmspack.cab; +using SabreTools.Compression.libmspack.CAB; +using static SabreTools.Compression.libmspack.CAB.Constants; namespace SabreTools.Compression.libmspack { - public abstract unsafe class mscabd_decompress_state + public unsafe class mscabd_decompress_state { /// /// Current folder we're extracting from @@ -32,7 +33,7 @@ namespace SabreTools.Compression.libmspack /// /// Special I/O code for decompressor /// - public mspack_system sys { get; set; } + public CABSystem sys { get; set; } /// /// Type of compression used by folder @@ -42,7 +43,7 @@ namespace SabreTools.Compression.libmspack /// /// Decompressor code /// - public abstract MSPACK_ERR decompress(object data, long offset); + public virtual MSPACK_ERR decompress(object data, long offset) => MSPACK_ERR.MSPACK_ERR_NOMEMORY; /// /// Decompressor state diff --git a/libmspack/mschm_decompressor.cs b/libmspack/mschm_decompressor.cs index f1396eb..02c31db 100644 --- a/libmspack/mschm_decompressor.cs +++ b/libmspack/mschm_decompressor.cs @@ -7,7 +7,7 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract class mschm_decompressor : Decompressor + public abstract class mschm_decompressor : BaseDecompressor { public mschmd_decompress_state d { get; set; } diff --git a/libmspack/mshlp_decompressor.cs b/libmspack/mshlp_decompressor.cs index 4b9947a..b881cc5 100644 --- a/libmspack/mshlp_decompressor.cs +++ b/libmspack/mshlp_decompressor.cs @@ -3,7 +3,7 @@ namespace SabreTools.Compression.libmspack /// /// TODO /// - public class mshlp_decompressor : Decompressor + public class mshlp_decompressor : BaseDecompressor { public int dummy { get; set; } } diff --git a/libmspack/mskwaj_decompressor.cs b/libmspack/mskwaj_decompressor.cs index 2d7d49f..cc8b091 100644 --- a/libmspack/mskwaj_decompressor.cs +++ b/libmspack/mskwaj_decompressor.cs @@ -7,7 +7,7 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract class mskwaj_decompressor : Decompressor + public abstract class mskwaj_decompressor : BaseDecompressor { /// /// Opens a KWAJ file and reads the header. diff --git a/libmspack/mslit_decompressor.cs b/libmspack/mslit_decompressor.cs index 5d8d609..45e9a5f 100644 --- a/libmspack/mslit_decompressor.cs +++ b/libmspack/mslit_decompressor.cs @@ -3,7 +3,7 @@ namespace SabreTools.Compression.libmspack /// /// TODO /// - public class mslit_decompressor : Decompressor + public class mslit_decompressor : BaseDecompressor { public int dummy { get; set; } } diff --git a/libmspack/msoab_decompressor.cs b/libmspack/msoab_decompressor.cs index b9bb32e..7c461e0 100644 --- a/libmspack/msoab_decompressor.cs +++ b/libmspack/msoab_decompressor.cs @@ -9,7 +9,7 @@ namespace SabreTools.Compression.libmspack /// /// /// - public unsafe class msoab_decompressor : Decompressor + public unsafe class msoab_decompressor : BaseDecompressor { public int buf_size { get; set; } @@ -39,60 +39,59 @@ namespace SabreTools.Compression.libmspack uint block_max, target_size; lzxd_stream lzx = null; mspack_oab_system oabd_sys; - oabd_file in_ofh, out_ofh; uint window_bits; MSPACK_ERR ret = MSPACK_ERR.MSPACK_ERR_OK; sys = this.system; - infh = sys.open(sys, input, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ); - if (!infh) + infh = sys.open(input, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ); + if (infh == null) { ret = MSPACK_ERR.MSPACK_ERR_OPEN; goto outlbl; } - if (sys.read(infh, hdrbuf, oabhead_SIZEOF) != oabhead_SIZEOF) + if (sys.read(infh, libmspack.system.GetArrayPointer(hdrbuf), oabhead_SIZEOF) != oabhead_SIZEOF) { ret = MSPACK_ERR.MSPACK_ERR_READ; goto outlbl; } - if (EndGetI32(&hdrbuf[oabhead_VersionHi]) != 3 || - EndGetI32(&hdrbuf[oabhead_VersionLo]) != 1) + if (System.BitConverter.ToInt32(hdrbuf, oabhead_VersionHi) != 3 || + System.BitConverter.ToInt32(hdrbuf, oabhead_VersionLo) != 1) { ret = MSPACK_ERR.MSPACK_ERR_SIGNATURE; goto outlbl; } - block_max = EndGetI32(&hdrbuf[oabhead_BlockMax]); - target_size = EndGetI32(&hdrbuf[oabhead_TargetSize]); + block_max = System.BitConverter.ToUInt32(hdrbuf, oabhead_BlockMax); + target_size = System.BitConverter.ToUInt32(hdrbuf, oabhead_TargetSize); - outfh = sys.open(sys, output, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE); - if (!outfh) + outfh = sys.open(output, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE); + if (outfh == null) { ret = MSPACK_ERR.MSPACK_ERR_OPEN; goto outlbl; } - buf = sys.alloc(sys, this.buf_size); - if (!buf) + buf = (byte*)sys.alloc(this.buf_size); + if (buf == null) { ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; goto outlbl; } - oabd_sys = *sys; - oabd_sys.read = oabd_sys_read; - oabd_sys.write = oabd_sys_write; + oabd_sys = sys as mspack_oab_system; + oabd_file in_ofh = new oabd_file(); in_ofh.orig_sys = sys; in_ofh.orig_file = infh; + oabd_file out_ofh = new oabd_file(); out_ofh.orig_sys = sys; out_ofh.orig_file = outfh; - while (target_size) + while (target_size > 0) { uint blk_csize, blk_dsize, blk_crc, blk_flags; @@ -101,10 +100,10 @@ namespace SabreTools.Compression.libmspack ret = MSPACK_ERR.MSPACK_ERR_READ; goto outlbl; } - blk_flags = EndGetI32(&buf[oabblk_Flags]); - blk_csize = EndGetI32(&buf[oabblk_CompSize]); - blk_dsize = EndGetI32(&buf[oabblk_UncompSize]); - blk_crc = EndGetI32(&buf[oabblk_CRC]); + blk_flags = System.BitConverter.ToInt32(buf, oabblk_Flags); + blk_csize = System.BitConverter.ToInt32(buf, oabblk_CompSize); + blk_dsize = System.BitConverter.ToInt32(buf, oabblk_UncompSize); + blk_crc = System.BitConverter.ToInt32(buf, oabblk_CRC); if (blk_dsize > block_max || blk_dsize > target_size || blk_flags > 1) { @@ -112,31 +111,30 @@ namespace SabreTools.Compression.libmspack goto outlbl; } - if (!blk_flags) + if (blk_flags == 0) { - /* Uncompressed block */ + // Uncompressed block if (blk_dsize != blk_csize) { ret = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; goto outlbl; } - ret = copy_fh(sys, infh, outfh, blk_dsize, buf, this.buf_size); - if (ret) goto outlbl; + ret = copy_fh(sys, infh, outfh, (int)blk_dsize, buf, this.buf_size); + if (ret != MSPACK_ERR.MSPACK_ERR_OK) goto outlbl; } else { - /* LZX compressed block */ + // LZX compressed block window_bits = 17; - while (window_bits < 25 && (1U << window_bits) < blk_dsize) + while (window_bits < 25 && (1 << (int)window_bits) < blk_dsize) window_bits++; - in_ofh.available = blk_csize; + in_ofh.available = (int)blk_csize; out_ofh.crc = 0xffffffff; - lzx = lzxd_init(&oabd_sys, (void*)&in_ofh, (void*)&out_ofh, window_bits, - 0, this.buf_size, blk_dsize, 1); - if (!lzx) + lzx = lzxd_init(&oabd_sys, (void*)&in_ofh, (void*)&out_ofh, window_bits, 0, this.buf_size, blk_dsize, 1); + if (lzx == null) { ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; goto outlbl; @@ -149,9 +147,9 @@ namespace SabreTools.Compression.libmspack lzxd_free(lzx); lzx = null; - /* Consume any trailing padding bytes before the next block */ + // Consume any trailing padding bytes before the next block ret = copy_fh(sys, infh, null, in_ofh.available, buf, this.buf_size); - if (ret) goto outlbl; + if (ret != MSPACK_ERR.MSPACK_ERR_OK) goto outlbl; if (out_ofh.crc != blk_crc) { @@ -163,9 +161,9 @@ namespace SabreTools.Compression.libmspack } outlbl: - if (lzx) lzxd_free(lzx); - if (outfh) sys.close(outfh); - if (infh) sys.close(infh); + if (lzx != null) lzxd_free(lzx); + if (outfh != null) sys.close(outfh); + if (infh != null) sys.close(infh); sys.free(buf); return ret; @@ -201,13 +199,15 @@ namespace SabreTools.Compression.libmspack /// An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful public MSPACK_ERR decompress_incremental(in string input, in string @base, in string output) { + mspack_file infh = null, basefh = null, outfh = null; lzxd_stream lzx = null; + byte* buf = null; uint window_bits, window_size; MSPACK_ERR ret = MSPACK_ERR.MSPACK_ERR_OK; mspack_system sys = this.system; - mspack_file infh = sys.open(input, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ); + infh = sys.open(input, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ); if (infh == null) { ret = MSPACK_ERR.MSPACK_ERR_OPEN; @@ -222,35 +222,35 @@ namespace SabreTools.Compression.libmspack goto outlbl; } - if (EndGetI32(&hdrbuf[patchhead_VersionHi]) != 3 || - EndGetI32(&hdrbuf[patchhead_VersionLo]) != 2) + if (System.BitConverter.ToInt32(hdrbuf, patchhead_VersionHi) != 3 || + System.BitConverter.ToInt32(hdrbuf, patchhead_VersionLo) != 2) { ret = MSPACK_ERR.MSPACK_ERR_SIGNATURE; goto outlbl; } - uint block_max = EndGetI32(&hdrbuf[patchhead_BlockMax]); - uint target_size = EndGetI32(&hdrbuf[patchhead_TargetSize]); + uint block_max = System.BitConverter.ToUInt32(hdrbuf, patchhead_BlockMax); + uint target_size = System.BitConverter.ToUInt32(hdrbuf, patchhead_TargetSize); // We use it for reading block headers too if (block_max < patchblk_SIZEOF) block_max = patchblk_SIZEOF; - mspack_file basefh = sys.open(@base, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ); + basefh = sys.open(@base, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ); if (basefh == null) { ret = MSPACK_ERR.MSPACK_ERR_OPEN; goto outlbl; } - mspack_file outfh = sys.open(output, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE); + outfh = sys.open(output, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE); if (outfh == null) { ret = MSPACK_ERR.MSPACK_ERR_OPEN; goto outlbl; } - byte* buf = (byte*)sys.alloc(this.buf_size); + buf = (byte*)sys.alloc(this.buf_size); if (buf == null) { ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; @@ -275,10 +275,10 @@ namespace SabreTools.Compression.libmspack goto outlbl; } - uint blk_csize = EndGetI32(&buf[patchblk_PatchSize]); - uint blk_dsize = EndGetI32(&buf[patchblk_TargetSize]); - uint blk_ssize = EndGetI32(&buf[patchblk_SourceSize]); - uint blk_crc = EndGetI32(&buf[patchblk_CRC]); + uint blk_csize = System.BitConverter.ToInt32(buf, patchblk_PatchSize); + uint blk_dsize = System.BitConverter.ToInt32(buf, patchblk_TargetSize); + uint blk_ssize = System.BitConverter.ToInt32(buf, patchblk_SourceSize); + uint blk_crc = System.BitConverter.ToInt32(buf, patchblk_CRC); if (blk_dsize > block_max || blk_dsize > target_size || blk_ssize > block_max) @@ -288,19 +288,18 @@ namespace SabreTools.Compression.libmspack } - window_size = (blk_ssize + 32767) & ~32767; + window_size = (uint)((blk_ssize + 32767) & ~32767); window_size += blk_dsize; window_bits = 17; - while (window_bits < 25 && (1U << window_bits) < window_size) + while (window_bits < 25 && (1 << (int)window_bits) < window_size) window_bits++; - in_ofh.available = blk_csize; + in_ofh.available = (int)blk_csize; out_ofh.crc = 0xffffffff; - lzx = lzxd_init(&oabd_sys, (void*)&in_ofh, (void*)&out_ofh, window_bits, - 0, 4096, blk_dsize, 1); - if (!lzx) + lzx = lzxd_init(&oabd_sys, (void*)&in_ofh, (void*)&out_ofh, window_bits, 0, 4096, blk_dsize, 1); + if (lzx == null) { ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; goto outlbl; @@ -316,9 +315,9 @@ namespace SabreTools.Compression.libmspack lzxd_free(lzx); lzx = null; - /* Consume any trailing padding bytes before the next block */ + // Consume any trailing padding bytes before the next block ret = copy_fh(sys, infh, null, in_ofh.available, buf, this.buf_size); - if (ret) goto outlbl; + if (ret != MSPACK_ERR.MSPACK_ERR_OK) goto outlbl; if (out_ofh.crc != blk_crc) { @@ -330,11 +329,11 @@ namespace SabreTools.Compression.libmspack } outlbl: - if (lzx) lzxd_free(lzx); - if (outfh) sys.close(outfh); - if (basefh) sys.close(basefh); - if (infh) sys.close(infh); - sys.free(buf); + if (lzx != null) lzxd_free(lzx); + if (outfh != null) sys.close(outfh); + if (basefh != null) sys.close(basefh); + if (infh != null) sys.close(infh); + if (buf != null) sys.free(buf); return ret; } diff --git a/libmspack/mspack.cs b/libmspack/mspack.cs index 4dc5c60..0aa14bb 100644 --- a/libmspack/mspack.cs +++ b/libmspack/mspack.cs @@ -4,6 +4,8 @@ namespace SabreTools.Compression.libmspack { public partial class mspack { + #region CAB + /// /// Creates a new CAB compressor. /// @@ -14,15 +16,12 @@ namespace SabreTools.Compression.libmspack /// /// Creates a new CAB decompressor. /// - /// A custom structure, or null to use the default - /// A or null - public static mscab_decompressor mspack_create_cab_decompressor(mspack_system sys) + /// A or null + public static CAB.Decompressor CreateCABDecompressor() { - if (sys == null) sys = new mspack_mscab_system(); - - var self = new mscab_decompressor + var self = new CAB.Decompressor { - system = sys, + system = new CAB.CABSystem(), d = null, error = MSPACK_ERR.MSPACK_ERR_OK, @@ -44,8 +43,8 @@ namespace SabreTools.Compression.libmspack /// /// Destroys an existing CAB decompressor. /// - /// The to destroy - public static void mspack_destroy_cab_decompressor(mscab_decompressor self) + /// The to destroy + public static void DestroyCABDecompressor(CAB.Decompressor self) { if (self != null) { @@ -53,7 +52,7 @@ namespace SabreTools.Compression.libmspack if (self.d != null) { if (self.d.infh != null) sys.close(self.d.infh); - cabd_free_decomp(self); + cab.cabd_free_decomp(self); //sys.free(self.d); } @@ -61,6 +60,8 @@ namespace SabreTools.Compression.libmspack } } + #endregion + /// /// Creates a new CHM compressor. /// diff --git a/libmspack/msszdd_decompressor.cs b/libmspack/msszdd_decompressor.cs index 3bf2fd2..c25e883 100644 --- a/libmspack/msszdd_decompressor.cs +++ b/libmspack/msszdd_decompressor.cs @@ -10,7 +10,7 @@ namespace SabreTools.Compression.libmspack /// /// /// - public unsafe class msszdd_decompressor : Decompressor + public unsafe class msszdd_decompressor : BaseDecompressor { /// /// Opens a SZDD file and reads the header. @@ -105,7 +105,7 @@ namespace SabreTools.Compression.libmspack if (sys.read(fh, bufPtr, 6) != 6) return MSPACK_ERR.MSPACK_ERR_READ; if (buf[0] != 0x41) return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; hdr.missing_char = (char)buf[1]; - hdr.length = EndGetI32(&buf[2]); + hdr.length = System.BitConverter.ToInt32(buf, 2); } else if (buf.SequenceEqual(szdd_signature_qbasic)) { @@ -113,7 +113,7 @@ namespace SabreTools.Compression.libmspack hdr.format = MSSZDD_FMT.MSSZDD_FMT_QBASIC; if (sys.read(fh, bufPtr, 4) != 4) return MSPACK_ERR.MSPACK_ERR_READ; hdr.missing_char = '\0'; - hdr.length = EndGetI32(buf); + hdr.length = System.BitConverter.ToInt32(buf, 0); } else {