diff --git a/src/core/data_formats/asm/instructions.ts b/src/core/data_formats/asm/instructions.ts index 17df8f84..dd7c95b9 100644 --- a/src/core/data_formats/asm/instructions.ts +++ b/src/core/data_formats/asm/instructions.ts @@ -1,5 +1,6 @@ import { Kind, Opcode } from "./opcodes"; import { array_buffers_equal, arrays_equal } from "../../util"; +import { BinFormat } from "../parsing/quest/BinFormat"; /** * Instruction invocation. @@ -7,14 +8,6 @@ import { array_buffers_equal, arrays_equal } from "../../util"; export type Instruction = { readonly opcode: Opcode; readonly args: readonly Arg[]; - /** - * Byte size of the argument list. - */ - readonly arg_size: number; - /** - * Byte size of the entire instruction, i.e. the sum of the opcode size and all argument sizes. - */ - readonly size: number; /** * Maps each parameter by index to its arguments. */ @@ -25,7 +18,6 @@ export type Instruction = { export function new_instruction(opcode: Opcode, args: Arg[], asm?: InstructionAsm): Instruction { const len = Math.min(opcode.params.length, args.length); const param_to_args: Arg[][] = []; - let arg_size = 0; for (let i = 0; i < len; i++) { const type = opcode.params[i].type; @@ -35,16 +27,12 @@ export function new_instruction(opcode: Opcode, args: Arg[], asm?: InstructionAs switch (type.kind) { case Kind.ILabelVar: case Kind.RegRefVar: - arg_size++; - for (let j = i; j < args.length; j++) { param_to_args[i].push(args[j]); - arg_size += args[j].size; } break; default: - arg_size += arg.size; param_to_args[i].push(arg); break; } @@ -53,13 +41,62 @@ export function new_instruction(opcode: Opcode, args: Arg[], asm?: InstructionAs return { opcode, args, - arg_size, - size: opcode.size + arg_size, param_to_args, asm, }; } +/** + * @returns The byte size of the entire instruction, i.e. the sum of the opcode size and all + * argument sizes. + */ +export function instruction_size(instruction: Instruction, format: BinFormat): number { + const opcode = instruction.opcode; + const p_len = Math.min(opcode.params.length, instruction.param_to_args.length); + let arg_size = 0; + + for (let i = 0; i < p_len; i++) { + const type = opcode.params[i].type; + const args = instruction.param_to_args[i]; + + switch (type.kind) { + case Kind.Byte: + case Kind.RegRef: + case Kind.RegTupRef: + arg_size++; + break; + case Kind.Word: + case Kind.Label: + case Kind.ILabel: + case Kind.DLabel: + case Kind.SLabel: + arg_size += 2; + break; + case Kind.DWord: + case Kind.Float: + arg_size += 4; + break; + case Kind.String: + if (format == BinFormat.DC_GC) { + arg_size += (args[0].value as string).length + 1; + } else { + arg_size += 2 * (args[0].value as string).length + 2; + } + break; + case Kind.ILabelVar: + arg_size += 1 + 2 * args.length; + break; + case Kind.RegRefVar: + arg_size += 1 + args.length; + break; + default: + throw new Error(`Parameter type ${Kind[type.kind]} not implemented.`); + } + } + + return opcode.size + arg_size; +} + function instructions_equal(a: Instruction, b: Instruction): boolean { return a.opcode.code === b.opcode.code && arrays_equal(a.args, b.args, args_equal); } @@ -68,8 +105,6 @@ export function clone_instruction(instr: Instruction): Instruction { return { opcode: instr.opcode, args: instr.args.map(arg => ({ ...arg })), - arg_size: instr.arg_size, - size: instr.size, param_to_args: instr.param_to_args.map(args => args.map(arg => ({ ...arg }))), asm: instr.asm, }; @@ -80,18 +115,14 @@ export function clone_instruction(instr: Instruction): Instruction { */ export type Arg = { readonly value: any; - readonly size: number; }; -export function new_arg(value: any, size: number): Arg { - return { - value, - size, - }; +export function new_arg(value: any): Arg { + return { value }; } function args_equal(a: Arg, b: Arg): boolean { - return a.value === b.value && a.size === b.size; + return a.value === b.value; } /** diff --git a/src/core/data_formats/parsing/quest/index.test.ts b/src/core/data_formats/parsing/quest/index.test.ts index 3c7d9152..84ef51cc 100644 --- a/src/core/data_formats/parsing/quest/index.test.ts +++ b/src/core/data_formats/parsing/quest/index.test.ts @@ -5,6 +5,12 @@ import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor"; import { BufferCursor } from "../../cursor/BufferCursor"; import { parse_qst_to_quest, write_quest_qst } from "./index"; import { ObjectType } from "./object_types"; +import { + DataSegment, + InstructionSegment, + SegmentType, + StringSegment, +} from "../../asm/instructions"; test("parse Towards the Future", () => { const buffer = readFileSync("test/resources/quest118_e.qst"); @@ -104,6 +110,35 @@ function round_trip_test(path: string, file_name: string, contents: Buffer): voi expect(test_quest.object_code.length).toBe(orig_quest.object_code.length); for (let i = 0; i < orig_quest.object_code.length; i++) { + const orig_segment = orig_quest.object_code[i]; + const test_segment = test_quest.object_code[i]; + + expect(test_segment.type).toBe(orig_segment.type); + expect(test_segment.labels).toEqual(orig_segment.labels); + + switch (orig_segment.type) { + case SegmentType.Instructions: + expect((test_segment as InstructionSegment).instructions.length).toBe( + orig_segment.instructions.length, + ); + + for (let j = 0; j < orig_segment.instructions.length; j++) { + const orig_inst = orig_segment.instructions[j]; + const test_inst = (test_segment as InstructionSegment).instructions[j]; + + expect(test_inst.opcode.code).toBe(orig_inst.opcode.code); + expect(test_inst.args).toEqual(orig_inst.args); + } + + break; + case SegmentType.Data: + expect((test_segment as DataSegment).data).toEqual(orig_segment.data); + break; + case SegmentType.String: + expect((test_segment as StringSegment).value).toBe(orig_segment.value); + break; + } + expect(test_quest.object_code[i]).toEqual(orig_quest.object_code[i]); } }); diff --git a/src/core/data_formats/parsing/quest/object_code.ts b/src/core/data_formats/parsing/quest/object_code.ts index 3846001f..3bbaad1e 100644 --- a/src/core/data_formats/parsing/quest/object_code.ts +++ b/src/core/data_formats/parsing/quest/object_code.ts @@ -2,6 +2,7 @@ import { Arg, DataSegment, Instruction, + instruction_size, InstructionSegment, new_arg, new_instruction, @@ -110,9 +111,15 @@ export function write_object_code( break; case Kind.String: if (format === BinFormat.DC_GC) { - cursor.write_string_ascii(arg.value, arg.size); + cursor.write_string_ascii( + arg.value, + (arg.value as string).length + 1, + ); } else { - cursor.write_string_utf16(arg.value, arg.size); + cursor.write_string_utf16( + arg.value, + 2 * (arg.value as string).length + 2, + ); } break; case Kind.ILabelVar: @@ -229,7 +236,7 @@ function internal_parse_object_code( switch (segment.type) { case SegmentType.Instructions: for (const instruction of segment.instructions) { - offset += instruction.size; + offset += instruction_size(instruction, format); } break; @@ -595,33 +602,31 @@ function parse_instruction_arguments(cursor: Cursor, opcode: Opcode, format: Bin for (const param of opcode.params) { switch (param.type.kind) { case Kind.Byte: - args.push(new_arg(cursor.u8(), 1)); + args.push(new_arg(cursor.u8())); break; case Kind.Word: - args.push(new_arg(cursor.u16(), 2)); + args.push(new_arg(cursor.u16())); break; case Kind.DWord: - args.push(new_arg(cursor.i32(), 4)); + args.push(new_arg(cursor.i32())); break; case Kind.Float: - args.push(new_arg(cursor.f32(), 4)); + args.push(new_arg(cursor.f32())); break; case Kind.Label: case Kind.ILabel: case Kind.DLabel: case Kind.SLabel: - args.push(new_arg(cursor.u16(), 2)); + args.push(new_arg(cursor.u16())); break; case Kind.String: { - const start_pos = cursor.position; const max_bytes = Math.min(4096, cursor.bytes_left); args.push( new_arg( format === BinFormat.DC_GC ? cursor.string_ascii(max_bytes, true, false) : cursor.string_utf16(max_bytes, true, false), - cursor.position - start_pos, ), ); } @@ -629,17 +634,17 @@ function parse_instruction_arguments(cursor: Cursor, opcode: Opcode, format: Bin case Kind.ILabelVar: { const arg_size = cursor.u8(); - args.push(...cursor.u16_array(arg_size).map(value => new_arg(value, 2))); + args.push(...cursor.u16_array(arg_size).map(value => new_arg(value))); } break; case Kind.RegRef: case Kind.RegTupRef: - args.push(new_arg(cursor.u8(), 1)); + args.push(new_arg(cursor.u8())); break; case Kind.RegRefVar: { const arg_size = cursor.u8(); - args.push(...cursor.u8_array(arg_size).map(value => new_arg(value, 1))); + args.push(...cursor.u8_array(arg_size).map(value => new_arg(value))); } break; default: diff --git a/src/quest_editor/scripting/assembly.test.ts b/src/quest_editor/scripting/assembly.test.ts index 9dbbaeb8..a85f8e72 100644 --- a/src/quest_editor/scripting/assembly.test.ts +++ b/src/quest_editor/scripting/assembly.test.ts @@ -37,7 +37,7 @@ test("basic script", () => { expect(segment_0.instructions.length).toBe(9); expect(segment_0.instructions[0].opcode).toBe(OP_SET_EPISODE); - expect(segment_0.instructions[0].args).toEqual([{ value: 0, size: 4 }]); + expect(segment_0.instructions[0].args).toEqual([{ value: 0 }]); expect(segment_0.instructions[0].asm).toEqual({ mnemonic: { line_no: 2, col: 10, len: 11 }, args: [{ line_no: 2, col: 22, len: 1 }], @@ -46,10 +46,10 @@ test("basic script", () => { expect(segment_0.instructions[1].opcode).toBe(OP_BB_MAP_DESIGNATE); expect(segment_0.instructions[1].args).toEqual([ - { value: 1, size: 1 }, - { value: 2, size: 2 }, - { value: 3, size: 1 }, - { value: 4, size: 1 }, + { value: 1 }, + { value: 2 }, + { value: 3 }, + { value: 4 }, ]); expect(segment_0.instructions[1].asm).toEqual({ mnemonic: { line_no: 3, col: 10, len: 16 }, @@ -63,13 +63,13 @@ test("basic script", () => { }); expect(segment_0.instructions[2].opcode).toBe(OP_ARG_PUSHL); - expect(segment_0.instructions[2].args).toEqual([{ value: 0, size: 4 }]); + expect(segment_0.instructions[2].args).toEqual([{ value: 0 }]); expect(segment_0.instructions[2].asm).toEqual({ args: [{ line_no: 4, col: 28, len: 1 }], stack_args: [], }); expect(segment_0.instructions[3].opcode).toBe(OP_ARG_PUSHW); - expect(segment_0.instructions[3].args).toEqual([{ value: 150, size: 2 }]); + expect(segment_0.instructions[3].args).toEqual([{ value: 150 }]); expect(segment_0.instructions[3].asm).toEqual({ args: [{ line_no: 4, col: 31, len: 3 }], stack_args: [], @@ -86,13 +86,13 @@ test("basic script", () => { }); expect(segment_0.instructions[5].opcode).toBe(OP_ARG_PUSHL); - expect(segment_0.instructions[5].args).toEqual([{ value: 1, size: 4 }]); + expect(segment_0.instructions[5].args).toEqual([{ value: 1 }]); expect(segment_0.instructions[5].asm).toEqual({ args: [{ line_no: 5, col: 28, len: 1 }], stack_args: [], }); expect(segment_0.instructions[6].opcode).toBe(OP_ARG_PUSHW); - expect(segment_0.instructions[6].args).toEqual([{ value: 151, size: 2 }]); + expect(segment_0.instructions[6].args).toEqual([{ value: 151 }]); expect(segment_0.instructions[6].asm).toEqual({ args: [{ line_no: 5, col: 31, len: 3 }], stack_args: [], @@ -117,7 +117,7 @@ test("basic script", () => { expect(segment_1.instructions.length).toBe(3); expect(segment_1.instructions[0].opcode).toBe(OP_ARG_PUSHL); - expect(segment_1.instructions[0].args).toEqual([{ value: 1, size: 4 }]); + expect(segment_1.instructions[0].args).toEqual([{ value: 1 }]); expect(segment_1.instructions[0].asm).toEqual({ args: [{ line_no: 7, col: 23, len: 1 }], stack_args: [], @@ -163,7 +163,7 @@ test("pass the value of a register via the stack", () => { expect(segment_0.instructions.length).toBe(4); expect(segment_0.instructions[1].opcode).toBe(OP_ARG_PUSHR); - expect(segment_0.instructions[1].args).toEqual([{ value: 255, size: 1 }]); + expect(segment_0.instructions[1].args).toEqual([{ value: 255 }]); }); test("pass a register reference via the stack", () => { @@ -186,8 +186,8 @@ test("pass a register reference via the stack", () => { expect(segment_0.instructions.length).toBe(4); expect(segment_0.instructions[0].opcode).toBe(OP_ARG_PUSHB); - expect(segment_0.instructions[0].args).toEqual([{ value: 200, size: 1 }]); + expect(segment_0.instructions[0].args).toEqual([{ value: 200 }]); expect(segment_0.instructions[1].opcode).toBe(OP_ARG_PUSHL); - expect(segment_0.instructions[1].args).toEqual([{ value: 3, size: 4 }]); + expect(segment_0.instructions[1].args).toEqual([{ value: 3 }]); }); diff --git a/src/quest_editor/scripting/assembly.ts b/src/quest_editor/scripting/assembly.ts index 1cf3be12..abac68f2 100644 --- a/src/quest_editor/scripting/assembly.ts +++ b/src/quest_editor/scripting/assembly.ts @@ -603,7 +603,7 @@ class Assembler { case Kind.Float: this.add_instruction( OP_ARG_PUSHL, - [{ value: reinterpret_f32_as_i32(arg.value), size: 4 }], + [{ value: reinterpret_f32_as_i32(arg.value) }], [], undefined, [arg_token], @@ -728,13 +728,7 @@ class Assembler { break; case Kind.Float: match = true; - arg_and_tokens.push([ - { - value: token.value, - size: 4, - }, - token, - ]); + arg_and_tokens.push([{ value: token.value }, token]); break; default: match = false; @@ -745,13 +739,7 @@ class Assembler { match = param.type.kind === Kind.Float; if (match) { - arg_and_tokens.push([ - { - value: token.value, - size: 4, - }, - token, - ]); + arg_and_tokens.push([{ value: token.value }, token]); } break; @@ -768,13 +756,7 @@ class Assembler { match = param.type.kind === Kind.String; if (match) { - arg_and_tokens.push([ - { - value: token.value, - size: 2 * token.value.length + 2, - }, - token, - ]); + arg_and_tokens.push([{ value: token.value }, token]); } break; @@ -864,13 +846,7 @@ class Assembler { message: `${bit_size}-Bit integer can't be greater than ${max_value}.`, }); } else { - arg_and_tokens.push([ - { - value, - size, - }, - token, - ]); + arg_and_tokens.push([{ value }, token]); } } @@ -884,13 +860,7 @@ class Assembler { message: `Invalid register reference, expected r0-r255.`, }); } else { - arg_and_tokens.push([ - { - value, - size: 1, - }, - token, - ]); + arg_and_tokens.push([{ value }, token]); } } diff --git a/src/quest_editor/scripting/disassembly.test.ts b/src/quest_editor/scripting/disassembly.test.ts index 553735b7..4f3605c0 100644 --- a/src/quest_editor/scripting/disassembly.test.ts +++ b/src/quest_editor/scripting/disassembly.test.ts @@ -34,10 +34,10 @@ test("vararg instructions should be disassembled correctly", () => { labels: [0], instructions: [ new_instruction(OP_SWITCH_JMP, [ - new_arg(90, 1), - new_arg(100, 2), - new_arg(101, 2), - new_arg(102, 2), + new_arg(90), + new_arg(100), + new_arg(101), + new_arg(102), ]), new_instruction(OP_RET, []), ], @@ -64,8 +64,8 @@ test("va list instructions should be disassembled correctly", () => { labels: [0], instructions: [ new_instruction(OP_VA_START, []), - new_instruction(OP_ARG_PUSHW, [new_arg(1337, 2)]), - new_instruction(OP_VA_CALL, [new_arg(100, 2)]), + 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, []), ], diff --git a/version.txt b/version.txt index abac1ea7..21e72e8a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -47 +48