#!/usr/bin/env python3 """Merge several Mesen CDL captures into one. Mesen's CDL accumulates: a capture is the OR of everything the CPU did since the logger was reset. Two captures of different sessions therefore hold different subsets of the execution, and the union of them covers more of the game than either does alone. The flags are merged with OR, so a byte that is Code in one capture stays Code in the merge. Usage: python3 _tools/merge_cdl.py [in.cdl ...] [--size 32768] """ import argparse from pathlib import Path CODE, DATA, EXEC, JUMP = 0x01, 0x02, 0x04, 0x08 def read(path, size): raw = Path(path).read_bytes() if raw[:5] == b"CDLv2": body = raw[9:9 + size] else: body = raw[-size:] if len(body) < size: raise SystemExit(f"{path}: expected {size} CDL bytes, got {len(body)}") return body def main(): ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap.add_argument("out") ap.add_argument("ins", nargs="+") ap.add_argument("--size", type=int, default=32768) args = ap.parse_args() merged = bytearray(args.size) for p in args.ins: body = read(p, args.size) for i, b in enumerate(body): merged[i] |= b code = sum(1 for b in merged if b & CODE) data = sum(1 for b in merged if b & DATA) jump = sum(1 for b in merged if b & JUMP) print(f"merged {len(args.ins)} capture(s): {code} code bytes, " f"{data} data bytes, {jump} jump targets") out = bytearray(b"CDLv2") out += args.size.to_bytes(4, "little") out += merged Path(args.out).write_bytes(bytes(out)) print(f"wrote {args.out}") if __name__ == "__main__": main()