Created
January 11, 2023 14:06
-
-
Save shimamura-sakura/504af5259bb91f499210da024d14703d to your computer and use it in GitHub Desktop.
Read SMX file of SourceMod (SourcePawn) in Zig language. Decompress using libdeflate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const c = @cImport(@cInclude("libdeflate.h")); | |
| pub const Error = error{ | |
| LibDeflateAlloc, | |
| LibDeflateBadData, | |
| LibDeflateShortOutput, | |
| LibDeflateInsufficientSpace, | |
| LibDeflateOther, | |
| }; | |
| pub const Decompressor = struct { | |
| const Self = @This(); | |
| ptr: *c.libdeflate_decompressor, | |
| pub fn alloc() Error!Self { | |
| return .{ .ptr = c.libdeflate_alloc_decompressor() orelse | |
| return Error.LibDeflateAlloc }; | |
| } | |
| pub fn free(self: Self) void { | |
| c.libdeflate_free_decompressor(self.ptr); | |
| } | |
| pub fn zlib(self: Self, in_buf: []const u8, out_buf: []u8) Error!usize { | |
| var actual_out: usize = 0; | |
| return switch (c.libdeflate_zlib_decompress( | |
| self.ptr, | |
| in_buf.ptr, | |
| in_buf.len, | |
| out_buf.ptr, | |
| out_buf.len, | |
| &actual_out, | |
| )) { | |
| c.LIBDEFLATE_SUCCESS => actual_out, | |
| c.LIBDEFLATE_BAD_DATA => Error.LibDeflateBadData, | |
| c.LIBDEFLATE_SHORT_OUTPUT => Error.LibDeflateShortOutput, | |
| c.LIBDEFLATE_INSUFFICIENT_SPACE => Error.LibDeflateInsufficientSpace, | |
| else => Error.LibDeflateOther, | |
| }; | |
| } | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const std = @import("std"); | |
| const smx = @import("smx.zig"); | |
| const mem = std.mem; | |
| pub fn main() !void { | |
| const alloc = std.heap.c_allocator; | |
| const data = try std.fs.cwd().readFileAlloc(alloc, "gokz-core.smx", std.math.maxInt(usize)); | |
| defer alloc.free(data); | |
| var image = try smx.SMX.init(alloc, data); | |
| defer image.deinit(alloc); | |
| const nametable = image.sections.get(".names").?.str(); | |
| const rtti_meth = image.sections.get("rtti.methods").?.rttiMethods(); | |
| for (rtti_meth.rows) |rptr| { | |
| const row = rptr.into(); | |
| std.debug.print("{s} {}\n", .{ nametable.get(row.name), row }); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const std = @import("std"); | |
| const mem = std.mem; | |
| const iLE = mem.readIntLittle; | |
| const Allocator = mem.Allocator; | |
| const StringMap = std.StringHashMapUnmanaged; | |
| const Error = error{ TooShort, NotSPFF }; | |
| pub fn zlibDecompress(in_buf: []const u8, out_buf: []u8) !void { | |
| const lib = @import("libdeflate.zig"); | |
| var d = try lib.Decompressor.alloc(); | |
| defer d.free(); | |
| _ = try d.zlib(in_buf, out_buf); | |
| } | |
| pub fn zstrTable(tbl: []const u8, offset: usize) []const u8 { | |
| const end = mem.indexOfScalarPos(u8, tbl, offset, 0) orelse tbl.len; | |
| return tbl[offset..end]; | |
| } | |
| pub const SMX = struct { | |
| const Self = @This(); | |
| const SMap = StringMap(Section); | |
| is_alloc: bool, | |
| img_data: []const u8, | |
| sections: SMap, | |
| pub fn deinit(self: *Self, allocator: Allocator) void { | |
| if (self.is_alloc) allocator.free(self.img_data); | |
| self.sections.deinit(allocator); | |
| } | |
| pub fn init(allocator: Allocator, bytes: []const u8) !Self { | |
| const is_alloc = bytes[6] != 0; | |
| const img_data = try decompress(allocator, bytes); | |
| errdefer if (is_alloc) allocator.free(img_data); | |
| return .{ | |
| .is_alloc = is_alloc, | |
| .img_data = img_data, | |
| .sections = try sects(allocator, img_data), | |
| }; | |
| } | |
| pub fn decompress(allocator: Allocator, bytes: []const u8) ![]const u8 { | |
| if (bytes.len < 24) return Error.TooShort; | |
| if (mem.eql(u8, bytes[0..4], "SPFF")) return Error.NotSPFF; | |
| const compression = bytes[6]; | |
| const disksize = iLE(u32, bytes[7..11]); | |
| const imagsize = iLE(u32, bytes[11..15]); | |
| const dataoffs = iLE(u32, bytes[20..24]); | |
| switch (compression) { | |
| 0 => { // no compression | |
| if (bytes.len < imagsize) return Error.TooShort; | |
| return bytes[0..imagsize]; | |
| }, | |
| 1 => { // zlib compression | |
| if (bytes.len < disksize) return Error.TooShort; | |
| const image = try allocator.alloc(u8, imagsize); | |
| errdefer allocator.free(image); | |
| mem.copy(u8, image[0..dataoffs], bytes[0..dataoffs]); | |
| image[6] = 0; | |
| try zlibDecompress(bytes[dataoffs..disksize], image[dataoffs..imagsize]); | |
| return image; | |
| }, | |
| else => unreachable, | |
| } | |
| } | |
| fn sects(allocator: Allocator, image: []const u8) !SMap { | |
| const sections = image[15]; | |
| const stringtab = image[iLE(u32, image[16..20])..]; | |
| var map = SMap{}; | |
| try map.ensureTotalCapacity(allocator, sections); | |
| for (@ptrCast([*]const [12]u8, image.ptr + 24)[0..sections]) |win| { | |
| const nameoffs = iLE(u32, win[0..4]); | |
| const dataoffs = iLE(u32, win[4..8]); | |
| const datasize = iLE(u32, win[8..12]); | |
| map.putAssumeCapacity(zstrTable(stringtab, nameoffs), .{ | |
| .slice = image[dataoffs .. dataoffs + datasize], | |
| }); | |
| } | |
| return map; | |
| } | |
| }; | |
| pub const Section = struct { | |
| const Self = @This(); | |
| slice: []const u8, | |
| pub fn str(self: Self) StrSection { | |
| return .{ .slice = self.slice }; | |
| } | |
| pub fn data(self: Self) DataSection { | |
| const datasize = iLE(u32, self.slice[0..4]); | |
| const dataoffs = iLE(u32, self.slice[8..12]); | |
| return .{ | |
| .memsize = iLE(u32, self.slice[4..8]), | |
| .data = self.slice[dataoffs .. dataoffs + datasize], | |
| }; | |
| } | |
| pub fn code(self: Self) CodeSection { | |
| const codesize = iLE(u32, self.slice[0..4]); | |
| const codeoffs = iLE(u32, self.slice[12..16]); | |
| const codevers = self.slice[5]; | |
| return .{ | |
| .code = self.slice[codeoffs .. codeoffs + codesize], | |
| .cellsize = self.slice[4], | |
| .codeversion = codevers, | |
| .flags = iLE(u16, self.slice[6..8]), | |
| .main = iLE(u32, self.slice[8..12]), | |
| .features = if (codevers >= 13) iLE(u32, self.slice[16..20]) else 0, | |
| }; | |
| } | |
| pub const rttiMethods = RTTITable(RTTIMethods).init; | |
| }; | |
| pub const StrSection = struct { | |
| const Self = @This(); | |
| slice: []const u8, | |
| pub fn get(self: Self, offset: usize) []const u8 { | |
| return zstrTable(self.slice, offset); | |
| } | |
| }; | |
| pub const DataSection = struct { | |
| data: []const u8, | |
| memsize: u32, | |
| }; | |
| pub const CodeSection = struct { | |
| code: []const u8, | |
| cellsize: u8, | |
| codeversion: u8, | |
| flags: u16, | |
| main: u32, | |
| features: u32, | |
| }; | |
| fn RTTITable(comptime RowType: type) type { | |
| const RowData = [RowType.ROW_SIZE]u8; | |
| const rowInit = RowType.init; | |
| return struct { | |
| const Self = @This(); | |
| const RPtr = struct { | |
| slice: RowData, | |
| pub fn into(self: @This()) RowType { | |
| return rowInit(self.slice); | |
| } | |
| }; | |
| rows: []const RPtr, | |
| fn init(sect: Section) Self { | |
| const header_size = iLE(u32, sect.slice[0..4]); | |
| const row_count = iLE(u32, sect.slice[8..12]); | |
| const body = sect.slice[header_size..]; | |
| return .{ .rows = @ptrCast([*]const RPtr, body)[0..row_count] }; | |
| } | |
| pub usingnamespace if (@hasField(RowType, "name")) struct { | |
| const Map = StringMap(RowType); | |
| pub fn map(self: Self, allocator: Allocator, names: StrSection) !Map { | |
| var m = Map{}; | |
| try m.ensureTotalCapacity(allocator, @intCast(u32, self.data.len)); | |
| for (self.rows) |rptr| { | |
| const row = rptr.into(); | |
| m.putAssumeCapacity(names.get(row.name), row); | |
| } | |
| return m; | |
| } | |
| } else struct {}; | |
| }; | |
| } | |
| pub const RTTIMethods = struct { | |
| const Self = @This(); | |
| const ROW_SIZE = 16; | |
| name: u32, | |
| pcode_start: u32, | |
| pcode_end: u32, | |
| signature: u32, | |
| pub fn init(bytes: [ROW_SIZE]u8) Self { | |
| return .{ | |
| .name = mem.readIntLittle(u32, bytes[0..4]), | |
| .pcode_start = mem.readIntLittle(u32, bytes[4..8]), | |
| .pcode_end = mem.readIntLittle(u32, bytes[8..12]), | |
| .signature = mem.readIntLittle(u32, bytes[12..16]), | |
| }; | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment