import { readFileSync } from "fs"; import { Endianness } from "../../core/data_formats/Endianness"; import { prs_decompress } from "../../core/data_formats/compression/prs/decompress"; import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor"; import { BufferCursor } from "../../core/data_formats/cursor/BufferCursor"; import { parse_bin, write_bin } from "../../core/data_formats/parsing/quest/bin"; import { assemble } from "./assembly"; import { disassemble } from "./disassembly"; import { new_arg, new_instruction, Segment, segment_arrays_equal, SegmentType, } from "../../core/data_formats/asm/instructions"; import { OP_ARG_PUSHW, OP_RET, OP_SWITCH_JMP, OP_VA_CALL, OP_VA_END, OP_VA_START, } from "../../core/data_formats/asm/opcodes"; import { parse_object_code, write_object_code, } from "../../core/data_formats/parsing/quest/object_code"; import { BinFormat } from "../../core/data_formats/parsing/quest/BinFormat"; test("vararg instructions should be disassembled correctly", () => { const asm = disassemble([ { type: SegmentType.Instructions, labels: [0], instructions: [ new_instruction(OP_SWITCH_JMP, [ new_arg(90), new_arg(100), new_arg(101), new_arg(102), ]), new_instruction(OP_RET, []), ], asm: { labels: [] }, }, ]); expect(asm).toEqual( `.code 0: switch_jmp r90, 100, 101, 102 ret `.split("\n"), ); }); // arg_push* instructions should always be output when in a va list whether manual stack management // is on or off. test("va list instructions should be disassembled correctly", () => { const ir: Segment[] = [ { type: SegmentType.Instructions, labels: [0], instructions: [ new_instruction(OP_VA_START, []), new_instruction(OP_ARG_PUSHW, [new_arg(1337)]), new_instruction(OP_VA_CALL, [new_arg(100)]), new_instruction(OP_VA_END, []), new_instruction(OP_RET, []), ], asm: { labels: [] }, }, ]; for (const manual_stack of [true, false]) { const asm = disassemble(ir, manual_stack); expect(asm).toEqual( `.code 0: va_start arg_pushw 1337 va_call 100 va_end ret `.split("\n"), ); } }); // Round-trip test. test("assembling disassembled object code with manual stack management should result in the same IR", () => { const orig_buffer = readFileSync("test/resources/quest27_e.bin"); const orig_bytes = prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)); const { bin } = parse_bin(orig_bytes); const orig_object_code = parse_object_code( bin.object_code, bin.label_offsets, [0], false, BinFormat.BB, ); const { object_code, warnings, errors } = assemble(disassemble(orig_object_code, true), true); expect(errors).toEqual([]); expect(warnings).toEqual([]); expect(segment_arrays_equal(object_code, orig_object_code)).toBe(true); }); // Round-trip test. test("assembling disassembled object code with automatic stack management should result in the same IR", () => { const orig_buffer = readFileSync("test/resources/quest27_e.bin"); const orig_bytes = prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)); const { bin } = parse_bin(orig_bytes); const orig_object_code = parse_object_code( bin.object_code, bin.label_offsets, [0], false, BinFormat.BB, ); const { object_code, warnings, errors } = assemble(disassemble(orig_object_code, false), false); expect(errors).toEqual([]); expect(warnings).toEqual([]); expect(segment_arrays_equal(object_code, orig_object_code)).toBe(true); }); // Round-trip test. test("assembling disassembled object code with manual stack management should result in the same object code", () => { const orig_buffer = readFileSync("test/resources/quest27_e.bin"); const orig_bytes = prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)); const { bin, format } = parse_bin(orig_bytes); const orig_object_code = parse_object_code( bin.object_code, bin.label_offsets, [0], false, BinFormat.BB, ); const { object_code, warnings, errors } = assemble(disassemble(orig_object_code, true), true); expect(errors).toEqual([]); expect(warnings).toEqual([]); const test_bytes = new ArrayBufferCursor( write_bin({ ...bin, ...write_object_code(object_code, format).object_code }, BinFormat.BB), Endianness.Little, ); orig_bytes.seek_start(0); expect(test_bytes.size).toBe(orig_bytes.size); let matching_bytes = 0; while (orig_bytes.bytes_left) { const test_byte = test_bytes.u8(); const orig_byte = orig_bytes.u8(); if (test_byte !== orig_byte) { throw new Error( `Byte ${matching_bytes} didn't match, expected ${orig_byte}, got ${test_byte}.`, ); } matching_bytes++; } expect(matching_bytes).toBe(orig_bytes.size); }); // Round-trip test. test("disassembling assembled assembly code with automatic stack management should result the same assembly code", () => { const orig_buffer = readFileSync("test/resources/quest27_e.bin"); const orig_bytes = prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)); const { bin } = parse_bin(orig_bytes); const orig_object_code = parse_object_code( bin.object_code, bin.label_offsets, [0], false, BinFormat.BB, ); const orig_asm = disassemble(orig_object_code, false); const { object_code, warnings, errors } = assemble(orig_asm, false); expect(errors).toEqual([]); expect(warnings).toEqual([]); const test_asm = disassemble(object_code); const len = Math.min(orig_asm.length, test_asm.length); for (let i = 0; i < len; i++) { expect(test_asm[i]).toBe(orig_asm[i]); } expect(test_asm.length).toBe(orig_asm.length); });