Skip to content

Instantly share code, notes, and snippets.

@shimamura-sakura
Created January 11, 2023 14:06
Show Gist options
  • Select an option

  • Save shimamura-sakura/504af5259bb91f499210da024d14703d to your computer and use it in GitHub Desktop.

Select an option

Save shimamura-sakura/504af5259bb91f499210da024d14703d to your computer and use it in GitHub Desktop.

Revisions

  1. shimamura-sakura created this gist Jan 11, 2023.
    42 changes: 42 additions & 0 deletions libdeflate.zig
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,42 @@
    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,
    };
    }
    };
    21 changes: 21 additions & 0 deletions main.zig
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,21 @@
    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 });
    }
    }
    198 changes: 198 additions & 0 deletions smx.zig
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,198 @@
    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]),
    };
    }
    };