From 9afbcd5bf7ad7c3b4199cebdb7b533ba834ad48b Mon Sep 17 00:00:00 2001 From: a92126 <182859762@qq.com> Date: Wed, 13 Nov 2024 16:41:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5zig=E5=86=99=E7=9A=84?= =?UTF-8?q?=E6=89=93=E5=8C=85=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/cheat-builder/make-acl.zig | 757 +++++++++++++++++++++++++++++++ 1 file changed, 757 insertions(+) create mode 100644 tools/cheat-builder/make-acl.zig diff --git a/tools/cheat-builder/make-acl.zig b/tools/cheat-builder/make-acl.zig new file mode 100644 index 0000000..ba41553 --- /dev/null +++ b/tools/cheat-builder/make-acl.zig @@ -0,0 +1,757 @@ +//! By convention, main.zig is where your main function lives in the case that +//! you are building an executable. If you are making a library, the convention +//! is to delete this file and start with root.zig instead. +const std = @import("std"); +const Allocator = std.mem.Allocator; +const vec = std.ArrayList; +const map = std.AutoArrayHashMap; +const hash = std.StringArrayHashMap; +const blk = std.heap.ArenaAllocator; + +const ParseError = error{ + Invalid, + Outofrange, +}; +const File = std.fs.File; +const Utf8View = std.unicode.Utf8View; + +const ParseState = enum { + WaitLock, + ReadLock, + WaitHole, + ReadHole, + ReadKey, + WaitPart, + NeedPart, +}; +const Passhole = [2][]const u8{ "text", "off" }; +const Hex = "0123456789abcdefABCDEF"; + +const Module = vec(u8); +const LockKey = vec(Module); +const LockHole = struct { name: vec(u8), keys: vec(LockKey) }; +const Lock = struct { name: vec(u8), holes: vec(LockHole) }; +const EntData = union(enum) { strs: *vec(*Module), keys: *vec(LockKey) }; +const Entry = struct { id: u32, data: EntData }; +fn cmpEntry(ctx: void, a: Entry, b: Entry) bool { + _ = ctx; + return a.id < b.id; +} + +const CheatMap = struct { order: []u8, serial: []u8 }; // data from json +const CheatSet = struct { code: u32, sets: Module }; // cht单文件产出 +const CheatGrp = struct { serial: []u8, grp: vec(CheatSet) }; // cht组文件产出 + +const ParserCtx = struct { + state: ParseState, + token: vec(u8), + line: u32, + + file: []u8, + code: u32, + farm: Allocator, + shop: Allocator, + + locks: vec(Lock), + curlk: ?Lock, + curhl: ?LockHole, + + strcache: ?hash(u16), + + output: *vec(CheatSet), +}; +var runs: i64 = 0; + +pub fn main() !void { + const now = std.time.microTimestamp(); + + // 追踪内存分配,确定没问题后就不用gpa而是arena了 + // var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + // defer std.debug.assert(gpa.deinit() == std.heap.Check.ok); + var gpa = blk.init(std.heap.page_allocator); + defer gpa.deinit(); + + const list = try loadList(gpa.allocator()); + defer list.deinit(); + + const roms = transform(gpa.allocator(), list) catch vec(CheatGrp).init(gpa.allocator()); + defer roms.deinit(); + std.debug.print("all rom has {d} and time {d}\n", .{ roms.items.len, std.time.microTimestamp() - now }); + + const content = calcfile: { + var alloc = blk.init(gpa.allocator()); + defer alloc.deinit(); + + var res = vec(*CheatGrp).init(alloc.allocator()); + var idx = hash(*vec(CheatSet)).init(alloc.allocator()); + for (roms.items) |*rom| { + if (rom.grp.items.len == 0) continue; + + if (!idx.contains(rom.serial)) { + var grp = try alloc.allocator().create(CheatGrp); + grp.serial = rom.serial; + grp.grp = vec(CheatSet).init(alloc.allocator()); + try res.append(grp); + var cheats = &grp.grp; + try cheats.appendSlice(rom.grp.items); + try idx.put(rom.serial, cheats); + } else { + const pcheats = idx.get(rom.serial).?; + try pcheats.appendSlice(rom.grp.items); + } + } + + const filedata = try format(alloc.allocator(), gpa.allocator(), &res); + break :calcfile filedata; + }; + defer gpa.allocator().free(content); + + try std.fs.cwd().writeFile(.{ .sub_path = "gba.acl", .data = content, .flags = .{ .truncate = true } }); + // std.debug.print("check runs {d}\n", .{std.time.microTimestamp() - now}); + + // 如果用gpa才需要手动释放这几个没有释放的内存 + // for (roms.items) |*grp| { + // gpa.allocator().free(grp.serial); + // for (grp.grp.items) |*set| { + // set.sets.deinit(); + // } + // grp.grp.deinit(); + // } +} + +fn readAll(allocator: Allocator, file: File) ![]u8 { + const file_size: usize = @intCast(try file.getEndPos()); + const file_contents = try allocator.alloc(u8, file_size); + + const readed = try file.readAll(file_contents); + if (readed != file_size) { + return error.EndOfStream; + } + return file_contents; +} + +fn loadList(allocator: Allocator) !vec(CheatMap) { + const file = try std.fs.cwd().openFile("./serial.json", .{}); + defer file.close(); + + const file_contents = try readAll(allocator, file); + defer allocator.free(file_contents); + + var entries = try std.json.parseFromSlice(std.json.Value, allocator, file_contents, .{}); + defer entries.deinit(); + + var iter = entries.value.object.iterator(); + var list = try vec(CheatMap).initCapacity(allocator, entries.value.object.count()); + while (iter.next()) |*entry| { + const key = entry.key_ptr; + const value = entry.value_ptr; + if (!std.mem.eql(u8, value.string, "????")) { + const nkey = try allocator.alloc(u8, key.len); + const nval = try allocator.alloc(u8, value.string.len); + std.mem.copyForwards(u8, nkey, key.*); + std.mem.copyForwards(u8, nval, value.string); + try list.append(CheatMap{ .order = nkey, .serial = nval }); + } + } + return list; +} + +fn transform(allocator: Allocator, list: vec(CheatMap)) !vec(CheatGrp) { + var alloc = blk.init(allocator); + defer alloc.deinit(); + + const innalloc = alloc.allocator(); + + var retval = vec(CheatGrp).init(allocator); + for (list.items) |*chtmap| { + const order = chtmap.order; + const serial = chtmap.serial; + defer allocator.free(order); + + const file = try std.fmt.allocPrint(innalloc, "./gba/{s}.u8", .{order}); + + var cheats = vec(CheatSet).init(allocator); + + const fd = std.fs.cwd().openFile(file, .{}) catch null; + if (fd) |chtfile| { + defer chtfile.close(); + + const chtdata = try readAll(innalloc, chtfile); + + if (chtdata.len > 0) { + parse(innalloc, allocator, &cheats, chtdata, serial, order) catch |err| { + std.debug.print("got error {any} in {s}!\n", .{ err, file }); + }; + try retval.append(CheatGrp{ .serial = serial, .grp = cheats }); + } else try retval.append(CheatGrp{ .serial = serial, .grp = cheats }); + } else try retval.append(CheatGrp{ .serial = serial, .grp = cheats }); + } + return retval; +} + +fn parse(innalloc: Allocator, outeralloc: Allocator, output: *vec(CheatSet), chtdata: []u8, serial: []u8, order: []u8) !void { + var context = ParserCtx{ + .state = ParseState.WaitLock, + .token = vec(u8).init(innalloc), + .line = 1, + + .file = order, + .farm = innalloc, + .shop = outeralloc, + .code = calc_code: { + var r: u32 = 0; + for (serial) |v| r = v | r << 8; + break :calc_code r; + }, + + .locks = vec(Lock).init(innalloc), + .curlk = null, + .curhl = null, + + .strcache = null, + + .output = output, + }; + defer context.locks.deinit(); + defer context.token.deinit(); + + var data = (try Utf8View.init(chtdata)).iterator(); + while (data.nextCodepointSlice()) |codepoint| { + try incr(codepoint, &context); + } + done(&context); +} + +fn done(ctx: *ParserCtx) void { + switch (ctx.state) { + ParseState.WaitPart, ParseState.NeedPart => {}, + else => unreachable, + } +} + +inline fn parserr(str: []u8, ctx: *ParserCtx) !void { + std.debug.print("Cheat Invad: {s} at line {d} for file {s}.u8\n", .{ str, ctx.line, ctx.file }); + return ParseError.Invalid; +} + +inline fn tokenStr(ctx: *ParserCtx) vec(u8) { + const s = ctx.token; + ctx.token = vec(u8).init(ctx.farm); + return s; +} + +fn incr(ch: []const u8, ctx: *ParserCtx) !void { + const state = ctx.state; + const allocator = ctx.farm; + switch (ch[0]) { + '[' => { + if (state == ParseState.WaitLock or state == ParseState.ReadHole or state == ParseState.NeedPart) { + ctx.state = ParseState.ReadLock; + if (ctx.curlk) |lock| { + try ctx.locks.append(lock); + } + ctx.curlk = Lock{ .name = vec(u8).init(allocator), .holes = vec(LockHole).init(allocator) }; + } else if (state == ParseState.WaitPart) {} else try parserr(try std.fmt.allocPrint(allocator, "error occur [ on {s}", .{@tagName(state)}), ctx); + }, + ']' => { + if (state == ParseState.ReadLock) { + ctx.state = ParseState.WaitHole; + const name = tokenStr(ctx); + if (std.ascii.eqlIgnoreCase(name.items, "gameinfo")) { + try lade(ctx); + ctx.state = ParseState.WaitPart; + ctx.locks = vec(Lock).init(allocator); + ctx.curhl = null; + ctx.curlk = null; + } else if (name.items.len > 0) { + ctx.curlk = Lock{ + .name = name, + .holes = vec(LockHole).init(allocator), + }; + ctx.curhl = null; + } else try parserr(try std.fmt.allocPrint(allocator, "empty lock name", .{}), ctx); + } else if (state == ParseState.WaitPart) {} else if (state == ParseState.NeedPart) { + ctx.state = ParseState.WaitPart; + } else try parserr(try std.fmt.allocPrint(allocator, "error occur ] on {s}", .{@tagName(state)}), ctx); + }, + '\r', '\n' => { + if (state == ParseState.WaitHole) { + ctx.state = ParseState.ReadHole; + } else if (state == ParseState.ReadKey) { + const token = tokenStr(ctx); + var pass = false; + if (ctx.curhl) |*hole| { + if (token.items.len > 0) { + var key = &hole.keys.items[hole.keys.items.len - 1]; + try key.append(token); + } + + pass = for (Passhole) |keyword| { + if (std.ascii.eqlIgnoreCase(keyword, hole.name.items)) { + break true; + } + } else false; + } + if (!pass) { + if (ctx.curlk != null) { + if (ctx.curhl) |hole| { + try ctx.curlk.?.holes.append(hole); + } else unreachable; + } + ctx.curhl = null; + } + ctx.state = ParseState.ReadHole; + } else if (state == ParseState.ReadLock) { + unreachable; + } else if (state == ParseState.ReadHole) { + const token = tokenStr(ctx); + if (token.items.len > 0) { + const onlyspace = for (token.items) |c| { + if (c != ' ' and c != '\t') { + break false; + } + } else true; + if (onlyspace == false) { + try parserr(try std.fmt.allocPrint(allocator, "error occur newline on {s}", .{@tagName(state)}), ctx); + } + } + } else if (state == ParseState.WaitPart) { + ctx.state = ParseState.NeedPart; + } else {} + if (ch[0] == '\n') ctx.line = ctx.line + 1; + }, + '=' => { + if (state == ParseState.ReadHole) { + ctx.state = ParseState.ReadKey; + ctx.curhl = LockHole{ .name = tokenStr(ctx), .keys = initkey: { + var res = vec(LockKey).init(allocator); + try res.append(vec(Module).init(allocator)); + break :initkey res; + } }; + } else if (state == ParseState.ReadLock) { + try ctx.token.appendSlice(ch); + } else if (state == ParseState.WaitPart) {} else if (state == ParseState.NeedPart) { + ctx.state = ParseState.WaitPart; + } else try parserr(try std.fmt.allocPrint(allocator, "error occur = on {s}", .{@tagName(state)}), ctx); + }, + ',' => { + if (state == ParseState.ReadKey) { + const token = tokenStr(ctx); + if (token.items.len > 0) { + if (ctx.curhl) |*hole| { + var key = &hole.keys.items[hole.keys.items.len - 1]; + try key.append(token); + } else unreachable; + } + } else if (state == ParseState.ReadHole) { + const token = tokenStr(ctx); + var holes = &ctx.curlk.?.holes; + const hole = holes.pop(); + if (token.items.len > 0) { + var key = &hole.keys.items[hole.keys.items.len - 1]; + try key.append(token); + } + ctx.curhl = hole; + ctx.state = ParseState.ReadKey; + } else if (state == ParseState.ReadLock) { + try ctx.token.appendSlice(ch); + } else if (state == ParseState.WaitPart) {} else if (state == ParseState.NeedPart) { + ctx.state = ParseState.WaitPart; + } else try parserr(try std.fmt.allocPrint(allocator, "error occur , on {s}", .{@tagName(state)}), ctx); + }, + ';' => { + if (state == ParseState.ReadKey) { + const token = tokenStr(ctx); + if (token.items.len > 0) { + const keys = ctx.curhl.?.keys; + var key = &keys.items[keys.items.len - 1]; + try key.append(token); + } + try ctx.curhl.?.keys.append(vec(Module).init(allocator)); + } else if (state == ParseState.ReadLock) { + try ctx.token.appendSlice(ch); + } else if (state == ParseState.ReadHole) { + var holes = &ctx.curlk.?.holes; + var hole = holes.pop(); + const token = tokenStr(ctx); + if (token.items.len > 0) { + var cmd = &hole.keys.items[hole.keys.items.len - 1]; + try cmd.append(token); + } + try hole.keys.append(vec(Module).init(allocator)); + ctx.curhl = hole; + ctx.state = ParseState.ReadKey; + } else if (state == ParseState.WaitPart) {} else if (state == ParseState.NeedPart) { + ctx.state = ParseState.WaitPart; + } else try parserr(try std.fmt.allocPrint(allocator, "error occur ; on {s}", .{@tagName(state)}), ctx); + }, + ' ' => { + if (state == ParseState.ReadLock or state == ParseState.ReadHole) { + try ctx.token.appendSlice(ch); + } else if (state == ParseState.NeedPart) { + ctx.state = ParseState.WaitPart; + } else {} + }, + else => { + if (state == ParseState.ReadLock or state == ParseState.ReadHole) { + try ctx.token.appendSlice(ch); + } else if (state == ParseState.ReadKey) { + const ishex = for (Hex) |c| { + if (c == ch[0]) { + break true; + } + } else false; + if (ishex) { + try ctx.token.appendSlice(ch); + } else if (ctx.curhl) |*hole| { + if (std.ascii.eqlIgnoreCase(hole.name.items, "text")) { + try ctx.token.appendSlice(ch); + } + } else try parserr(try std.fmt.allocPrint(allocator, "error occur {s} on {s}", .{ ch, @tagName(state) }), ctx); + } else if (state == ParseState.WaitPart) {} else if (state == ParseState.NeedPart) { + ctx.state = ParseState.WaitPart; + } else try parserr(try std.fmt.allocPrint(allocator, "error occur {s} on {s}", .{ ch, @tagName(state) }), ctx); + }, + } +} + +inline fn makeId(a: usize, b: usize) u32 { + return @intCast(a | (b << 16)); +} + +inline fn getalign(n: usize, basic: usize) usize { + const ext = n % basic; + return if (ext == 0) n else n + basic - ext; +} + +fn findIndex(t: []u8, tr: []u8) i32 { + const rlen: usize = tr.len; + const tlen: usize = t.len; + if (tlen < rlen) return -1; + const limit = tlen - rlen; + for (0..limit) |i| { + if (t[i + rlen] != 0) continue; + + const eq = for (0..rlen) |j| { + if (t[i + j] != tr[j]) { + break false; + } + } else true; + if (eq) return @intCast(i); + } + return -1; +} + +fn fromTaddr(taddr: vec(u8)) !u32 { + const n = try std.fmt.parseInt(u32, taddr.items, 16); + if (n == 0 or n > 0x41f_ffff) { + return error.Outofrange; + } + + return if (n < 0x3_ffff) n | 0x200_0000 else if (0 == (n & 0xf00_0000)) (n & 0xffff) | 0x300_0000 else n; +} + +fn assembleCheat(ctx: *ParserCtx, list: *vec(LockKey), dup: *map(usize, u16), hole: u16) !vec(u32) { + const alloc = ctx.farm; + + var addrval = map(u32, u32).init(alloc); + for (list.items) |*command| { + if (command.items.len == 0) { + continue; + } + const addr = try fromTaddr(command.items[0]); + const len = command.items.len - 1; + for (0..len) |pos| { + const target = command.items[pos + 1]; + try addrval.put(@intCast(addr + pos), try std.fmt.parseInt(u32, target.items, 16)); + } + } + + const ordered = calcordered: { + var res = vec([2]u32).init(alloc); + var iter = addrval.iterator(); + while (iter.next()) |kvp| { + try res.append(.{ kvp.key_ptr.*, kvp.value_ptr.* }); + } + std.sort.block([2]u32, res.items, {}, cmpOrdered); + break :calcordered res; + }; + var blocks = vec(u32).init(alloc); + var curr: [3]u32 = .{ 0, 0, 0 }; + for (ordered.items) |cmd| { + const addr = cmd[0]; + const value = cmd[1]; + try dup.put(addr, hole); + + if (curr[2] == 0) { + curr = .{ addr, value, 1 }; + } else { + if (curr[1] == value and curr[0] + curr[2] == addr) { + curr[2] = curr[2] + 1; + } else { + try blocks.append(curr[0]); + try blocks.append((curr[2] << 8) | curr[1]); + curr = .{ addr, value, 1 }; + } + } + } + if (curr[2] != 0) { + try blocks.append(curr[0]); + try blocks.append((curr[2] << 8) | curr[1]); + } + return blocks; +} + +fn cmpNames(ctx: void, a: *Module, b: *Module) bool { + _ = ctx; + if (a.items.len == b.items.len) { + return std.mem.lessThan(u8, a.items, b.items); + } else return b.items.len < a.items.len; +} + +fn cmpOrdered(ctx: void, a: [2]u32, b: [2]u32) bool { + _ = ctx; + return a[0] < b[0]; +} + +fn utpush(str: []const u8, table: *vec(u8)) !u16 { + const off = table.items.len; + try table.ensureTotalCapacity(off + str.len + 1); + try table.appendSlice(str); + try table.append(0); + return @intCast(off); +} + +fn utaddr(ctx: *ParserCtx, str: []u8, table: *vec(u8)) !u16 { + var strcache = &ctx.strcache.?; + if (strcache.contains(str)) { + const res = strcache.get(str); + return res.?; + } else { + const idx = findIndex(table.items, str); + const off: u16 = if (idx < 0) try utpush(str, table) else @intCast(idx); + try strcache.put(str, off); + return off; + } +} + +fn addString(ctx: *ParserCtx, list: *const vec(*Module), table: *vec(u8)) !vec(u16) { + var retval = try vec(u16).initCapacity(ctx.farm, list.items.len); + for (list.items) |str| { + try retval.append(try utaddr(ctx, str.items, table)); + } + return retval; +} + +fn addStrIndex(list: *const vec(u16), table: *vec(u16)) !u32 { + if (list.items.len == 0) return 0; + + const ret = table.items.len; + try table.ensureTotalCapacity(ret + list.items.len); + try table.appendSlice(list.items); + + return @intCast(ret * 2); +} + +fn addCommand(list: vec(u32), table: *vec(u32)) !u32 { + const ret = table.items.len; + try table.ensureTotalCapacity(ret + list.items.len); + try table.appendSlice(list.items); + + return @intCast(ret * 4); +} + +fn lade(ctx: *ParserCtx) !void { + // const st = std.time.microTimestamp(); + // defer runs = runs - st + std.time.microTimestamp(); + const alloc = ctx.farm; + var rootstr = vec(*Module).init(alloc); + var names = vec(*Module).init(alloc); + var enttable = vec(Entry).init(alloc); + + const locks = ctx.locks; + for (locks.items, 0..) |*l, i| { + try rootstr.append(&l.name); + try names.append(&l.name); + + var holestr = try ctx.farm.create(vec(*Module)); + holestr.* = vec(*Module).init(ctx.farm); + const colname = l.holes.items.len > 1; + for (l.holes.items, 0..) |*hole, index| { + try holestr.append(&hole.name); + if (colname) { + try names.append(&hole.name); + } + const id = makeId(i + 1, index + 1); + try enttable.append(Entry{ .id = id, .data = EntData{ .keys = &hole.keys } }); + } + const id = makeId(i + 1, 0); + try enttable.append(Entry{ .id = id, .data = EntData{ .strs = holestr } }); + } + try enttable.append(Entry{ .id = makeId(0, 0), .data = EntData{ .strs = &rootstr } }); + + if (enttable.items.len > 0xffff) { + try parserr(try std.fmt.allocPrint(alloc, "too many entries in file {s}", .{ctx.file}), ctx); + } + + std.sort.block(Entry, enttable.items, {}, cmpEntry); + std.sort.block(*Module, names.items, {}, cmpNames); + + var strtable = vec(u8).init(alloc); + var idxtable = vec(u16).init(alloc); + var cmdtable = vec(u32).init(alloc); + + ctx.strcache = hash(u16).init(alloc); + _ = try addString(ctx, &names, &strtable); + var entbytelist = vec([3]u32).init(alloc); + const emptylist = vec(*Module).init(alloc); + var holedup = map(usize, u16).init(alloc); + + for (enttable.items) |*entry| { + const id = entry.id; + if (id < 0x1_0000) { + const labels = if (id == 0) entry.data.strs else if (entry.data.strs.items.len > 1) entry.data.strs else &emptylist; + const idxlist = try addString(ctx, labels, &strtable); + const location = try addStrIndex(&idxlist, &idxtable); + try entbytelist.append([3]u32{ id, location, @intCast(idxlist.items.len) }); + } else { + const armbytecode = try assembleCheat(ctx, entry.data.keys, &holedup, @intCast(id & 0xffff)); + const location = try addCommand(armbytecode, &cmdtable); + try entbytelist.append([3]u32{ id, location, @intCast(armbytecode.items.len) }); + } + } + + const code = calccode: { + var res = ctx.code; + for (cmdtable.items) |cmd| res = res ^ cmd; + break :calccode res; + }; + + const sets = try pack(ctx, &entbytelist, &strtable, &idxtable, &cmdtable); + try ctx.output.append(CheatSet{ .code = code, .sets = sets }); +} + +fn pack(ctx: *ParserCtx, entlist: *vec([3]u32), strlist: *vec(u8), idxlist: *vec(u16), cmdlist: *vec(u32)) !vec(u8) { + const external = ctx.shop; + + const entrysize = @sizeOf([3]u32); + const strbase = 2 + entlist.items.len * entrysize; + const idxbase = strbase + strlist.items.len; + const cmdbase = idxbase + idxlist.items.len * 2; + const noalign_size = cmdbase + cmdlist.items.len * 4; + const size = getalign(noalign_size, 32); + const endian = std.builtin.Endian.little; + + var result = try vec(u8).initCapacity(external, size); + try result.writer().writeInt(u16, @intCast(entlist.items.len), endian); + + for (entlist.items) |entry| { + const locbase = if (entry[0] > 0xffff) cmdbase else idxbase; + try result.writer().writeInt(u32, entry[0], endian); + try result.writer().writeInt(u32, @intCast(locbase + entry[1]), endian); + try result.writer().writeInt(u32, entry[2], endian); + } + + try result.writer().writeAll(strlist.items); + try result.writer().writeAll(std.mem.sliceAsBytes(idxlist.items)); + try result.writer().writeAll(std.mem.sliceAsBytes(cmdlist.items)); + if (noalign_size < size) { + try result.writer().writeByteNTimes(0, size - noalign_size); + } + + return result; +} + +fn cmpCheatGrp(ctx: void, a: *CheatGrp, b: *CheatGrp) bool { + _ = ctx; + return std.mem.lessThan(u8, a.serial, b.serial); +} + +fn format(alloc: Allocator, global: Allocator, cheats: *vec(*CheatGrp)) ![]const u8 { + std.sort.block(*CheatGrp, cheats.items, {}, cmpCheatGrp); + std.debug.print("valid rom has {d}\n", .{cheats.items.len}); + + var sers: ?vec(u8) = null; + var offs: ?vec(u16) = null; + var chtc: ?usize = null; + var maxl: ?usize = null; + { + var item1 = vec(u8).init(alloc); + var item2 = vec(u16).init(alloc); + var item3: usize = 0; + var item4: usize = 0; + var item5: []const u8 = ""; + var last: usize = 0; + for (cheats.items) |cheat| { + const val = cheat.serial[0..3]; + if (!std.mem.eql(u8, val, item5)) { + if (item2.items.len > 0 and item3 - last > item4) { + item4 = item3 - last; + } + try item1.appendSlice(val); + try item2.append(@intCast(item3)); + last = item3; + item5 = val; + } + item3 = item3 + cheat.grp.items.len; + } + if (item2.items.len > 0) { + if (item3 - last > item4) { + item4 = item3 - last; + } + try item2.append(@intCast(item3)); + } + sers = item1; + offs = item2; + chtc = item3; + maxl = item4; + } + + var chts: ?vec(u32) = null; + var expanded: ?vec(u8) = null; + { + var item1 = vec(u32).init(alloc); + var item2 = vec(u8).init(alloc); + const item3 = getalign(8 + sers.?.items.len + offs.?.items.len * 2 + chtc.? * 8, 32); + for (cheats.items) |cheat| { + const val = cheat.serial[3]; + for (cheat.grp.items) |*set| { + const off = item3 + item2.items.len; + try item1.append(@intCast(val | (off << 3))); + try item1.append(set.code); + try item2.ensureTotalCapacity(item2.items.len + set.sets.items.len); + try item2.appendSlice(set.sets.items); + } + } + chts = item1; + expanded = item2; + } + std.debug.print("name: {d} cheats: {d} maxl: {d}\n", .{ sers.?.items.len, chtc.?, maxl.? }); + + const serialbase = 8; + const offsetbase = serialbase + sers.?.items.len; + const cheatbase = offsetbase + offs.?.items.len * 2; + const cheatend = cheatbase + chts.?.items.len * 4; + const expandbase = getalign(cheatend, 32); + const total = expandbase + expanded.?.items.len; + const endian = std.builtin.Endian.little; + + var retval = try global.alloc(u8, total); + retval[0] = 'A'; + retval[1] = 'C'; + retval[2] = 'L'; + retval[3] = 1; + + std.mem.writeInt(u16, retval[4..6], @intCast(sers.?.items.len / 3), endian); + std.mem.writeInt(u16, retval[6..8], @intCast(maxl.?), endian); + + std.mem.copyForwards(u8, retval[serialbase..offsetbase], sers.?.items); + std.mem.copyForwards(u8, retval[offsetbase..cheatbase], std.mem.sliceAsBytes(offs.?.items)); + std.mem.copyForwards(u8, retval[cheatbase..cheatend], std.mem.sliceAsBytes(chts.?.items)); + if (cheatend < expandbase) { + @memset(retval[cheatend..expandbase], 0); + } + std.mem.copyForwards(u8, retval[expandbase..], expanded.?.items); + + return retval; +}