diff --git a/src/core/data_formats/parsing/quest/bin.ts b/src/core/data_formats/parsing/quest/bin.ts index e9c03b01..52cad4ce 100644 --- a/src/core/data_formats/parsing/quest/bin.ts +++ b/src/core/data_formats/parsing/quest/bin.ts @@ -8,6 +8,7 @@ import { DataSegment, Instruction, InstructionSegment, + new_arg, new_instruction, Segment, SegmentType, @@ -638,50 +639,48 @@ function parse_instruction_arguments(cursor: Cursor, opcode: Opcode): Arg[] { for (const param of opcode.params) { switch (param.type.kind) { case Kind.Byte: - args.push({ value: cursor.u8(), size: 1 }); + args.push(new_arg(cursor.u8(), 1)); break; case Kind.Word: - args.push({ value: cursor.u16(), size: 2 }); + args.push(new_arg(cursor.u16(), 2)); break; case Kind.DWord: - args.push({ value: cursor.i32(), size: 4 }); + args.push(new_arg(cursor.i32(), 4)); break; case Kind.Float: - args.push({ value: cursor.f32(), size: 4 }); + args.push(new_arg(cursor.f32(), 4)); break; case Kind.Label: case Kind.ILabel: case Kind.DLabel: case Kind.SLabel: - args.push({ value: cursor.u16(), size: 2 }); + args.push(new_arg(cursor.u16(), 2)); break; case Kind.String: { const start_pos = cursor.position; - args.push({ - value: cursor.string_utf16( - Math.min(4096, cursor.bytes_left), - true, - false, + args.push( + new_arg( + cursor.string_utf16(Math.min(4096, cursor.bytes_left), true, false), + cursor.position - start_pos, ), - size: cursor.position - start_pos, - }); + ); } break; case Kind.ILabelVar: { const arg_size = cursor.u8(); - args.push(...cursor.u16_array(arg_size).map(value => ({ value, size: 2 }))); + args.push(...cursor.u16_array(arg_size).map(value => new_arg(value, 2))); } break; case Kind.RegRef: case Kind.RegTupRef: - args.push({ value: cursor.u8(), size: 1 }); + args.push(new_arg(cursor.u8(), 1)); break; case Kind.RegRefVar: { const arg_size = cursor.u8(); - args.push(...cursor.u8_array(arg_size).map(value => ({ value, size: 1 }))); + args.push(...cursor.u8_array(arg_size).map(value => new_arg(value, 1))); } break; default: diff --git a/src/quest_editor/scripting/assembly.ts b/src/quest_editor/scripting/assembly.ts index a112287f..612cd6ee 100644 --- a/src/quest_editor/scripting/assembly.ts +++ b/src/quest_editor/scripting/assembly.ts @@ -193,7 +193,7 @@ class Assembler { }; } - private add_instruction(opcode: Opcode, args: Arg[]): void { + private add_instruction(opcode: Opcode, args: Arg[], token: IdentToken): void { if (!this.segment) { // Unreachable code, technically valid. const instruction_segment: InstructionSegment = { @@ -205,7 +205,9 @@ class Assembler { this.segment = instruction_segment; this.object_code.push(instruction_segment); } else if (this.segment.type === SegmentType.Instructions) { - this.segment.instructions.push(new_instruction(opcode, args)); + this.segment.instructions.push( + new_instruction(opcode, args, { col: token.col, len: token.len }), + ); } else { logger.error(`Line ${this.line_no}: Expected instructions segment.`); } @@ -418,7 +420,8 @@ class Assembler { } } - private parse_instruction({ col, len, value }: IdentToken): void { + private parse_instruction(ident_token: IdentToken): void { + const { col, len, value } = ident_token; const opcode = OPCODES_BY_MNEMONIC.get(value); if (!opcode) { @@ -495,37 +498,45 @@ class Assembler { if (token.type === TokenType.Register) { if (param.type.kind === Kind.RegTupRef) { - this.add_instruction(OP_ARG_PUSHB, [arg]); + this.add_instruction(OP_ARG_PUSHB, [arg], ident_token); } else { - this.add_instruction(OP_ARG_PUSHR, [arg]); + this.add_instruction(OP_ARG_PUSHR, [arg], ident_token); } } else { switch (param.type.kind) { case Kind.Byte: case Kind.RegRef: case Kind.RegTupRef: - this.add_instruction(OP_ARG_PUSHB, [arg]); + this.add_instruction(OP_ARG_PUSHB, [arg], ident_token); break; case Kind.Word: case Kind.Label: case Kind.ILabel: case Kind.DLabel: case Kind.SLabel: - this.add_instruction(OP_ARG_PUSHW, [arg]); + this.add_instruction(OP_ARG_PUSHW, [arg], ident_token); break; case Kind.DWord: - this.add_instruction(OP_ARG_PUSHL, [arg]); + this.add_instruction(OP_ARG_PUSHL, [arg], ident_token); break; case Kind.Float: - this.add_instruction(OP_ARG_PUSHL, [ - { - value: reinterpret_f32_as_i32(arg.value), - size: 4, - }, - ]); + this.add_instruction( + OP_ARG_PUSHL, + [ + { + value: reinterpret_f32_as_i32(arg.value), + size: 4, + asm: { + col: token.col, + len: token.len, + }, + }, + ], + ident_token, + ); break; case Kind.String: - this.add_instruction(OP_ARG_PUSHS, [arg]); + this.add_instruction(OP_ARG_PUSHS, [arg], ident_token); break; default: logger.error( @@ -539,7 +550,7 @@ class Assembler { } } - this.add_instruction(opcode, ins_args.map(([arg]) => arg)); + this.add_instruction(opcode, ins_args.map(([arg]) => arg), ident_token); } } @@ -616,6 +627,10 @@ class Assembler { { value: token.value, size: 4, + asm: { + col: token.col, + len: token.len, + }, }, token, ]); @@ -633,6 +648,10 @@ class Assembler { { value: token.value, size: 4, + asm: { + col: token.col, + len: token.len, + }, }, token, ]); @@ -656,6 +675,10 @@ class Assembler { { value: token.value, size: 2 * token.value.length + 2, + asm: { + col: token.col, + len: token.len, + }, }, token, ]); @@ -752,6 +775,10 @@ class Assembler { { value, size, + asm: { + col: token.col, + len: token.len, + }, }, token, ]); @@ -772,6 +799,10 @@ class Assembler { { value, size: 1, + asm: { + col: token.col, + len: token.len, + }, }, token, ]); diff --git a/src/quest_editor/scripting/disassembly.test.ts b/src/quest_editor/scripting/disassembly.test.ts index 89b04a85..fa7167c9 100644 --- a/src/quest_editor/scripting/disassembly.test.ts +++ b/src/quest_editor/scripting/disassembly.test.ts @@ -6,7 +6,13 @@ 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_instruction, Segment, segment_arrays_equal, SegmentType } from "./instructions"; +import { + new_arg, + new_instruction, + Segment, + segment_arrays_equal, + SegmentType, +} from "./instructions"; import { OP_ARG_PUSHW, OP_RET, OP_SWITCH_JMP, OP_VA_CALL, OP_VA_END, OP_VA_START } from "./opcodes"; test("vararg instructions should be disassembled correctly", () => { @@ -16,10 +22,10 @@ test("vararg instructions should be disassembled correctly", () => { labels: [0], instructions: [ new_instruction(OP_SWITCH_JMP, [ - { value: 90, size: 1 }, - { value: 100, size: 2 }, - { value: 101, size: 2 }, - { value: 102, size: 2 }, + new_arg(90, 1), + new_arg(100, 2), + new_arg(101, 2), + new_arg(102, 2), ]), new_instruction(OP_RET, []), ], @@ -45,8 +51,8 @@ test("va list instructions should be disassembled correctly", () => { labels: [0], instructions: [ new_instruction(OP_VA_START, []), - new_instruction(OP_ARG_PUSHW, [{ value: 1337, size: 2 }]), - new_instruction(OP_VA_CALL, [{ value: 100, size: 2 }]), + new_instruction(OP_ARG_PUSHW, [new_arg(1337, 2)]), + new_instruction(OP_VA_CALL, [new_arg(100, 2)]), new_instruction(OP_VA_END, []), new_instruction(OP_RET, []), ], diff --git a/src/quest_editor/scripting/instructions.ts b/src/quest_editor/scripting/instructions.ts index 6276bf71..c1fbda37 100644 --- a/src/quest_editor/scripting/instructions.ts +++ b/src/quest_editor/scripting/instructions.ts @@ -1,6 +1,14 @@ import { Kind, Opcode } from "./opcodes"; import { array_buffers_equal, arrays_equal } from "../../core/util"; +/** + * Dimensions of related assembly code. + */ +export type AsmToken = { + readonly col: number; + readonly len: number; +}; + /** * Instruction invocation. */ @@ -19,9 +27,17 @@ export type Instruction = { * Maps each parameter by index to its arguments. */ readonly param_to_args: readonly Arg[][]; + /** + * Dimensions of the opcode's mnemonic in the related asm code. + */ + readonly asm: AsmToken; }; -export function new_instruction(opcode: Opcode, args: Arg[]): Instruction { +export function new_instruction( + opcode: Opcode, + args: Arg[], + asm: AsmToken = { col: 0, len: 0 }, +): Instruction { const len = Math.min(opcode.params.length, args.length); const param_to_args: Arg[][] = []; let arg_size = 0; @@ -55,6 +71,7 @@ export function new_instruction(opcode: Opcode, args: Arg[]): Instruction { arg_size, size: opcode.size + arg_size, param_to_args, + asm, }; } @@ -66,10 +83,19 @@ function instructions_equal(a: Instruction, b: Instruction): boolean { * Instruction argument. */ export type Arg = { - value: any; - size: number; + readonly value: any; + readonly size: number; + readonly asm: AsmToken; }; +export function new_arg(value: any, size: number, asm: AsmToken = { col: 0, len: 0 }): Arg { + return { + value, + size, + asm, + }; +} + function args_equal(a: Arg, b: Arg): boolean { return a.value === b.value && a.size === b.size; } diff --git a/src/quest_editor/stores/quest_creation.ts b/src/quest_editor/stores/quest_creation.ts index 104e33f3..2db11bad 100644 --- a/src/quest_editor/stores/quest_creation.ts +++ b/src/quest_editor/stores/quest_creation.ts @@ -1,6 +1,6 @@ import { Episode } from "../../core/data_formats/parsing/quest/Episode"; import { QuestModel } from "../model/QuestModel"; -import { new_instruction, SegmentType } from "../scripting/instructions"; +import { new_arg, new_instruction, SegmentType } from "../scripting/instructions"; import { ObjectType } from "../../core/data_formats/parsing/quest/object_types"; import { NpcType } from "../../core/data_formats/parsing/quest/npc_types"; import { @@ -38,15 +38,15 @@ export function create_new_quest(episode: Episode): QuestModel { labels: [0], type: SegmentType.Instructions, instructions: [ - new_instruction(OP_SET_EPISODE, [{ value: 0, size: 4 }]), - new_instruction(OP_ARG_PUSHL, [{ value: 0, size: 4 }]), - new_instruction(OP_ARG_PUSHW, [{ value: 150, size: 2 }]), + new_instruction(OP_SET_EPISODE, [new_arg(0, 4)]), + new_instruction(OP_ARG_PUSHL, [new_arg(0, 4)]), + new_instruction(OP_ARG_PUSHW, [new_arg(150, 2)]), new_instruction(OP_SET_FLOOR_HANDLER, []), new_instruction(OP_BB_MAP_DESIGNATE, [ - { value: 0, size: 1 }, - { value: 0, size: 2 }, - { value: 0, size: 1 }, - { value: 0, size: 1 }, + new_arg(0, 1), + new_arg(0, 2), + new_arg(0, 1), + new_arg(0, 1), ]), new_instruction(OP_RET, []), ], @@ -55,33 +55,33 @@ export function create_new_quest(episode: Episode): QuestModel { labels: [150], type: SegmentType.Instructions, instructions: [ - new_instruction(OP_LETI, [{ value: 60, size: 1 }, { value: 237, size: 4 }]), - new_instruction(OP_LETI, [{ value: 61, size: 1 }, { value: 0, size: 4 }]), - new_instruction(OP_LETI, [{ value: 62, size: 1 }, { value: 333, size: 4 }]), - new_instruction(OP_LETI, [{ value: 63, size: 1 }, { value: -15, size: 4 }]), - new_instruction(OP_ARG_PUSHL, [{ value: 0, size: 4 }]), - new_instruction(OP_ARG_PUSHR, [{ value: 60, size: 1 }]), + new_instruction(OP_LETI, [new_arg(60, 1), new_arg(237, 4)]), + new_instruction(OP_LETI, [new_arg(61, 1), new_arg(0, 4)]), + new_instruction(OP_LETI, [new_arg(62, 1), new_arg(333, 4)]), + new_instruction(OP_LETI, [new_arg(63, 1), new_arg(-15, 4)]), + new_instruction(OP_ARG_PUSHL, [new_arg(0, 4)]), + new_instruction(OP_ARG_PUSHR, [new_arg(60, 1)]), new_instruction(OP_P_SETPOS, []), - new_instruction(OP_LETI, [{ value: 60, size: 1 }, { value: 255, size: 4 }]), - new_instruction(OP_LETI, [{ value: 61, size: 1 }, { value: 0, size: 4 }]), - new_instruction(OP_LETI, [{ value: 62, size: 1 }, { value: 338, size: 4 }]), - new_instruction(OP_LETI, [{ value: 63, size: 1 }, { value: -43, size: 4 }]), - new_instruction(OP_ARG_PUSHL, [{ value: 1, size: 4 }]), - new_instruction(OP_ARG_PUSHR, [{ value: 60, size: 1 }]), + new_instruction(OP_LETI, [new_arg(60, 1), new_arg(255, 4)]), + new_instruction(OP_LETI, [new_arg(61, 1), new_arg(0, 4)]), + new_instruction(OP_LETI, [new_arg(62, 1), new_arg(338, 4)]), + new_instruction(OP_LETI, [new_arg(63, 1), new_arg(-43, 4)]), + new_instruction(OP_ARG_PUSHL, [new_arg(1, 4)]), + new_instruction(OP_ARG_PUSHR, [new_arg(60, 1)]), new_instruction(OP_P_SETPOS, []), - new_instruction(OP_LETI, [{ value: 60, size: 1 }, { value: 222, size: 4 }]), - new_instruction(OP_LETI, [{ value: 61, size: 1 }, { value: 0, size: 4 }]), - new_instruction(OP_LETI, [{ value: 62, size: 1 }, { value: 322, size: 4 }]), - new_instruction(OP_LETI, [{ value: 63, size: 1 }, { value: 25, size: 4 }]), - new_instruction(OP_ARG_PUSHL, [{ value: 2, size: 4 }]), - new_instruction(OP_ARG_PUSHR, [{ value: 60, size: 1 }]), + new_instruction(OP_LETI, [new_arg(60, 1), new_arg(222, 4)]), + new_instruction(OP_LETI, [new_arg(61, 1), new_arg(0, 4)]), + new_instruction(OP_LETI, [new_arg(62, 1), new_arg(322, 4)]), + new_instruction(OP_LETI, [new_arg(63, 1), new_arg(25, 4)]), + new_instruction(OP_ARG_PUSHL, [new_arg(2, 4)]), + new_instruction(OP_ARG_PUSHR, [new_arg(60, 1)]), new_instruction(OP_P_SETPOS, []), - new_instruction(OP_LETI, [{ value: 60, size: 1 }, { value: 248, size: 4 }]), - new_instruction(OP_LETI, [{ value: 61, size: 1 }, { value: 0, size: 4 }]), - new_instruction(OP_LETI, [{ value: 62, size: 1 }, { value: 323, size: 4 }]), - new_instruction(OP_LETI, [{ value: 63, size: 1 }, { value: -20, size: 4 }]), - new_instruction(OP_ARG_PUSHL, [{ value: 3, size: 4 }]), - new_instruction(OP_ARG_PUSHR, [{ value: 60, size: 1 }]), + new_instruction(OP_LETI, [new_arg(60, 1), new_arg(248, 4)]), + new_instruction(OP_LETI, [new_arg(61, 1), new_arg(0, 4)]), + new_instruction(OP_LETI, [new_arg(62, 1), new_arg(323, 4)]), + new_instruction(OP_LETI, [new_arg(63, 1), new_arg(-20, 4)]), + new_instruction(OP_ARG_PUSHL, [new_arg(3, 4)]), + new_instruction(OP_ARG_PUSHR, [new_arg(60, 1)]), new_instruction(OP_P_SETPOS, []), new_instruction(OP_RET, []), ],