// AGI v2 LOGIC disassembler. // // Walks one LOGIC resource and prints the bytecode in a human-readable // form, with names for action opcodes, test commands, common variables // and flags. Intended as a debugging aid while filling out the VM: // dump KQ3's logic.0 + logic.45 to identify which stub opcodes are // gating title-screen progression. // // Usage: // disasmAgi [end-pc] // // `end-pc` caps the walk at a given offset; useful for spelunking // through huge logic.0 without dumping the whole thing. #include "agi.h" #include #include #include #define AGI_LOGIC_HDR_BYTES 2u #define ACTION_UNKNOWN 0xFFu #define MAX_TEST 0x12u // ----- Prototypes ----- static const char *actionName(uint8_t op); static uint16_t disasmAction(const uint8_t *code, uint16_t pc, uint16_t length, uint8_t op); static uint16_t disasmIf(const uint8_t *code, uint16_t pc, uint16_t length); static uint16_t disasmTest(const uint8_t *code, uint16_t pc, uint16_t length); static const char *testName(uint8_t op); static int walk(const uint8_t *code, uint16_t length, uint16_t endPc); // AGI v2 action-opcode names. Indexed by opcode id; NULL means "no // such opcode" (matches the VM's ACTION_UNKNOWN sentinel). static const char *kActionNames[256] = { /* 0x00 */ "return", "increment", "decrement", "assignn", /* 0x04 */ "assignv", "addn", "addv", "subn", /* 0x08 */ "subv", "lindirectv", "rindirect", "lindirectn", /* 0x0C */ "set", "reset", "toggle", "setv", /* 0x10 */ "resetv", "togglev", "new.room", "new.room.v", /* 0x14 */ "load.logic", "load.logic.v", "call", "call.v", /* 0x18 */ "load.pic", "draw.pic", "show.pic", "discard.pic", /* 0x1C */ "overlay.pic", "show.pri.screen", "load.view", "load.view.v", /* 0x20 */ "discard.view", "animate.obj", "unanimate.all", "draw", /* 0x24 */ "erase", "position", "position.v", "get.posn", /* 0x28 */ "reposition", "set.view", "set.view.v", "set.loop", /* 0x2C */ "set.loop.v", "fix.loop", "release.loop", "set.cel", /* 0x30 */ "set.cel.v", "last.cel", "current.cel", "current.loop", /* 0x34 */ "current.view", "number.of.loops", "set.priority", "set.priority.v", /* 0x38 */ "release.priority", "get.priority", "stop.update", "start.update", /* 0x3C */ "force.update", "ignore.horizon", "observe.horizon", "set.horizon", /* 0x40 */ "object.on.water", "object.on.land", "object.on.anything", "ignore.objs", /* 0x44 */ "observe.objs", "distance", "stop.cycling", "start.cycling", /* 0x48 */ "normal.cycle", "end.of.loop", "reverse.cycle", "reverse.loop", /* 0x4C */ "cycle.time", "stop.motion", "start.motion", "step.size", /* 0x50 */ "step.time", "move.obj", "move.obj.v", "follow.ego", /* 0x54 */ "wander", "normal.motion", "set.dir", "get.dir", /* 0x58 */ "ignore.blocks", "observe.blocks", "block", "unblock", /* 0x5C */ "get", "get.v", "drop", "put", /* 0x60 */ "put.v", "get.room.v", "load.sound", "sound", /* 0x64 */ "stop.sound", "print", "print.v", "display", /* 0x68 */ "display.v", "clear.lines", "text.screen", "graphics", /* 0x6C */ "set.cursor.char", "set.text.attribute", "shake.screen", "configure.screen", /* 0x70 */ "status.line.on", "status.line.off", "set.string", "get.string", /* 0x74 */ "word.to.string", "parse", "get.num", "prevent.input", /* 0x78 */ "accept.input", "set.key", "add.to.pic", "add.to.pic.v", /* 0x7C */ "status", "save.game", "restore.game", "init.disk", /* 0x80 */ "restart.game", "show.obj", "random", "program.control", /* 0x84 */ "player.control", "obj.status.v", "quit", "show.mem", /* 0x88 */ "pause", "echo.line", "cancel.line", "init.joy", /* 0x8C */ "toggle.monitor", "version", "script.size", "set.game.id", /* 0x90 */ "log", "set.scan.start", "reset.scan.start", "reposition.to", /* 0x94 */ "reposition.to.v", "trace.on", "trace.info", "print.at", /* 0x98 */ "print.at.v", "discard.view.v", "clear.text.rect", "set.upper.left", /* 0x9C */ "set.menu", "set.menu.item", "submit.menu", "enable.item", /* 0xA0 */ "disable.item", "menu.input", "show.obj.v", "open.dialogue", /* 0xA4 */ "close.dialogue", "mul.n", "mul.v", "div.n", /* 0xA8 */ "div.v", "close.window", "set.simple", "push.script", /* 0xAC */ "pop.script", "hold.key", "set.pri.base", "discard.sound", /* 0xB0 */ "hide.mouse", "allow.menu", "show.mouse", "fence.mouse", /* 0xB4 */ "mouse.posn", "release.key", "adj.ego.move.to.x.y", /* 0xB7..0xFD */ NULL // 0xFE = goto, 0xFF = if -- handled separately }; // AGI v2 action arg byte counts (matches kActionArgBytes in agiVm.c). static const uint8_t kActionArgBytes[256] = { /* 0x00 */ 0u, 1u, 1u, 2u, 2u, 2u, 2u, 2u, 2u, 2u, 2u, 2u, 1u, 1u, 1u, 1u, /* 0x10 */ 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 0u, 1u, 1u, 0u, 1u, 1u, /* 0x20 */ 1u, 1u, 0u, 1u, 1u, 3u, 3u, 3u, 3u, 2u, 2u, 2u, 2u, 1u, 1u, 2u, /* 0x30 */ 2u, 2u, 2u, 2u, 2u, 2u, 2u, 2u, 1u, 2u, 1u, 1u, 1u, 1u, 1u, 1u, /* 0x40 */ 1u, 1u, 1u, 1u, 1u, 3u, 1u, 1u, 1u, 2u, 1u, 2u, 2u, 1u, 1u, 2u, /* 0x50 */ 2u, 5u, 5u, 3u, 1u, 1u, 2u, 2u, 1u, 1u, 4u, 0u, 1u, 1u, 1u, 2u, /* 0x60 */ 2u, 2u, 1u, 2u, 0u, 1u, 1u, 3u, 3u, 3u, 0u, 0u, 1u, 2u, 1u, 3u, /* 0x70 */ 0u, 0u, 2u, 5u, 2u, 1u, 2u, 0u, 0u, 3u, 7u, 7u, 0u, 0u, 0u, 0u, /* 0x80 */ 0u, 1u, 3u, 0u, 0u, 1u, 1u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 1u, 1u, /* 0x90 */ 1u, 0u, 0u, 3u, 3u, 0u, 3u, 4u, 4u, 1u, 4u, 2u, 1u, 2u, 0u, 1u, /* 0xA0 */ 1u, 0u, 1u, 0u, 0u, 2u, 2u, 2u, 2u, 0u, 1u, 0u, 0u, 0u, 1u, 1u, /* 0xB0 */ 0u, 1u, 0u, 4u, 2u, 0u, 0u, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, /* 0xBA */ ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, /* 0xC0 */ ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, /* 0xC8 */ ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, /* 0xD0 */ ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, /* 0xD8 */ ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, /* 0xE0 */ ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, /* 0xE8 */ ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, /* 0xF0 */ ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, /* 0xF8 */ ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN, ACTION_UNKNOWN }; static const char *kTestNames[MAX_TEST + 1u] = { /* 0x00 */ NULL, /* 0x01 */ "equaln", /* 0x02 */ "equalv", /* 0x03 */ "lessn", /* 0x04 */ "lessv", /* 0x05 */ "greatern", /* 0x06 */ "greaterv", /* 0x07 */ "isset", /* 0x08 */ "issetv", /* 0x09 */ "has", /* 0x0A */ "obj.in.room", /* 0x0B */ "posn", /* 0x0C */ "controller", /* 0x0D */ "have.key", /* 0x0E */ "said", /* 0x0F */ "compare.strings", /* 0x10 */ "obj.in.box", /* 0x11 */ "center.posn", /* 0x12 */ "right.posn" }; static const uint8_t kTestArgBytes[MAX_TEST + 1u] = { 0u, 2u, 2u, 2u, 2u, 2u, 2u, 1u, 1u, 1u, 2u, 5u, 1u, 0u, 0u /*said*/, 2u, 5u, 5u, 5u }; // ----- Helpers (alphabetical) ----- static const char *actionName(uint8_t op) { if (op == 0xFFu) { return "if"; } if (op == 0xFEu) { return "goto"; } if (kActionNames[op] != NULL) { return kActionNames[op]; } return "???"; } static uint16_t disasmAction(const uint8_t *code, uint16_t pc, uint16_t length, uint8_t op) { uint8_t args; uint8_t i; uint16_t here; here = (uint16_t)(pc - 1u); args = kActionArgBytes[op]; if (args == ACTION_UNKNOWN) { printf(" %04X: %02X ???\n", (unsigned)here, (unsigned)op); return length; } if ((uint32_t)pc + (uint32_t)args > (uint32_t)length) { printf(" %04X: %02X truncated args\n", (unsigned)here, (unsigned)op); return length; } printf(" %04X: %s", (unsigned)here, actionName(op)); if (args == 0u) { printf("()\n"); } else { printf("("); for (i = 0u; i < args; i++) { if (i > 0u) { printf(", "); } printf("%u", (unsigned)code[pc + i]); } printf(")\n"); } return (uint16_t)(pc + args); } static uint16_t disasmIf(const uint8_t *code, uint16_t pc, uint16_t length) { uint16_t here; uint8_t testOp; bool first; bool notNext; bool inOr; uint16_t skip; uint16_t target; here = (uint16_t)(pc - 1u); printf(" %04X: if (", (unsigned)here); first = true; notNext = false; inOr = false; for (;;) { if (pc >= length) { printf(" )\n"); return length; } testOp = code[pc]; pc = (uint16_t)(pc + 1u); if (testOp == 0xFFu) { // End of test list. break; } if (testOp == 0xFCu) { if (inOr) { printf(")"); inOr = false; } else { if (!first) { printf(" && "); } printf("("); inOr = true; first = true; } continue; } if (testOp == 0xFDu) { notNext = true; continue; } if (!first) { printf(inOr ? " || " : " && "); } first = false; if (notNext) { printf("!"); notNext = false; } pc = disasmTest(code, pc, length); } if (inOr) { printf(")"); } if ((uint32_t)pc + 2u > (uint32_t)length) { printf(") \n"); return length; } skip = (uint16_t)(code[pc] | ((uint16_t)code[pc + 1u] << 8)); target = (uint16_t)(pc + 2u + skip); printf(") -> %04X\n", (unsigned)target); return (uint16_t)(pc + 2u); } static uint16_t disasmTest(const uint8_t *code, uint16_t pc, uint16_t length) { uint8_t args; uint8_t i; uint8_t testOp; uint8_t wordCount; uint16_t word; testOp = code[pc - 1u]; if (testOp == 0u || testOp > MAX_TEST) { printf("???_test_0x%02X(", (unsigned)testOp); return pc; } if (testOp == 0x0Eu) { // said(word, word, ...) if (pc >= length) { printf("said("); return length; } wordCount = code[pc]; pc = (uint16_t)(pc + 1u); printf("said("); for (i = 0u; i < wordCount; i++) { if (i > 0u) { printf(", "); } if ((uint32_t)pc + 2u > (uint32_t)length) { printf(""); return length; } word = (uint16_t)(code[pc] | ((uint16_t)code[pc + 1u] << 8)); printf("w%u", (unsigned)word); pc = (uint16_t)(pc + 2u); } printf(")"); return pc; } args = kTestArgBytes[testOp]; printf("%s(", testName(testOp)); if ((uint32_t)pc + (uint32_t)args > (uint32_t)length) { printf(")"); return length; } for (i = 0u; i < args; i++) { if (i > 0u) { printf(", "); } printf("%u", (unsigned)code[pc + i]); } printf(")"); return (uint16_t)(pc + args); } static const char *testName(uint8_t op) { if (op > MAX_TEST || kTestNames[op] == NULL) { return "???"; } return kTestNames[op]; } static int walk(const uint8_t *code, uint16_t length, uint16_t endPc) { uint16_t pc; uint8_t op; int16_t gotoOff; uint16_t target; pc = 0u; while (pc < length && pc < endPc) { op = code[pc]; pc = (uint16_t)(pc + 1u); if (op == 0xFFu) { pc = disasmIf(code, pc, length); continue; } if (op == 0xFEu) { if ((uint32_t)pc + 2u > (uint32_t)length) { printf(" %04X: goto \n", (unsigned)(pc - 1u)); return 1; } gotoOff = (int16_t)(code[pc] | ((uint16_t)code[pc + 1u] << 8)); target = (uint16_t)((int32_t)pc + 2 + (int32_t)gotoOff); printf(" %04X: goto %04X\n", (unsigned)(pc - 1u), (unsigned)target); pc = (uint16_t)(pc + 2u); continue; } pc = disasmAction(code, pc, length, op); if (op == 0x00u) { // return -- end of logic. Keep walking past it; some // logics have unreachable handlers below the top-level // return that callers jump into via goto. } } return 0; } // ----- main ----- int main(int argc, char **argv) { AgiGameT game; uint16_t logicId; uint8_t *bytes; uint16_t length; uint16_t bytecodeLen; uint16_t endPc; int ret; if (argc < 3 || argc > 4) { fprintf(stderr, "usage: %s [end-pc]\n", argv[0]); return 2; } if (!agiResOpen(&game, argv[1])) { fprintf(stderr, "FAIL: agiResOpen rejected '%s'\n", argv[1]); return 1; } logicId = (uint16_t)atoi(argv[2]); if (logicId >= game.resCount[AGI_RES_LOGIC]) { fprintf(stderr, "FAIL: no LOGIC at id %u\n", (unsigned)logicId); agiResClose(&game); return 1; } bytes = agiResLoad(&game, AGI_RES_LOGIC, logicId, &length); if (bytes == NULL) { fprintf(stderr, "FAIL: LOGIC %u failed to load\n", (unsigned)logicId); agiResClose(&game); return 1; } if (length < AGI_LOGIC_HDR_BYTES) { fprintf(stderr, "FAIL: LOGIC %u truncated header\n", (unsigned)logicId); free(bytes); agiResClose(&game); return 1; } bytecodeLen = (uint16_t)(bytes[0] | ((uint16_t)bytes[1] << 8)); if ((uint32_t)bytecodeLen + AGI_LOGIC_HDR_BYTES > (uint32_t)length) { fprintf(stderr, "FAIL: LOGIC %u bytecode length exceeds resource\n", (unsigned)logicId); free(bytes); agiResClose(&game); return 1; } endPc = (argc == 4) ? (uint16_t)atoi(argv[3]) : bytecodeLen; if (endPc > bytecodeLen) { endPc = bytecodeLen; } printf("LOGIC %u: %u bytes resource, %u bytes bytecode\n", (unsigned)logicId, (unsigned)length, (unsigned)bytecodeLen); ret = walk(bytes + AGI_LOGIC_HDR_BYTES, bytecodeLen, endPc); free(bytes); agiResClose(&game); return ret; }