diff --git a/libmspack/CAB/CABSystem.cs b/libmspack/CAB/CABSystem.cs index 81d9b30..9442bb2 100644 --- a/libmspack/CAB/CABSystem.cs +++ b/libmspack/CAB/CABSystem.cs @@ -109,12 +109,12 @@ namespace SabreTools.Compression.libmspack.CAB int len, full_len; // Reset the input block pointer and end of block pointer - d.i_ptr = d.i_end = (byte*)d.input.Pointer; + d.i_ptr = d.i_end = d.input; do { // Read the block header - if (sys.read(d.infh, (byte*)hdr.Pointer, cfdata_SIZEOF) != cfdata_SIZEOF) + if (sys.read(d.infh, hdr, cfdata_SIZEOF) != cfdata_SIZEOF) { return MSPACK_ERR.MSPACK_ERR_READ; } diff --git a/libmspack/CAB/Decompressor.cs b/libmspack/CAB/Decompressor.cs index f96c1f9..6688a7e 100644 --- a/libmspack/CAB/Decompressor.cs +++ b/libmspack/CAB/Decompressor.cs @@ -21,7 +21,7 @@ namespace SabreTools.Compression.libmspack.CAB public int salvage { get; private set; } - public MSPACK_ERR read_error { get; private set; } + public MSPACK_ERR read_error { get; set; } /// /// Creates a new CAB decompressor @@ -236,7 +236,7 @@ namespace SabreTools.Compression.libmspack.CAB } // Read in the CFHEADER - if (sys.read(fh, (byte*)buf.Pointer, cfhead_SIZEOF) != cfhead_SIZEOF) + if (sys.read(fh, buf, cfhead_SIZEOF) != cfhead_SIZEOF) { return MSPACK_ERR.MSPACK_ERR_READ; } @@ -279,7 +279,7 @@ namespace SabreTools.Compression.libmspack.CAB if (cab.flags.HasFlag(MSCAB_HDR.MSCAB_HDR_RESV)) { - if (sys.read(fh, (byte*)buf.Pointer, cfheadext_SIZEOF) != cfheadext_SIZEOF) + if (sys.read(fh, buf, cfheadext_SIZEOF) != cfheadext_SIZEOF) { return MSPACK_ERR.MSPACK_ERR_READ; } @@ -329,7 +329,7 @@ namespace SabreTools.Compression.libmspack.CAB // Read folders for (i = 0; i < num_folders; i++) { - if (sys.read(fh, (byte*)buf.Pointer, cffold_SIZEOF) != cffold_SIZEOF) + if (sys.read(fh, buf, cffold_SIZEOF) != cffold_SIZEOF) { return MSPACK_ERR.MSPACK_ERR_READ; } @@ -361,7 +361,7 @@ namespace SabreTools.Compression.libmspack.CAB // Read files for (i = 0; i < num_files; i++) { - if (sys.read(fh, (byte*)buf.Pointer, cffile_SIZEOF) != cffile_SIZEOF) + if (sys.read(fh, buf, cffile_SIZEOF) != cffile_SIZEOF) { return MSPACK_ERR.MSPACK_ERR_READ; } @@ -461,12 +461,12 @@ namespace SabreTools.Compression.libmspack.CAB 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]; + FixedArray buf = new FixedArray(256); string str; int len, i, ok; - // Read up to 256 bytes */ - if ((len = sys.read(fh, (byte*)buf.Pointer, 256)) <= 0) + // Read up to 256 bytes + if ((len = sys.read(fh, buf, 256)) <= 0) { error = MSPACK_ERR.MSPACK_ERR_READ; return null; @@ -492,9 +492,9 @@ namespace SabreTools.Compression.libmspack.CAB return null; } - char[] strchr = new char[len]; - sys.copy((byte*)buf.Pointer, &strchr[0], len); - str = new string(strchr); + FixedArray strchr = new FixedArray(len); + sys.copy(buf, (void*)strchr, len); + str = new string(strchr.ToArray()); error = MSPACK_ERR.MSPACK_ERR_OK; return str; } @@ -538,14 +538,14 @@ namespace SabreTools.Compression.libmspack.CAB { mscabd_cabinet cab = null; mspack_system sys; - byte* search_buf; + FixedArray search_buf; mspack_file fh; long filelen, firstlen = 0; sys = this.system; // Allocate a search buffer - search_buf = (byte*)sys.alloc(this.searchbuf_size); + search_buf = new FixedArray(this.searchbuf_size); if (search_buf == null) { this.error = MSPACK_ERR.MSPACK_ERR_NOMEMORY; @@ -590,7 +590,7 @@ namespace SabreTools.Compression.libmspack.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) + private MSPACK_ERR Find(FixedArray buf, mspack_file fh, in string filename, long flen, ref long firstlen, mscabd_cabinet firstcab) { mscabd_cabinet cab, link = null; long caboff, offset, length; @@ -624,7 +624,7 @@ namespace SabreTools.Compression.libmspack.CAB } // Read through the entire buffer. - for (p = (byte*)buf.Pointer, pend = &buf[length]; p < pend;) + for (p = buf, pend = (byte*)buf.Pointer + length; p < pend;) { switch (state) { @@ -660,7 +660,7 @@ namespace SabreTools.Compression.libmspack.CAB 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 - (byte*)buf.Pointer) - 20; + caboff = offset + (p - buf) - 20; // Should reading cabinet fail, restart search just after 'MSCF' offset = caboff + 4; @@ -1132,7 +1132,7 @@ namespace SabreTools.Compression.libmspack.CAB this.d.offset = 0; this.d.block = 0; this.d.outlen = 0; - this.d.i_ptr = this.d.i_end = &d.input[0]; + this.d.i_ptr = this.d.i_end = d.input; // Read_error lasts for the lifetime of a decompressor this.read_error = MSPACK_ERR.MSPACK_ERR_OK; diff --git a/libmspack/CHM/Decompressor.cs b/libmspack/CHM/Decompressor.cs index 078796f..da62113 100644 --- a/libmspack/CHM/Decompressor.cs +++ b/libmspack/CHM/Decompressor.cs @@ -1,4 +1,9 @@ using System; +using System.Linq; +using System.Runtime.InteropServices; +using static SabreTools.Compression.libmspack.CHM.Constants; +using static SabreTools.Compression.libmspack.macros; +using static SabreTools.Compression.libmspack.system; namespace SabreTools.Compression.libmspack.CHM { @@ -8,10 +13,30 @@ namespace SabreTools.Compression.libmspack.CHM /// All fields are READ ONLY. /// /// - public class Decompressor : BaseDecompressor + public unsafe class Decompressor : BaseDecompressor { public mschmd_decompress_state d { get; private set; } + // Filenames of the system files used for decompression. + // Content and ControlData are essential. + // ResetTable is preferred, but SpanInfo can be used if not available + private const string content_name = "::DataSpace/Storage/MSCompressed/Content"; + private const string control_name = "::DataSpace/Storage/MSCompressed/ControlData"; + private const string spaninfo_name = "::DataSpace/Storage/MSCompressed/SpanInfo"; + private const string rtable_name = "::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable"; + + // The GUIDs found in CHM header + private static readonly byte[] guids = new byte[32] + { + // {7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC} + 0x10, 0xFD, 0x01, 0x7C, 0xAA, 0x7B, 0xD0, 0x11, + 0x9E, 0x0C, 0x00, 0xA0, 0xC9, 0x22, 0xE6, 0xEC, + + // {7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC} + 0x11, 0xFD, 0x01, 0x7C, 0xAA, 0x7B, 0xD0, 0x11, + 0x9E, 0x0C, 0x00, 0xA0, 0xC9, 0x22, 0xE6, 0xEC + }; + /// /// Creates a new CHM decompressor /// @@ -56,7 +81,10 @@ namespace SabreTools.Compression.libmspack.CHM /// /// A pointer to a mschmd_header structure, or null on failure /// - public mschmd_header open(in string filename) => throw new NotImplementedException(); + public mschmd_header open(in string filename) + { + return chmd_real_open(filename, 1); + } /// /// Closes a previously opened CHM helpfile. @@ -73,7 +101,364 @@ namespace SabreTools.Compression.libmspack.CHM /// The CHM helpfile to close /// /// - public void close(mschmd_header chm) => throw new NotImplementedException(); + public void close(mschmd_header chm) + { + mschmd_file fi, nfi; + mspack_system sys; + uint i; + + sys = this.system; + + this.error = MSPACK_ERR.MSPACK_ERR_OK; + + // Free files + for (fi = chm.files; fi != null; fi = nfi) + { + nfi = fi.next; + //sys.free(fi); + } + for (fi = chm.sysfiles; fi != null; fi = nfi) + { + nfi = fi.next; + //sys.free(fi); + } + + // If this CHM was being decompressed, free decompression state + if (this.d != null && (this.d.chm == chm)) + { + if (this.d.infh != null) sys.close(this.d.infh); + if (this.d.state != null) lzxd_free(this.d.state); + //sys.free(this.d); + this.d = null; + } + + // If this CHM had a chunk cache, free it and contents + if (chm.chunk_cache != null) + { + for (i = 0; i < chm.num_chunks; i++) sys.free(chm.chunk_cache[i]); + sys.free(chm.chunk_cache); + } + + //sys.free(chm); + } + + /// + /// Reads the basic CHM file headers. If the "entire" parameter is + /// non-zero, all file entries will also be read. fills out a pre-existing + /// mschmd_header structure, allocates memory for files as necessary + /// + private MSPACK_ERR chmd_read_headers(mspack_system sys, mspack_file fh, mschmd_header chm, int entire) + { + uint errors, num_chunks; + FixedArray buf = new FixedArray(0x54); + FixedArray chunk = null; + byte* name, p, end; + mschmd_file fi, link = null; + long offset_hs0, filelen; + int num_entries; + MSPACK_ERR err = MSPACK_ERR.MSPACK_ERR_OK; + + // Initialise pointers + chm.files = null; + chm.sysfiles = null; + chm.chunk_cache = null; + chm.sec0.chm = chm; + chm.sec0.id = 0; + chm.sec1.chm = chm; + chm.sec1.id = 1; + chm.sec1.content = null; + chm.sec1.control = null; + chm.sec1.spaninfo = null; + chm.sec1.rtable = null; + + // Read the first header + if (sys.read(fh, buf, chmhead_SIZEOF) != chmhead_SIZEOF) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + + // Check ITSF signature + if (EndGetI32(buf, chmhead_Signature) != 0x46535449) + { + return MSPACK_ERR.MSPACK_ERR_SIGNATURE; + } + + // Check both header GUIDs + if (!buf.ToArray().Skip(chmhead_GUID1).Take(guids.Length).SequenceEqual(guids)) + { + Console.Error.WriteLine("Incorrect GUIDs"); + return MSPACK_ERR.MSPACK_ERR_SIGNATURE; + } + + chm.version = EndGetI32(buf, chmhead_Version); + chm.timestamp = EndGetM32(buf, chmhead_Timestamp); + chm.language = EndGetI32(buf, chmhead_LanguageID); + if (chm.version > 3) + { + sys.message(fh, "WARNING; CHM version > 3"); + } + + // Read the header section table + if (sys.read(fh, buf, chmhst3_SIZEOF) != chmhst3_SIZEOF) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + + // chmhst3_OffsetCS0 does not exist in version 1 or 2 CHM files. + // The offset will be corrected later, once HS1 is read. + if (read_off64(&offset_hs0, &buf[chmhst_OffsetHS0], sys, fh) || + read_off64(&chm.dir_offset, &buf[chmhst_OffsetHS1], sys, fh) || + read_off64(&chm.sec0.offset, &buf[chmhst3_OffsetCS0], sys, fh)) + { + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // Seek to header section 0 + if (sys.seek(fh, offset_hs0, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0) + { + return MSPACK_ERR.MSPACK_ERR_SEEK; + } + + // Read header section 0 + if (sys.read(fh, buf, chmhs0_SIZEOF) != chmhs0_SIZEOF) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + + if (read_off64(&chm.length, &buf[chmhs0_FileLen], sys, fh)) + { + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // Compare declared CHM file size against actual size + if (mspack_sys_filelen(sys, fh, &filelen) == 0) + { + if (chm.length > filelen) + { + sys.message(fh, $"WARNING; file possibly truncated by {chm.length - filelen} bytes"); + } + else if (chm.length < filelen) + { + sys.message(fh, $"WARNING; possible {filelen - chm.length} extra bytes at end of file"); + } + } + + // Seek to header section 1 + if (sys.seek(fh, chm.dir_offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0) + { + return MSPACK_ERR.MSPACK_ERR_SEEK; + } + + // Read header section 1 + if (sys.read(fh, buf, chmhs1_SIZEOF) != chmhs1_SIZEOF) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + + chm.dir_offset = sys.tell(fh); + chm.chunk_size = EndGetI32(buf, chmhs1_ChunkSize); + chm.density = EndGetI32(buf, chmhs1_Density); + chm.depth = EndGetI32(buf, chmhs1_Depth); + chm.index_root = EndGetI32(buf, chmhs1_IndexRoot); + chm.num_chunks = EndGetI32(buf, chmhs1_NumChunks); + chm.first_pmgl = EndGetI32(buf, chmhs1_FirstPMGL); + chm.last_pmgl = EndGetI32(buf, chmhs1_LastPMGL); + + if (chm.version < 3) + { + // Versions before 3 don't have chmhst3_OffsetCS0 + chm.sec0.offset = chm.dir_offset + (chm.chunk_size * chm.num_chunks); + } + + // Check if content offset or file size is wrong + if (chm.sec0.offset > chm.length) + { + Console.Error.WriteLine("content section begins after file has ended"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // Ensure there are chunks and that chunk size is + // large enough for signature and num_entries + if (chm.chunk_size < (pmgl_Entries + 2)) + { + Console.Error.WriteLine("chunk size not large enough"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + if (chm.num_chunks == 0) + { + Console.Error.WriteLine("no chunks"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // The chunk_cache data structure is not great; large values for num_chunks + // or num_chunks*chunk_size can exhaust all memory. Until a better chunk + // cache is implemented, put arbitrary limits on num_chunks and chunk size. + if (chm.num_chunks > 100000) + { + Console.Error.WriteLine("more than 100,000 chunks"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + if (chm.chunk_size > 8192) + { + Console.Error.WriteLine("chunk size over 8192 (get in touch if this is valid)"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + if ((long)chm.chunk_size * (long)chm.num_chunks > chm.length) + { + Console.Error.WriteLine("chunks larger than entire file"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // Common sense checks on header section 1 fields + if (chm.chunk_size != 4096) + { + sys.message(fh, "WARNING; chunk size is not 4096"); + } + if (chm.first_pmgl != 0) + { + sys.message(fh, "WARNING; first PMGL chunk is not zero"); + } + if (chm.first_pmgl > chm.last_pmgl) + { + Console.Error.WriteLine("first pmgl chunk is after last pmgl chunk"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + if (chm.index_root != 0xFFFFFFFF && chm.index_root >= chm.num_chunks) + { + Console.Error.WriteLine("index_root outside valid range"); + return MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + // If we are doing a quick read, stop here! + if (entire == 0) + { + return MSPACK_ERR.MSPACK_ERR_OK; + } + + // Seek to the first PMGL chunk, and reduce the number of chunks to read + if (chm.first_pmgl != 0) + { + long pmgl_offset = (long)chm.first_pmgl * (long)chm.chunk_size; + if (sys.seek(fh, pmgl_offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0) + { + return MSPACK_ERR.MSPACK_ERR_SEEK; + } + } + num_chunks = chm.last_pmgl - chm.first_pmgl + 1; + + chunk = new FixedArray((int)chm.chunk_size); + + // Read and process all chunks from FirstPMGL to LastPMGL + errors = 0; + while (num_chunks-- > 0) + { + // Read next chunk + if (sys.read(fh, chunk, (int)chm.chunk_size) != (int)chm.chunk_size) + { + sys.free(chunk); + return MSPACK_ERR.MSPACK_ERR_READ; + } + + // Process only directory (PMGL) chunks + if (EndGetI32(chunk, pmgl_Signature) != 0x4C474D50) continue; + + if (EndGetI32(chunk, pmgl_QuickRefSize) < 2) + { + sys.message(fh, "WARNING; PMGL quickref area is too small"); + } + if (EndGetI32(chunk, pmgl_QuickRefSize) > + (chm.chunk_size - pmgl_Entries)) + { + sys.message(fh, "WARNING; PMGL quickref area is too large"); + } + + p = (byte*)chunk.Pointer + pmgl_Entries; + end = (byte*)chunk.Pointer + chm.chunk_size - 2; + num_entries = EndGetI16(chunk, (int)(chm.chunk_size - 2)); + + while (num_entries-- > 0) + { + uint name_len, section; + long offset, length; + name_len = read_encint(&p, end, &err); + if (err != MSPACK_ERR.MSPACK_ERR_OK || (name_len > (uint)(end - p))) goto encint_err; + name = p; p += name_len; + section = read_encint(&p, end, &err); + offset = read_encint(&p, end, &err); + length = read_encint(&p, end, &err); + if (err != MSPACK_ERR.MSPACK_ERR_OK) goto encint_err; + + // Ignore blank or one-char (e.g. "/") filenames we'd return as blank */ + if (name_len < 2 || name[0] == 0x00 || name[1] == 0x00) continue; + + // Empty files and directory names are stored as a file entry at + // offset 0 with length 0. We want to keep empty files, but not + // directory names, which end with a "/" + if ((offset == 0) && (length == 0)) + { + if ((name_len > 0) && (name[name_len - 1] == '/')) continue; + } + + if (section > 1) + { + sys.message(fh, $"Invalid section number '{section}'."); + continue; + } + + fi = new mschmd_file(); + fi.next = null; + fi.section = (section == 0 ? (mschmd_section)chm.sec0 : (mschmd_section)chm.sec1); + fi.offset = offset; + fi.length = length; + + char[] filenameArr = new char[name_len]; + Marshal.Copy((IntPtr)name, filenameArr, 0, (int)name_len); + filenameArr[(int)name_len] = '\0'; + fi.filename = new string(filenameArr); + + if (name[0] == ':' && name[1] == ':') + { + // System file + if (name_len == 40 && fi.filename.StartsWith(content_name)) + { + chm.sec1.content = fi; + } + else if (name_len == 44 && fi.filename.StartsWith(control_name)) + { + chm.sec1.control = fi; + } + else if (name_len == 41 && fi.filename.StartsWith(spaninfo_name)) + { + chm.sec1.spaninfo = fi; + } + else if (name_len == 105 && fi.filename.StartsWith(rtable_name)) + { + chm.sec1.rtable = fi; + } + fi.next = chm.sysfiles; + chm.sysfiles = fi; + } + else + { + // Normal file + if (link != null) link.next = fi; else chm.files = fi; + link = fi; + } + } + + // This is reached either when num_entries runs out, or if + // an ENCINT is badly encoded + encint_err: + if (num_entries >= 0) + { + Console.Error.WriteLine("bad encint before all entries could be read"); + errors++; + } + } + + sys.free(chunk); + return (errors > 0) ? MSPACK_ERR.MSPACK_ERR_DATAFORMAT : MSPACK_ERR.MSPACK_ERR_OK; + } /// /// Extracts a file from a CHM helpfile. @@ -125,9 +510,56 @@ namespace SabreTools.Compression.libmspack.CHM /// A pointer to a mschmd_header structure, or null on failure /// /// - /// + /// /// - public mschmd_header fast_open(in string filename) => throw new NotImplementedException(); + public mschmd_header fast_open(in string filename) + { + return chmd_real_open(filename, 0); + } + + /// + /// The real implementation of chmd_open() and chmd_fast_open(). It simply + /// passes the "entire" parameter to chmd_read_headers(), which will then + /// either read all headers, or a bare mininum. + /// + private mschmd_header chmd_real_open(in string filename, int entire) + { + mschmd_header chm = null; + MSPACK_ERR error; + + mspack_system sys = this.system; + + mspack_file fh; + if ((fh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ)) != null) + { + chm = new mschmd_header(); + chm.filename = filename; + error = chmd_read_headers(sys, fh, chm, entire); + if (error != MSPACK_ERR.MSPACK_ERR_OK) + { + // If the error is DATAFORMAT, and there are some results, return + // partial results with a warning, rather than nothing + if (error == MSPACK_ERR.MSPACK_ERR_DATAFORMAT && (chm.files != null || chm.sysfiles != null)) + { + sys.message(fh, "WARNING; contents are corrupt"); + error = MSPACK_ERR.MSPACK_ERR_OK; + } + else + { + close(chm); + chm = null; + } + } + + this.error = error; + sys.close(fh); + } + else + { + this.error = MSPACK_ERR.MSPACK_ERR_OPEN; + } + return chm; + } /// /// Finds file details quickly. @@ -161,12 +593,330 @@ namespace SabreTools.Compression.libmspack.CHM /// The CHM helpfile to search for the file /// The filename of the file to search for /// A pointer to a caller-provded mschmd_file structure - /// sizeof(struct mschmd_file) + /// sizeof(mschmd_file) /// An error code, or MSPACK_ERR_OK if successful /// /// - /// + /// /// - public MSPACK_ERR fast_find(mschmd_header chm, in string filename, mschmd_file f_ptr, int f_size) => throw new NotImplementedException(); + public MSPACK_ERR fast_find(mschmd_header chm, in string filename, ref mschmd_file f_ptr, int f_size) + { + mspack_system sys; + mspack_file fh; + + // p and end are initialised to prevent MSVC warning about "potentially" + // uninitialised usage. This is provably untrue, but MS won't fix: + // https://developercommunity.visualstudio.com/content/problem/363489/c4701-false-positive-warning.html + FixedArray chunk; + byte* p = null, end = null; + MSPACK_ERR err = MSPACK_ERR.MSPACK_ERR_OK; + int result = -1; + uint n, sec; + + if (chm == null || f_ptr == null) + { + return MSPACK_ERR.MSPACK_ERR_ARGS; + } + + sys = this.system; + + // Clear the results structure + f_ptr = new mschmd_file(); + + if ((fh = sys.open(chm.filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ)) == null) + { + return MSPACK_ERR.MSPACK_ERR_OPEN; + } + + // Go through PMGI chunk hierarchy to reach PMGL chunk + if (chm.index_root < chm.num_chunks) + { + n = chm.index_root; + for (; ; ) + { + if ((chunk = read_chunk(chm, fh, n)) == null) + { + sys.close(fh); + return this.error; + } + + // Search PMGI/PMGL chunk. exit early if no entry found + if ((result = search_chunk(chm, chunk, filename, &p, &end)) <= 0) + { + break; + } + + // Found result. loop around for next chunk if this is PMGI + if (chunk[3] == 0x4C) break; + + n = read_encint(&p, end, &err); + if (err != MSPACK_ERR.MSPACK_ERR_OK) goto encint_err; + } + } + else + { + // PMGL chunks only, search from first_pmgl to last_pmgl + for (n = chm.first_pmgl; n <= chm.last_pmgl; n = EndGetI32(chunk, pmgl_NextChunk)) + { + if ((chunk = read_chunk(chm, fh, n)) == null) + { + err = this.error; + break; + } + + // Search PMGL chunk. exit if file found + if ((result = search_chunk(chm, chunk, filename, &p, &end)) > 0) + { + break; + } + + // Stop simple infinite loops: can't visit the same chunk twice + if (n == EndGetI32(chunk, pmgl_NextChunk)) + { + break; + } + } + } + + // If we found a file, read it + if (result > 0) + { + sec = read_encint(&p, end, &err); + f_ptr.section = (sec == 0) ? (mschmd_section)chm.sec0 : (mschmd_section)chm.sec1; + f_ptr.offset = read_encint(&p, end, &err); + f_ptr.length = read_encint(&p, end, &err); + if (err != MSPACK_ERR.MSPACK_ERR_OK) goto encint_err; + } + else if (result < 0) + { + err = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + sys.close(fh); + return this.error = err; + + encint_err: + Console.Error.WriteLine("Bad encint in PGMI/PGML chunk"); + sys.close(fh); + return this.error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + } + + /// + /// Reads the given chunk into memory, storing it in a chunk cache + /// so it doesn't need to be read from disk more than once + /// + /// + private FixedArray read_chunk(mschmd_header chm, mspack_file fh, uint chunk_num) + { + mspack_system sys = this.system; + FixedArray buf; + + // Check arguments - most are already checked by chmd_fast_find + if (chunk_num >= chm.num_chunks) return null; + + // Ensure chunk cache is available + if (chm.chunk_cache == null) + { + chm.chunk_cache = new FixedArray[chm.num_chunks]; + if (chm.chunk_cache == null) + { + this.error = MSPACK_ERR.MSPACK_ERR_NOMEMORY; + return null; + } + } + + // Try to answer out of chunk cache + if (chm.chunk_cache[chunk_num] != null) return chm.chunk_cache[chunk_num]; + + // Need to read chunk - allocate memory for it + buf = new FixedArray((int)chm.chunk_size); + + // Seek to block and read it + if (sys.seek(fh, chm.dir_offset + (chunk_num * chm.chunk_size), MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0) + { + this.error = MSPACK_ERR.MSPACK_ERR_SEEK; + sys.free(buf); + return null; + } + if (sys.read(fh, buf, (int)chm.chunk_size) != (int)chm.chunk_size) + { + this.error = MSPACK_ERR.MSPACK_ERR_READ; + sys.free(buf); + return null; + } + + // Check the signature. Is is PMGL or PMGI? + if (!((buf[0] == 0x50) && (buf[1] == 0x4D) && (buf[2] == 0x47) && ((buf[3] == 0x4C) || (buf[3] == 0x49)))) + { + this.error = MSPACK_ERR.MSPACK_ERR_SEEK; + sys.free(buf); + return null; + } + + // All OK. Store chunk in cache and return it + return chm.chunk_cache[chunk_num] = buf; + } + + /// + /// Searches a PMGI/PMGL chunk for a given filename entry. Returns -1 on + /// data format error, 0 if entry definitely not found, 1 if entry + /// found. In the latter case, *result and *result_end are set pointing + /// to that entry's data (either the "next chunk" ENCINT for a PMGI or + /// the section, offset and length ENCINTs for a PMGL). + /// + /// In the case of PMGL chunks, the entry has definitely been + /// found. In the case of PMGI chunks, the entry which points to the + /// chunk that may eventually contain that entry has been found. + /// + /// + private int search_chunk(mschmd_header chm, in FixedArray chunk, in string filename, byte** result, byte** result_end) + { + byte* p; + uint qr_size, num_entries, qr_entries, qr_density, name_len; + uint L, R, M, entries_off, is_pmgl; + int cmp; + MSPACK_ERR err = MSPACK_ERR.MSPACK_ERR_OK; + + uint fname_len = (uint)filename.Length; + + // PMGL chunk or PMGI chunk? (note: read_chunk() has already + // checked the rest of the characters in the chunk signature) + if (chunk[3] == 0x4C) + { + is_pmgl = 1; + entries_off = pmgl_Entries; + } + else + { + is_pmgl = 0; + entries_off = pmgi_Entries; + } + + // Step 1: binary search first filename of each QR entry + // - target filename == entry + // found file + // - target filename < all entries + // file not found + // - target filename > all entries + // proceed to step 2 using final entry + // - target filename between two searched entries + // proceed to step 2 + qr_size = EndGetI32(chunk, pmgl_QuickRefSize); + int start = (int)(chm.chunk_size - 2); + int end = (int)(chm.chunk_size - qr_size); + num_entries = EndGetI16(chunk, (int)(chm.chunk_size - 2)); + qr_density = (uint)(1 + (1 << (int)chm.density)); + qr_entries = (num_entries + qr_density - 1) / qr_density; + + if (num_entries == 0) + { + Console.Error.WriteLine("Chunk has no entries"); + return -1; + } + + if (qr_size > chm.chunk_size) + { + Console.Error.WriteLine("Quickref size > chunk size"); + return -1; + } + + *result_end = &chunk[end]; + + if (((int)qr_entries * 2) > (start - end)) + { + Console.Error.WriteLine("WARNING; more quickrefs than quickref space"); + qr_entries = 0; // But we can live with it + } + + if (qr_entries > 0) + { + L = 0; + R = qr_entries - 1; + do + { + // Pick new midpoint + M = (L + R) >> 1; + + // Compare filename with entry QR points to + p = &chunk[entries_off + (M != 0 ? EndGetI16(chunk, start - (int)(M << 1)) : 0)]; + name_len = read_encint(&p, end, &err); + if (err != MSPACK_ERR.MSPACK_ERR_OK || (name_len > (uint)(end - p))) goto encint_err; + cmp = compare(filename, (char*)p, fname_len, name_len); + + if (cmp == 0) break; + else if (cmp < 0) { if (M) R = M - 1; else return 0; } + else if (cmp > 0) L = M + 1; + } while (L <= R); + M = (L + R) >> 1; + + if (cmp == 0) + { + /* exact match! */ + p += name_len; + *result = p; + return 1; + } + + /* otherwise, read the group of entries for QR entry M */ + p = &chunk[entries_off + (M ? EndGetI16(chunk, start - (M << 1)) : 0)]; + num_entries -= (M * qr_density); + if (num_entries > qr_density) num_entries = qr_density; + } + else + { + p = &chunk[entries_off]; + } + + /* Step 2: linear search through the set of entries reached in step 1. + * - filename == any entry + * found entry + * - filename < all entries (PMGI) or any entry (PMGL) + * entry not found, stop now + * - filename > all entries + * entry not found (PMGL) / maybe found (PMGI) + * - + */ + *result = null; + while (num_entries-- > 0) + { + name_len = read_encint(&p, end, &err); + if (err || (name_len > (uint)(end - p))) goto encint_err; + cmp = compare(filename, (char*)p, fname_len, name_len); + p += name_len; + + if (cmp == 0) + { + /* entry found */ + *result = p; + return 1; + } + + if (cmp < 0) + { + /* entry not found (PMGL) / maybe found (PMGI) */ + break; + } + + /* read and ignore the rest of this entry */ + if (is_pmgl) + { + while (p < end && (*p++ & 0x80)) ; /* skip section ENCINT */ + while (p < end && (*p++ & 0x80)) ; /* skip offset ENCINT */ + while (p < end && (*p++ & 0x80)) ; /* skip length ENCINT */ + } + else + { + *result = p; /* store potential final result */ + while (p < end && (*p++ & 0x80)) ; /* skip chunk number ENCINT */ + } + } + + /* PMGL? not found. PMGI? maybe found */ + return (is_pmgl) ? 0 : (*result ? 1 : 0); + + encint_err: + Console.Error.WriteLine("bad encint while searching"); + return -1; + } } } \ No newline at end of file diff --git a/libmspack/CHM/mschmd_header.cs b/libmspack/CHM/mschmd_header.cs index 9b78b4a..844e5bc 100644 --- a/libmspack/CHM/mschmd_header.cs +++ b/libmspack/CHM/mschmd_header.cs @@ -115,6 +115,6 @@ namespace SabreTools.Compression.libmspack /// A cache of loaded chunks, filled in by mschm_decoder::fast_find(). /// Available only in CHM decoder version 2 and above. /// - public byte** chunk_cache { get; set; } + public FixedArray[] chunk_cache { get; set; } } } \ No newline at end of file diff --git a/libmspack/FixedArray.cs b/libmspack/FixedArray.cs index 052d622..9493f11 100644 --- a/libmspack/FixedArray.cs +++ b/libmspack/FixedArray.cs @@ -5,18 +5,35 @@ namespace SabreTools.Compression.libmspack { public unsafe class FixedArray where T : struct { + /// + /// Direct access to the internal pointer + /// public IntPtr Pointer { get; private set; } - int sizeofT { get { return Marshal.SizeOf(typeof(T)); } } + /// + /// Size of the T object + /// + private int sizeofT { get { return Marshal.SizeOf(typeof(T)); } } + + /// + /// Length of the fixed array + /// + private int _length; public T this[int i] { get { + if (i < 0 || i >= _length) + return default; + return (T)Marshal.PtrToStructure(Pointer + i * sizeofT, typeof(T)); } set { + if (i < 0 || i >= _length) + return; + Marshal.StructureToPtr(value, Pointer + i * sizeofT, false); } } @@ -24,6 +41,7 @@ namespace SabreTools.Compression.libmspack public FixedArray(int length) { Pointer = Marshal.AllocHGlobal(sizeofT * length); + _length = 0; } ~FixedArray() @@ -31,10 +49,17 @@ namespace SabreTools.Compression.libmspack Marshal.FreeHGlobal(Pointer); } + public static implicit operator T*(FixedArray arr) => (T*)arr.Pointer; + + public static implicit operator T[](FixedArray arr) => arr.ToArray(); + /// public bool SequenceEqual(T[] arr) { - for (int i = 0; i < arr.Length; i++) + if (arr.Length < _length) + return false; + + for (int i = 0; i < _length; i++) { if (!this[i].Equals(arr[i])) return false; @@ -42,5 +67,20 @@ namespace SabreTools.Compression.libmspack return true; } + + /// + /// Convert the unmanaged data to an array + /// + /// Array created from the pointer data + public T[] ToArray() + { + T[] arr = new T[_length]; + for (int i = 0; i < _length; i++) + { + arr[i] = this[i]; + } + + return arr; + } } } \ No newline at end of file diff --git a/libmspack/KWAJ/Decompressor.cs b/libmspack/KWAJ/Decompressor.cs index 8cf6c4b..17972ca 100644 --- a/libmspack/KWAJ/Decompressor.cs +++ b/libmspack/KWAJ/Decompressor.cs @@ -1,5 +1,6 @@ using System; using static SabreTools.Compression.libmspack.KWAJ.Constants; +using static SabreTools.Compression.libmspack.macros; namespace SabreTools.Compression.libmspack.KWAJ { @@ -101,11 +102,11 @@ namespace SabreTools.Compression.libmspack.KWAJ /// private MSPACK_ERR ReadHeaders(mspack_system sys, mspack_file fh, mskwajd_header hdr) { - byte[] buf = new byte[16]; + FixedArray buf = new FixedArray(16); int i; // Read in the header - if (sys.read(fh, &buf[0], kwajh_SIZEOF) != kwajh_SIZEOF) + if (sys.read(fh, buf, kwajh_SIZEOF) != kwajh_SIZEOF) { return MSPACK_ERR.MSPACK_ERR_READ; } @@ -131,7 +132,7 @@ namespace SabreTools.Compression.libmspack.KWAJ // 4 bytes: length of unpacked file if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASLENGTH)) { - if (sys.read(fh, &buf[0], 4) != 4) + if (sys.read(fh, buf, 4) != 4) return MSPACK_ERR.MSPACK_ERR_READ; hdr.length = BitConverter.ToUInt32(buf, 0); @@ -140,14 +141,14 @@ namespace SabreTools.Compression.libmspack.KWAJ // 2 bytes: unknown purpose if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASUNKNOWN1)) { - if (sys.read(fh, &buf[0], 2) != 2) + if (sys.read(fh, buf, 2) != 2) return MSPACK_ERR.MSPACK_ERR_READ; } // 2 bytes: length of section, then [length] bytes: unknown purpose if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASUNKNOWN2)) { - if (sys.read(fh, &buf[0], 2) != 2) + if (sys.read(fh, buf, 2) != 2) return MSPACK_ERR.MSPACK_ERR_READ; i = BitConverter.ToUInt16(buf, 0); if (sys.seek(fh, i, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0) @@ -168,7 +169,7 @@ namespace SabreTools.Compression.libmspack.KWAJ if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASFILENAME)) { // Read and copy up to 9 bytes of a null terminated string - if ((len = sys.read(fh, &buf[0], 9)) < 2) + if ((len = sys.read(fh, buf, 9)) < 2) return MSPACK_ERR.MSPACK_ERR_READ; for (i = 0; i < len; i++) @@ -192,7 +193,7 @@ namespace SabreTools.Compression.libmspack.KWAJ *fn++ = '.'; // Read and copy up to 4 bytes of a null terminated string - if ((len = sys.read(fh, &buf[0], 4)) < 2) + if ((len = sys.read(fh, buf, 4)) < 2) return MSPACK_ERR.MSPACK_ERR_READ; for (i = 0; i < len; i++) @@ -215,10 +216,10 @@ namespace SabreTools.Compression.libmspack.KWAJ // 2 bytes: extra text length then [length] bytes of extra text data if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASEXTRATEXT)) { - if (sys.read(fh, &buf[0], 2) != 2) + if (sys.read(fh, buf, 2) != 2) return MSPACK_ERR.MSPACK_ERR_READ; - i = BitConverter.ToUInt16(&buf[0]); + i = EndGetI16(buf, 0); hdr.extra = (char*)sys.alloc(i + 1); if (hdr.extra == null) return MSPACK_ERR.MSPACK_ERR_NOMEMORY; diff --git a/libmspack/None/DecompressState.cs b/libmspack/None/DecompressState.cs index c429a99..fde6e1b 100644 --- a/libmspack/None/DecompressState.cs +++ b/libmspack/None/DecompressState.cs @@ -36,8 +36,8 @@ namespace SabreTools.Compression.libmspack.None while (bytes > 0) { int run = (bytes > s.BufferSize) ? s.BufferSize : (int)bytes; - if (s.InternalSystem.read(s.Input, &s.Buffer[0], run) != run) return MSPACK_ERR.MSPACK_ERR_READ; - if (s.InternalSystem.write(s.Output, &s.Buffer[0], run) != run) return MSPACK_ERR.MSPACK_ERR_WRITE; + if (s.InternalSystem.read(s.Input, s.Buffer, run) != run) return MSPACK_ERR.MSPACK_ERR_READ; + if (s.InternalSystem.write(s.Output, s.Buffer, run) != run) return MSPACK_ERR.MSPACK_ERR_WRITE; bytes -= run; } diff --git a/libmspack/None/State.cs b/libmspack/None/State.cs index 3d7f401..1b87b68 100644 --- a/libmspack/None/State.cs +++ b/libmspack/None/State.cs @@ -11,7 +11,7 @@ namespace SabreTools.Compression.libmspack.None public mspack_file Output { get; private set; } - public byte* Buffer { get; private set; } + public FixedArray Buffer { get; private set; } public int BufferSize { get; private set; } @@ -20,7 +20,7 @@ namespace SabreTools.Compression.libmspack.None this.InternalSystem = sys; this.Input = infh; this.Output = outfh; - this.Buffer = (byte*)new FixedArray(bufsize).Pointer; + this.Buffer = new FixedArray(bufsize); this.BufferSize = bufsize; } diff --git a/libmspack/OAB/Decompressor.cs b/libmspack/OAB/Decompressor.cs index bff36d9..5f0e879 100644 --- a/libmspack/OAB/Decompressor.cs +++ b/libmspack/OAB/Decompressor.cs @@ -45,8 +45,8 @@ namespace SabreTools.Compression.libmspack.OAB mspack_system sys; mspack_file infh = null; mspack_file outfh = null; - byte* buf = null; - byte[] hdrbuf = new byte[oabhead_SIZEOF]; + FixedArray buf = null; + FixedArray hdrbuf = new FixedArray(oabhead_SIZEOF); uint block_max, target_size; lzxd_stream lzx = null; OABSystem oabd_sys; @@ -62,7 +62,7 @@ namespace SabreTools.Compression.libmspack.OAB goto outlbl; } - if (sys.read(infh, &hdrbuf[0], oabhead_SIZEOF) != oabhead_SIZEOF) + if (sys.read(infh, hdrbuf, oabhead_SIZEOF) != oabhead_SIZEOF) { ret = MSPACK_ERR.MSPACK_ERR_READ; goto outlbl; @@ -85,7 +85,7 @@ namespace SabreTools.Compression.libmspack.OAB goto outlbl; } - buf = (byte*)sys.alloc(this.buf_size); + buf = new FixedArray(this.buf_size); if (buf == null) { ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; @@ -212,7 +212,7 @@ namespace SabreTools.Compression.libmspack.OAB { mspack_file infh = null, basefh = null, outfh = null; lzxd_stream lzx = null; - byte* buf = null; + FixedArray buf = null; uint window_bits, window_size; MSPACK_ERR ret = MSPACK_ERR.MSPACK_ERR_OK; @@ -226,7 +226,7 @@ namespace SabreTools.Compression.libmspack.OAB } FixedArray hdrbuf = new FixedArray(patchhead_SIZEOF); - if (sys.read(infh, (byte*)hdrbuf.Pointer, patchhead_SIZEOF) != patchhead_SIZEOF) + if (sys.read(infh, hdrbuf, patchhead_SIZEOF) != patchhead_SIZEOF) { ret = MSPACK_ERR.MSPACK_ERR_READ; goto outlbl; @@ -260,7 +260,7 @@ namespace SabreTools.Compression.libmspack.OAB goto outlbl; } - buf = (byte*)sys.alloc(this.buf_size); + buf = new FixedArray(this.buf_size); if (buf == null) { ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; @@ -343,7 +343,7 @@ namespace SabreTools.Compression.libmspack.OAB if (outfh != null) sys.close(outfh); if (basefh != null) sys.close(basefh); if (infh != null) sys.close(infh); - if (buf != null) sys.free(buf); + //if (buf != null) sys.free(buf); return ret; } diff --git a/libmspack/Quantum/qtm.cs b/libmspack/Quantum/qtm.cs index 024f04e..4bf9c7e 100644 --- a/libmspack/Quantum/qtm.cs +++ b/libmspack/Quantum/qtm.cs @@ -113,22 +113,6 @@ namespace SabreTools.Compression.libmspack } } - /// - /// 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. /// @@ -198,15 +182,15 @@ namespace SabreTools.Compression.libmspack // - 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); + qtm.model0 = new qtmd_model(qtm.m0sym, 0, 64); + qtm.model1 = new qtmd_model(qtm.m1sym, 64, 64); + qtm.model2 = new qtmd_model(qtm.m2sym, 128, 64); + qtm.model3 = new qtmd_model(qtm.m3sym, 192, 64); + qtm.model4 = new qtmd_model(qtm.m4sym, 0, (i > 24) ? 24 : i); + qtm.model5 = new qtmd_model(qtm.m5sym, 0, (i > 36) ? 36 : i); + qtm.model6 = new qtmd_model(qtm.m6sym, 0, i); + qtm.model6len = new qtmd_model(qtm.m6lsym, 0, 27); + qtm.model7 = new qtmd_model(qtm.m7sym, 0, 7); // All ok return qtm; diff --git a/libmspack/Quantum/qtmd_model.cs b/libmspack/Quantum/qtmd_model.cs index 1068f90..b08fc7d 100644 --- a/libmspack/Quantum/qtmd_model.cs +++ b/libmspack/Quantum/qtmd_model.cs @@ -6,6 +6,22 @@ namespace SabreTools.Compression.libmspack public int entries { get; set; } - public qtmd_modelsym* syms { get; set; } + public qtmd_modelsym[] syms { get; set; } + + /// + /// Initialises a model to decode symbols from [start] to [start]+[len]-1 + /// + public qtmd_model(qtmd_modelsym[] syms, int start, int len) + { + this.shiftsleft = 4; + this.entries = len; + this.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 + } + } } } \ No newline at end of file diff --git a/libmspack/SZDD/Decompressor.cs b/libmspack/SZDD/Decompressor.cs index 6ff5f09..a1bc5ed 100644 --- a/libmspack/SZDD/Decompressor.cs +++ b/libmspack/SZDD/Decompressor.cs @@ -1,4 +1,3 @@ -using System.Linq; using static SabreTools.Compression.libmspack.macros; using static SabreTools.Compression.libmspack.SZDD.Constants; @@ -112,7 +111,7 @@ namespace SabreTools.Compression.libmspack.SZDD FixedArray buf = new FixedArray(8); // Read and check signature - if (sys.read(fh, (byte*)buf.Pointer, 8) != 8) return MSPACK_ERR.MSPACK_ERR_READ; + if (sys.read(fh, buf, 8) != 8) return MSPACK_ERR.MSPACK_ERR_READ; if (buf.SequenceEqual(szdd_signature_expand)) { @@ -120,7 +119,7 @@ namespace SabreTools.Compression.libmspack.SZDD hdr.format = MSSZDD_FMT.MSSZDD_FMT_NORMAL; // Read the rest of the header - if (sys.read(fh, (byte*)buf.Pointer, 6) != 6) return MSPACK_ERR.MSPACK_ERR_READ; + if (sys.read(fh, buf, 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); @@ -129,7 +128,7 @@ namespace SabreTools.Compression.libmspack.SZDD { // Special QBasic SZDD hdr.format = MSSZDD_FMT.MSSZDD_FMT_QBASIC; - if (sys.read(fh, (byte*)buf.Pointer, 4) != 4) return MSPACK_ERR.MSPACK_ERR_READ; + if (sys.read(fh, buf, 4) != 4) return MSPACK_ERR.MSPACK_ERR_READ; hdr.missing_char = '\0'; hdr.length = EndGetI32(buf, 0); }