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.
Read SMX file of SourceMod (SourcePawn) in Zig language. Decompress using libdeflate.
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,
};
}
};
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 });
}
}
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