135 lines
3.7 KiB
C
135 lines
3.7 KiB
C
// Host-side test for examples/agi/agiVm.c (Phase 1 sub-A).
|
|
//
|
|
// Loads a LOGIC resource (default 0), runs the VM until it halts,
|
|
// and prints the halt reason plus any non-zero vars/flags. Since
|
|
// only the arithmetic and flag opcodes (0x00-0x11) are implemented
|
|
// this pass, expectation is one of:
|
|
// - HALT_RETURN at offset N (logic was entirely sub-A opcodes)
|
|
// - HALT_UNKNOWN_OP 0xXX at offset N (hit IF / new.room / etc.)
|
|
// - HALT_TRUNCATED at offset N (malformed resource)
|
|
//
|
|
// Usage:
|
|
// testAgiVm <game-directory> [logic-id]
|
|
|
|
#include "agi.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static const char *haltName(AgiVmHaltE r);
|
|
static void printNonZeroState(const AgiVmT *vm);
|
|
|
|
|
|
// ----- Helpers (alphabetical) -----
|
|
|
|
static const char *haltName(AgiVmHaltE r) {
|
|
switch (r) {
|
|
case AGI_VM_HALT_NONE: return "NONE";
|
|
case AGI_VM_HALT_RETURN: return "RETURN";
|
|
case AGI_VM_HALT_UNKNOWN_OP: return "UNKNOWN_OP";
|
|
case AGI_VM_HALT_TRUNCATED: return "TRUNCATED";
|
|
case AGI_VM_HALT_NEW_ROOM: return "NEW_ROOM";
|
|
default: return "?";
|
|
}
|
|
}
|
|
|
|
|
|
static void printNonZeroState(const AgiVmT *vm) {
|
|
uint16_t i;
|
|
uint16_t nonZeroVars;
|
|
uint16_t nonZeroFlags;
|
|
|
|
nonZeroVars = 0u;
|
|
nonZeroFlags = 0u;
|
|
for (i = 0u; i < AGI_VM_NUM_VARS; i++) {
|
|
if (vm->vars[i] != 0u) {
|
|
if (nonZeroVars == 0u) {
|
|
printf(" vars:");
|
|
}
|
|
printf(" v%u=%u", (unsigned)i, (unsigned)vm->vars[i]);
|
|
nonZeroVars++;
|
|
}
|
|
}
|
|
if (nonZeroVars > 0u) {
|
|
printf("\n");
|
|
}
|
|
for (i = 0u; i < AGI_VM_NUM_FLAGS; i++) {
|
|
if (vm->flags[i] != 0u) {
|
|
if (nonZeroFlags == 0u) {
|
|
printf(" flags:");
|
|
}
|
|
printf(" f%u", (unsigned)i);
|
|
nonZeroFlags++;
|
|
}
|
|
}
|
|
if (nonZeroFlags > 0u) {
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
|
|
// ----- main -----
|
|
|
|
int main(int argc, char **argv) {
|
|
AgiGameT game;
|
|
AgiVmT vm;
|
|
uint16_t logicId;
|
|
uint8_t *bytes;
|
|
uint16_t length;
|
|
AgiVmHaltE reason;
|
|
|
|
if (argc < 2 || argc > 3) {
|
|
fprintf(stderr, "usage: %s <game-directory> [logic-id]\n", argv[0]);
|
|
return 2;
|
|
}
|
|
|
|
if (!agiResOpen(&game)) {
|
|
fprintf(stderr, "FAIL: agiResOpen rejected '%s'\n", argv[1]);
|
|
return 1;
|
|
}
|
|
|
|
logicId = (argc == 3) ? (uint16_t)atoi(argv[2]) : 0u;
|
|
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;
|
|
}
|
|
|
|
agiVmInit(&vm);
|
|
if (!agiVmLoadLogic(&vm, bytes, length)) {
|
|
fprintf(stderr, "FAIL: agiVmLoadLogic rejected LOGIC %u (truncated header?)\n", (unsigned)logicId);
|
|
free(bytes);
|
|
agiResClose(&game);
|
|
return 1;
|
|
}
|
|
|
|
printf("LOGIC %u: %u bytes resource, %u bytes bytecode\n",
|
|
(unsigned)logicId, (unsigned)length, (unsigned)vm.codeLength);
|
|
|
|
reason = agiVmRun(&vm);
|
|
|
|
printf(" halt=%s at pc=%u/%u",
|
|
haltName(reason), (unsigned)vm.pc, (unsigned)vm.codeLength);
|
|
if (reason == AGI_VM_HALT_UNKNOWN_OP) {
|
|
printf(" opcode=0x%02X", (unsigned)vm.lastUnknownOp);
|
|
}
|
|
if (reason == AGI_VM_HALT_NEW_ROOM) {
|
|
printf(" newRoom=%u", (unsigned)vm.newRoomId);
|
|
}
|
|
printf("\n");
|
|
printNonZeroState(&vm);
|
|
|
|
free(bytes);
|
|
agiResClose(&game);
|
|
return 0;
|
|
}
|