加入zig写的打包脚本

This commit is contained in:
a92126 2024-11-13 16:41:25 +08:00
parent 5e503b55ff
commit 9afbcd5bf7

View File

@ -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;
}