2024-11-13 16:41:25 +08:00

758 lines
27 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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;
}