From 3093994ac18be86d857a30b5735fe258d51114ea Mon Sep 17 00:00:00 2001 From: "Caleb J. Gardner" Date: Sun, 29 Mar 2026 03:36:43 -0500 Subject: [PATCH] Started work on actual decompression implementations --- build.zig.zon | 14 +++- src/archive.zig | 29 +++---- src/c_libs.zig | 7 ++ src/decomp.zig | 2 +- src/decomp/c/lz4.zig | 0 src/decomp/c/lzma.zig | 0 src/decomp/c/lzo.zig | 0 src/decomp/c/xz.zig | 0 src/decomp/c/zlib.zig | 98 ++++++++++++++++++++++++ src/decomp/c/zstd.zig | 164 ++++++++++++++++++++++++++++++++++++++++ src/decomp/types.zig | 8 ++ src/decomp/zig/lzma.zig | 0 src/decomp/zig/xz.zig | 0 src/decomp/zig/zlib.zig | 15 ++++ src/decomp/zig/zstd.zig | 15 ++++ 15 files changed, 333 insertions(+), 19 deletions(-) create mode 100644 src/c_libs.zig create mode 100644 src/decomp/c/lz4.zig create mode 100644 src/decomp/c/lzma.zig create mode 100644 src/decomp/c/lzo.zig create mode 100644 src/decomp/c/xz.zig create mode 100644 src/decomp/c/zlib.zig create mode 100644 src/decomp/c/zstd.zig create mode 100644 src/decomp/types.zig create mode 100644 src/decomp/zig/lzma.zig create mode 100644 src/decomp/zig/xz.zig create mode 100644 src/decomp/zig/zlib.zig create mode 100644 src/decomp/zig/zstd.zig diff --git a/build.zig.zon b/build.zig.zon index c4c13be..ccb6f95 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,8 +5,9 @@ .minimum_zig_version = "0.15.2", .dependencies = .{ .zlib_ng = .{ - .url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133", - .hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa", + // .url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133", + // .hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa", + .path = "../zig-zlib-ng", }, .zstd = .{ .url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3", @@ -17,8 +18,13 @@ .hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2", }, .minilzo = .{ - .url = "git+https://github.com/CalebQ42/zig-minilzo.git#7cbae997b91a44d74b7cd6c073584dc9562a6c90", - .hash = "minilzo-2.10.0-Ij7BO8wLAADeWI4Pe4jp8XTDsDaquZR14oZ7_9yKKDWP", + // .url = "git+https://github.com/CalebQ42/zig-minilzo.git#7cbae997b91a44d74b7cd6c073584dc9562a6c90", + // .hash = "minilzo-2.10.0-Ij7BO8wLAADeWI4Pe4jp8XTDsDaquZR14oZ7_9yKKDWP", + .path = "../zig-minilzo", + }, + .fastlzma2 = .{ + .url = "git+https://github.com/allyourcodebase/fast-lzma2#d7615e0c957a62fcd6691b3fe9519a091885bfa2", + .hash = "fastlzma2-0.0.0-gNWHgVeLAAD0Tlak3xhNcgpPSYcjyJppq0tlGmPKCC_V", }, }, .paths = .{ diff --git a/src/archive.zig b/src/archive.zig index 67a3ed0..8448df2 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -9,26 +9,27 @@ super: Superblock, pub fn init(fil: std.fs.File, offset: u64) !Archive { var super: Superblock = undefined; var fil_rdr = fil.reader(&[0]u8{}); - try fil_rdr.seekTo(offset); + if (offset > 0) + try fil_rdr.seekTo(offset); try fil_rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little); + try super.validate(); return .{ .super = super, }; } +pub const Error = error{ + BadMagic, + BadBlockLog, + BadVersion, + BadCheck, +}; + // Superblock const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little); -const SuperblockError = error{ - InvalidMagic, - InvalidBlockLog, - InvalidVersion, - InvalidCheck, -}; - -/// A squashfs Superblock pub const Superblock = packed struct { magic: u32, inode_count: u32, @@ -36,7 +37,7 @@ pub const Superblock = packed struct { block_size: u32, frag_count: u32, compression: enum(u16) { - gzip = 1, + gzip = 1, // Though officially named gzip, it actually uses zlib. lzma, lzo, xz, @@ -74,12 +75,12 @@ pub const Superblock = packed struct { /// Validate the Superblock. If an error is returned, it's likely the archive is corrupted or not a squashfs archive. fn validate(self: Superblock) !void { if (self.magic != SQUASHFS_MAGIC) - return SuperblockError.InvalidMagic; + return Error.BadMagic; if (self.flags.check) - return SuperblockError.InvalidCheck; + return Error.BadCheck; if (self.ver_maj != 4 or self.ver_min != 0) - return SuperblockError.InvalidVersion; + return Error.BadVersion; if (std.math.log2(self.block_size) != self.block_log) - return SuperblockError.InvalidBlockLog; + return Error.BadBlockLog; } }; diff --git a/src/c_libs.zig b/src/c_libs.zig new file mode 100644 index 0000000..a20cbc0 --- /dev/null +++ b/src/c_libs.zig @@ -0,0 +1,7 @@ +pub const c = @cImport({ + @cInclude("zlib-ng.h"); + @cInclude("lzo/minilzo.h"); + @cInclude("lz4.h"); + @cInclude("zstd.h"); + @cInclude("lzma.h"); +}); diff --git a/src/decomp.zig b/src/decomp.zig index 3c3faf8..4bc30c3 100644 --- a/src/decomp.zig +++ b/src/decomp.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const Error = error{ +pub const Error = error{ OutOfMemory, EndOfStream, ReadFailed, diff --git a/src/decomp/c/lz4.zig b/src/decomp/c/lz4.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/decomp/c/lzma.zig b/src/decomp/c/lzma.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/decomp/c/lzo.zig b/src/decomp/c/lzo.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/decomp/c/xz.zig b/src/decomp/c/xz.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/decomp/c/zlib.zig b/src/decomp/c/zlib.zig new file mode 100644 index 0000000..b7821c1 --- /dev/null +++ b/src/decomp/c/zlib.zig @@ -0,0 +1,98 @@ +const std = @import("std"); + +const c = @import("../../c_libs.zig").c; +const Decompressor = @import("../../decomp.zig"); + +const Zlib = @This(); + +streams: std.AutoHashMap(std.Thread.Id, c.zng_stream), + +interface: Decompressor, + +err: ?Error = null, + +pub fn init(alloc: std.mem.Allocator) !Zlib { + return .{ + .streams = try .init(alloc), + .interface = .{ + .alloc = alloc, + .vtable = .{ .decompress = decompress, .stateless = stateless }, + }, + }; +} + +fn getOrCreate(self: *Zlib) !*c.zng_stream { + const res = try self.streams.getOrPut(std.Thread.getCurrentId()); + if (res.found_existing) return res.value_ptr; + res.value_ptr.* = .{ + .@"opaque" = self, + .zalloc = zalloc, + .zfree = zfree, + }; +} + +fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { + var self: *Zlib = @fieldParentPtr("interface", decomp); + + var stream = try self.getOrCreate(); + stream.next_in = in.ptr; + stream.avail_in = in.len; + stream.next_out = out.ptr; + stream.avail_out = out.len; + var res = c.zng_inflateReset(stream); + decodeError(res) catch |err| { + self.err = err; + return Decompressor.Error.ReadFailed; + }; + res = c.zng_inflate(stream, c.Z_FINISH); + decodeError(res) catch |err| { + self.err = err; + return switch (err) { + Error.OutOfMemory => err, + else => Decompressor.Error.ReadFailed, + }; + }; +} + +fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { + _ = alloc; + var out_len = out.len; + const res = c.zng_uncompress(out.ptr, &out_len, in.ptr, in.len); + return switch (res) { + c.Z_OK => out_len, + c.Z_MEM_ERROR => Decompressor.Error.OutOfMemory, + c.Z_BUF_ERROR => Decompressor.Error.WriteFailed, + else => Decompressor.Error.ReadFailed, + }; +} + +inline fn decodeError(res: i32) Error!void { + return switch (res) { + c.Z_OK => {}, + c.Z_STREAM_ERROR => Error.Stream, + c.Z_BUF_ERROR => Error.Buffer, + c.Z_MEM_ERROR => Error.OutOfMemory, + c.Z_DATA_ERROR => Error.Data, + c.Z_VERSION_ERROR => Error.Version, + else => Error.Unknown, + }; +} + +fn zalloc(ptr: ?*anyopaque, items: c_uint, size: c_uint) callconv(.c) ?*anyopaque { + var self: *Zlib = @ptrCast(ptr); + return self.interface.alloc.rawAlloc(items * size, .@"1", 0); +} + +fn zfree(ptr: ?*anyopaque, addr: ?*anyopaque) callconv(.c) void { + var self: *Zlib = @ptrCast(ptr); + self.interface.alloc.rawFree(@ptrCast(addr), .@"1", 0); +} + +pub const Error = error{ + OutOfMemory, + Stream, + Buffer, + Data, + Version, + Unknown, +}; diff --git a/src/decomp/c/zstd.zig b/src/decomp/c/zstd.zig new file mode 100644 index 0000000..cc5e432 --- /dev/null +++ b/src/decomp/c/zstd.zig @@ -0,0 +1,164 @@ +const std = @import("std"); + +const c = @import("../../c_libs.zig").c; +const Decompressor = @import("../../decomp.zig"); + +const Zstd = @This(); + +context: std.AutoHashMap(std.Thread.Id, ?*c.ZSTD_DCtx), + +interface: Decompressor, + +err: ?Error = null, + +pub fn init(alloc: std.mem.Allocator) !Zstd { + return .{ + .streams = try .init(alloc), + .interface = .{ + .alloc = alloc, + .vtable = .{ .decompress = decompress, .stateless = stateless }, + }, + }; +} + +fn getOrCreate(self: *Zstd) !*c.ZSTD_DCtx { + const res = try self.context.getOrPut(std.Thread.getCurrentId()); + if (res.found_existing) return res.value_ptr; + res.value_ptr.* = c.ZSTD_createDCtx(); + if (res.value_ptr.* == null) return Error.OutOfMemory; +} + +fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { + var self: *Zstd = @fieldParentPtr("interface", decomp); + + const ctx = self.getOrCreate(); + const res = c.ZSTD_decompressDCtx(ctx, out.ptr, out.len, in.ptr, in.len); + decodeError(res) catch |err| { + self.err = err; + return ZstdErrorToDecompError(err); + }; + return res; +} +fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { + _ = alloc; + const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len); + decodeError(res) catch |err| return ZstdErrorToDecompError(err); + return res; +} + +inline fn decodeError(res: usize) Error!void { + if (c.ZSTD_isError(res) == 0) return; + return switch (c.ZSTD_getErrorCode(res)) { + c.ZSTD_error_GENERIC => Error.Generic, + c.ZSTD_error_prefix_unknown => Error.PrefixUnknown, + c.ZSTD_error_version_unsupported => Error.VersionUnsupported, + c.ZSTD_error_frameParameter_unsupported => Error.FrameParameterUnsupported, + c.ZSTD_error_frameParameter_windowTooLarge => Error.FrameParameterWindowTooLarge, + c.ZSTD_error_corruption_detected => Error.CorruptionDetected, + c.ZSTD_error_checksum_wrong => Error.ChecksumWrong, + c.ZSTD_error_literals_headerWrong => Error.LiteralsHeaderWrong, + c.ZSTD_error_dictionary_corrupted => Error.DictionaryCorrupted, + c.ZSTD_error_dictionary_wrong => Error.DictionaryWrong, + c.ZSTD_error_dictionaryCreation_failed => Error.DictionaryCreationFailed, + c.ZSTD_error_parameter_unsupported => Error.ParameterUnsupported, + c.ZSTD_error_parameter_combination_unsupported => Error.ParameterCombinationUnsupported, + c.ZSTD_error_parameter_outOfBound => Error.ParameterOutOfBound, + c.ZSTD_error_tableLog_tooLarge => Error.TableLogTooLarge, + c.ZSTD_error_maxSymbolValue_tooLarge => Error.MaxSymbolValueTooLarge, + c.ZSTD_error_maxSymbolValue_tooSmall => Error.MaxSymbolValueTooSmall, + c.ZSTD_error_cannotProduce_uncompressedBlock => Error.CannotProduceUncompressedBlock, + c.ZSTD_error_stabilityCondition_notRespected => Error.StabilityConditionNotRespected, + c.ZSTD_error_stage_wrong => Error.StageWrong, + c.ZSTD_error_init_missing => Error.InitMissing, + c.ZSTD_error_memory_allocation => Error.MemoryAllocation, + c.ZSTD_error_workSpace_tooSmall => Error.WorkSpaceTooSmall, + c.ZSTD_error_dstSize_tooSmall => Error.DstSizeTooSmall, + c.ZSTD_error_srcSize_wrong => Error.SrcSizeWrong, + c.ZSTD_error_dstBuffer_null => Error.DstBufferNull, + c.ZSTD_error_noForwardProgress_destFull => Error.NoForwardProgressDestFull, + c.ZSTD_error_noForwardProgress_inputEmpty => Error.NoForwardProgressInputEmpty, + c.ZSTD_error_frameIndex_tooLarge => Error.FrameIndexTooLarge, + c.ZSTD_error_seekableIO => Error.SeekableIo, + c.ZSTD_error_dstBuffer_wrong => Error.DstBufferWrong, + c.ZSTD_error_srcBuffer_wrong => Error.SrcBufferWrong, + c.ZSTD_error_sequenceProducer_failed => Error.SequenceProducerFailed, + c.ZSTD_error_externalSequences_invalid => Error.ExternalSequencesInvalid, + }; +} +inline fn ZstdErrorToDecompError(err: Error) Decompressor.Error { + return switch (err) { + Error.OutOfMemory => err, + Error.Generic => Decompressor.Error.ReadFailed, + Error.PrefixUnknown => Decompressor.Error.ReadFailed, + Error.VersionUnsupported => Decompressor.Error.ReadFailed, + Error.FrameParameterUnsupported => Decompressor.Error.ReadFailed, + Error.FrameParameterWindowTooLarge => Decompressor.Error.ReadFailed, + Error.CorruptionDetected => Decompressor.Error.ReadFailed, + Error.ChecksumWrong => Decompressor.Error.ReadFailed, + Error.LiteralsHeaderWrong => Decompressor.Error.ReadFailed, + Error.DictionaryCorrupted => Decompressor.Error.ReadFailed, + Error.DictionaryWrong => Decompressor.Error.ReadFailed, + Error.DictionaryCreationFailed => Decompressor.Error.ReadFailed, + Error.ParameterUnsupported => Decompressor.Error.ReadFailed, + Error.ParameterCombinationUnsupported => Decompressor.Error.ReadFailed, + Error.ParameterOutOfBound => Decompressor.Error.ReadFailed, + Error.TableLogTooLarge => Decompressor.Error.ReadFailed, + Error.MaxSymbolValueTooLarge => Decompressor.Error.ReadFailed, + Error.MaxSymbolValueTooSmall => Decompressor.Error.ReadFailed, + Error.CannotProduceUncompressedBlock => Decompressor.Error.ReadFailed, + Error.StabilityConditionNotRespected => Decompressor.Error.ReadFailed, + Error.StageWrong => Decompressor.Error.ReadFailed, + Error.InitMissing => Decompressor.Error.ReadFailed, + Error.MemoryAllocation => Decompressor.Error.OutOfMemory, + Error.WorkSpaceTooSmall => Decompressor.Error.WriteFailed, + Error.DstSizeTooSmall => Decompressor.Error.WriteFailed, + Error.SrcSizeWrong => Decompressor.Error.ReadFailed, + Error.DstBufferNull => Decompressor.Error.WriteFailed, + Error.NoForwardProgressDestFull => Decompressor.Error.WriteFailed, + Error.NoForwardProgressInputEmpty => Decompressor.Error.ReadFailed, + Error.FrameIndexTooLarge => Decompressor.Error.ReadFailed, + Error.SeekableIo => Decompressor.Error.ReadFailed, + Error.DstBufferWrong => Decompressor.Error.WriteFailed, + Error.SrcBufferWrong => Decompressor.Error.ReadFailed, + Error.SequenceProducerFailed => Decompressor.Error.ReadFailed, + Error.ExternalSequencesInvalid => Decompressor.Error.ReadFailed, + }; +} + +pub const Error = error{ + OutOfMemory, + Generic, + PrefixUnknown, + VersionUnsupported, + FrameParameterUnsupported, + FrameParameterWindowTooLarge, + CorruptionDetected, + ChecksumWrong, + LiteralsHeaderWrong, + DictionaryCorrupted, + DictionaryWrong, + DictionaryCreationFailed, + ParameterUnsupported, + ParameterCombinationUnsupported, + ParameterOutOfBound, + TableLogTooLarge, + MaxSymbolValueTooLarge, + MaxSymbolValueTooSmall, + CannotProduceUncompressedBlock, + StabilityConditionNotRespected, + StageWrong, + InitMissing, + MemoryAllocation, + WorkSpaceTooSmall, + DstSizeTooSmall, + SrcSizeWrong, + DstBufferNull, + NoForwardProgressDestFull, + NoForwardProgressInputEmpty, + FrameIndexTooLarge, + SeekableIo, + DstBufferWrong, + SrcBufferWrong, + SequenceProducerFailed, + ExternalSequencesInvalid, +}; diff --git a/src/decomp/types.zig b/src/decomp/types.zig new file mode 100644 index 0000000..f9ba627 --- /dev/null +++ b/src/decomp/types.zig @@ -0,0 +1,8 @@ +const config = @import("config"); + +const Zlib = if (config.use_zig_decomp) @import("zig/zlib.zig") else @import("c/zlib.zig"); +const Lzma = if (config.use_zig_decomp) @import("zig/lzma.zig") else @import("c/lzma.zig"); +const Lzo = if (config.use_zig_decomp) void else @import("c/lzo.zig"); +const Xz = if (config.use_zig_decomp) @import("zig/xz.zig") else @import("c/xz.zig"); +const Lz4 = if (config.use_zig_decomp) void else @import("c/lz4.zig"); +const Zstd = if (config.use_zig_decomp) @import("zig/zstd.zig") else @import("c/zstd.zig"); diff --git a/src/decomp/zig/lzma.zig b/src/decomp/zig/lzma.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/decomp/zig/xz.zig b/src/decomp/zig/xz.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/decomp/zig/zlib.zig b/src/decomp/zig/zlib.zig new file mode 100644 index 0000000..ddec263 --- /dev/null +++ b/src/decomp/zig/zlib.zig @@ -0,0 +1,15 @@ +const std = @import("std"); +const flate = std.compress.flate; +const Reader = std.Io.Reader; + +const Decompressor = @import("../../decomp.zig"); + +interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, + +fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { + const buf = try alloc.alloc(u8, out.len); + defer alloc.free(buf); + var rdr: Reader = .static(in); + var decomp = flate.Decompress.init(&rdr, .zlib, buf); + return decomp.reader.readSliceShort(out); +} diff --git a/src/decomp/zig/zstd.zig b/src/decomp/zig/zstd.zig new file mode 100644 index 0000000..6714a7b --- /dev/null +++ b/src/decomp/zig/zstd.zig @@ -0,0 +1,15 @@ +const std = @import("std"); +const zstd = std.compress.zstd; +const Reader = std.Io.Reader; + +const Decompressor = @import("../../decomp.zig"); + +interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, + +fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { + const buf = try alloc.alloc(u8, out.len * 2); + defer alloc.free(buf); + var rdr: Reader = .static(in); + var decomp = zstd.Decompress.init(&rdr, buf, .{ .window_len = out.len * 2 }); + return decomp.reader.readSliceShort(out); +}