diff --git a/libmspack/msoab_decompressor.cs b/libmspack/msoab_decompressor.cs index a9f2466..933a114 100644 --- a/libmspack/msoab_decompressor.cs +++ b/libmspack/msoab_decompressor.cs @@ -1,3 +1,5 @@ +using static SabreTools.Compression.libmspack.oab; + namespace SabreTools.Compression.libmspack { /// @@ -7,7 +9,7 @@ namespace SabreTools.Compression.libmspack /// /// /// - public abstract class msoab_decompressor + public unsafe class msoab_decompressor { public mspack_system system { get; set; } @@ -28,8 +30,150 @@ namespace SabreTools.Compression.libmspack /// The filename of the output file. This is passed /// directly to mspack_system::open(). /// - /// An error code, or MSPACK_ERR_OK if successful - public abstract MSPACK_ERR decompress(in string input, in string output); + /// An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful + public MSPACK_ERR decompress(in string input, in string output) + { + msoab_decompressor_p* self = (msoab_decompressor_p*)_self; + mspack_system* sys; + mspack_file* infh = NULL; + mspack_file* outfh = NULL; + byte* buf = NULL; + byte[] hdrbuf = new byte[oabhead_SIZEOF]; + uint block_max, target_size; + lzxd_stream* lzx = NULL; + mspack_system oabd_sys; + oabd_file in_ofh, out_ofh; + uint window_bits; + int ret = MSPACK_ERR_OK; + + if (!self) return MSPACK_ERR_ARGS; + sys = self->system; + + infh = sys->open(sys, input, MSPACK_SYS_OPEN_READ); + if (!infh) + { + ret = MSPACK_ERR_OPEN; + goto outlbl; + } + + if (sys->read(infh, hdrbuf, oabhead_SIZEOF) != oabhead_SIZEOF) + { + ret = MSPACK_ERR_READ; + goto outlbl; + } + + if (EndGetI32(&hdrbuf[oabhead_VersionHi]) != 3 || + EndGetI32(&hdrbuf[oabhead_VersionLo]) != 1) + { + ret = MSPACK_ERR_SIGNATURE; + goto outlbl; + } + + block_max = EndGetI32(&hdrbuf[oabhead_BlockMax]); + target_size = EndGetI32(&hdrbuf[oabhead_TargetSize]); + + outfh = sys->open(sys, output, MSPACK_SYS_OPEN_WRITE); + if (!outfh) + { + ret = MSPACK_ERR_OPEN; + goto outlbl; + } + + buf = sys->alloc(sys, self->buf_size); + if (!buf) + { + ret = MSPACK_ERR_NOMEMORY; + goto outlbl; + } + + oabd_sys = *sys; + oabd_sys.read = oabd_sys_read; + oabd_sys.write = oabd_sys_write; + + in_ofh.orig_sys = sys; + in_ofh.orig_file = infh; + + out_ofh.orig_sys = sys; + out_ofh.orig_file = outfh; + + while (target_size) + { + uint blk_csize, blk_dsize, blk_crc, blk_flags; + + if (sys->read(infh, buf, oabblk_SIZEOF) != oabblk_SIZEOF) + { + ret = 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]); + + if (blk_dsize > block_max || blk_dsize > target_size || blk_flags > 1) + { + ret = MSPACK_ERR_DATAFORMAT; + goto outlbl; + } + + if (!blk_flags) + { + /* Uncompressed block */ + if (blk_dsize != blk_csize) + { + ret = MSPACK_ERR_DATAFORMAT; + goto outlbl; + } + ret = copy_fh(sys, infh, outfh, blk_dsize, buf, self->buf_size); + if (ret) goto outlbl; + } + else + { + /* LZX compressed block */ + window_bits = 17; + + while (window_bits < 25 && (1U << window_bits) < blk_dsize) + window_bits++; + + in_ofh.available = blk_csize; + out_ofh.crc = 0xffffffff; + + lzx = lzxd_init(&oabd_sys, (void*)&in_ofh, (void*)&out_ofh, window_bits, + 0, self->buf_size, blk_dsize, 1); + if (!lzx) + { + ret = MSPACK_ERR_NOMEMORY; + goto outlbl; + } + + ret = lzxd_decompress(lzx, blk_dsize); + if (ret != MSPACK_ERR_OK) + goto outlbl; + + lzxd_free(lzx); + lzx = NULL; + + /* Consume any trailing padding bytes before the next block */ + ret = copy_fh(sys, infh, NULL, in_ofh.available, buf, self->buf_size); + if (ret) goto outlbl; + + if (out_ofh.crc != blk_crc) + { + ret = MSPACK_ERR_CHECKSUM; + goto outlbl; + } + } + target_size -= blk_dsize; + } + + outlbl: + if (lzx) lzxd_free(lzx); + if (outfh) sys->close(outfh); + if (infh) sys->close(infh); + sys->free(buf); + + return ret; + } /// /// Decompresses an Offline Address Book with an incremental patch file. @@ -58,8 +202,169 @@ namespace SabreTools.Compression.libmspack /// The filename of the output file. This is passed /// directly to mspack_system::open(). /// - /// An error code, or MSPACK_ERR_OK if successful - public abstract MSPACK_ERR decompress_incremental(in string input, in string @base, in string output); + /// 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) + { + lzxd_stream lzx = 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); + if (infh == null) + { + ret = MSPACK_ERR.MSPACK_ERR_OPEN; + goto outlbl; + } + + byte[] hdrbuf = new byte[patchhead_SIZEOF]; + byte* hdrbufPtr = libmspack.system.GetArrayPointer(hdrbuf); + if (sys.read(infh, hdrbufPtr, patchhead_SIZEOF) != patchhead_SIZEOF) + { + ret = MSPACK_ERR.MSPACK_ERR_READ; + goto outlbl; + } + + if (EndGetI32(&hdrbuf[patchhead_VersionHi]) != 3 || + EndGetI32(&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]); + + // 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); + if (basefh == null) + { + ret = MSPACK_ERR.MSPACK_ERR_OPEN; + goto outlbl; + } + + mspack_file 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); + if (buf == null) + { + ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; + goto outlbl; + } + + mspack_oab_system 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 > 0) + { + if (sys.read(infh, buf, patchblk_SIZEOF) != patchblk_SIZEOF) + { + ret = MSPACK_ERR.MSPACK_ERR_READ; + 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]); + + if (blk_dsize > block_max || blk_dsize > target_size || + blk_ssize > block_max) + { + ret = MSPACK_ERR.MSPACK_ERR_DATAFORMAT; + goto outlbl; + } + + + window_size = (blk_ssize + 32767) & ~32767; + window_size += blk_dsize; + window_bits = 17; + + while (window_bits < 25 && (1U << window_bits) < window_size) + window_bits++; + + in_ofh.available = 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) + { + ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY; + goto outlbl; + } + ret = lzxd_set_reference_data(lzx, sys, basefh, blk_ssize); + if (ret != MSPACK_ERR.MSPACK_ERR_OK) + goto outlbl; + + ret = lzxd_decompress(lzx, blk_dsize); + if (ret != MSPACK_ERR.MSPACK_ERR_OK) + goto outlbl; + + lzxd_free(lzx); + lzx = null; + + /* 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 (out_ofh.crc != blk_crc) + { + ret = MSPACK_ERR.MSPACK_ERR_CHECKSUM; + goto outlbl; + } + + target_size -= blk_dsize; + } + + outlbl: + if (lzx) lzxd_free(lzx); + if (outfh) sys.close(outfh); + if (basefh) sys.close(basefh); + if (infh) sys.close(infh); + sys.free(buf); + + return ret; + } + + private static MSPACK_ERR copy_fh(mspack_system sys, mspack_file infh, mspack_file outfh, int bytes_to_copy, byte* buf, int buf_size) + { + while (bytes_to_copy > 0) + { + int run = buf_size; + if (run > bytes_to_copy) + { + run = bytes_to_copy; + } + if (sys.read(infh, buf, run) != run) + { + return MSPACK_ERR.MSPACK_ERR_READ; + } + if (outfh != null && sys.write(outfh, buf, run) != run) + { + return MSPACK_ERR.MSPACK_ERR_WRITE; + } + bytes_to_copy -= run; + } + + return MSPACK_ERR.MSPACK_ERR_OK; + } /// /// Sets an OAB decompression engine parameter. Available only in OAB @@ -72,9 +377,19 @@ namespace SabreTools.Compression.libmspack /// The parameter to set /// The value to set the parameter to /// - /// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there + /// MSPACK_ERR.MSPACK_ERR_OK if all is OK, or MSPACK_ERR.MSPACK_ERR_ARGS if there /// is a problem with either parameter or value. /// - public abstract MSPACK_ERR set_param(MSOABD_PARAM param, int value); + public MSPACK_ERR set_param(MSOABD_PARAM param, int value) + { + if (param == MSOABD_PARAM.MSOABD_PARAM_DECOMPBUF && value >= 16) + { + // Must be at least 16 bytes (patchblk_SIZEOF, oabblk_SIZEOF) + this.buf_size = value; + return MSPACK_ERR.MSPACK_ERR_OK; + } + + return MSPACK_ERR.MSPACK_ERR_ARGS; + } } } \ No newline at end of file diff --git a/libmspack/mspack_default_system.cs b/libmspack/mspack_default_system.cs index 0b70577..d33d37a 100644 --- a/libmspack/mspack_default_system.cs +++ b/libmspack/mspack_default_system.cs @@ -120,8 +120,7 @@ namespace SabreTools.Compression.libmspack /// public override unsafe void* alloc(int bytes) { - var arr = new byte[bytes]; - return (byte*)arr[0]; + return system.CreateArray(bytes); } /// diff --git a/libmspack/mspack_oab_system.cs b/libmspack/mspack_oab_system.cs new file mode 100644 index 0000000..fd0c187 --- /dev/null +++ b/libmspack/mspack_oab_system.cs @@ -0,0 +1,34 @@ +namespace SabreTools.Compression.libmspack +{ + public unsafe class mspack_oab_system : mspack_default_system + { + /// + public override unsafe int read(mspack_file base_file, void* buf, int size) + { + oabd_file file = (oabd_file)base_file; + int bytes_read; + + if (size > file.available) + size = file.available; + + bytes_read = file.orig_sys.read(file.orig_file, buf, size); + if (bytes_read < 0) + return bytes_read; + + file.available -= bytes_read; + return bytes_read; + } + + /// + public override unsafe int write(mspack_file base_file, void* buf, int size) + { + oabd_file file = (oabd_file)base_file; + int bytes_written = file.orig_sys.write(file.orig_file, buf, size); + + if (bytes_written > 0) + file.crc = mspack.crc32(file.crc, buf, bytes_written); + + return bytes_written; + } + } +} \ No newline at end of file diff --git a/libmspack/oabd_file.cs b/libmspack/oabd_file.cs new file mode 100644 index 0000000..4a838b9 --- /dev/null +++ b/libmspack/oabd_file.cs @@ -0,0 +1,13 @@ +namespace SabreTools.Compression.libmspack +{ + public class oabd_file : mspack_file + { + public mspack_system orig_sys { get; set; } + + public mspack_file orig_file { get; set; } + + public uint crc { get; set; } + + public int available { get; set; } + } +} \ No newline at end of file