Only one bug remains after opcode typing changes.

This commit is contained in:
Daan Vanden Bosch 2019-08-04 22:59:32 +02:00
parent 8e5472044b
commit dbe1b06fa6
16 changed files with 548 additions and 349 deletions

View File

@ -98,6 +98,7 @@
"data_label",
"string_label",
"string",
"instruction_label_var",
"reg_ref",
"reg_tup_ref",
"reg_ref_var",

View File

@ -602,7 +602,7 @@ opcodes:
reg_tup:
- type: dword
access: read
- type: instruction_label
- type: instruction_label_var
- code: 0x41
mnemonic: switch_call
@ -611,7 +611,7 @@ opcodes:
reg_tup:
- type: dword
access: read
- type: instruction_label
- type: instruction_label_var
- code: 0x42
mnemonic: stack_push
@ -676,6 +676,23 @@ opcodes:
- type: word
stack: push
- code: 0x4c
mnemonic: arg_pusha
doc: Pushes the memory address of the given register onto the stack. Not used by Sega.
params:
- type: reg_tup_ref
reg_tup:
- type: any
access: read
stack: push
- code: 0x4d
mnemonic: arg_pusho
doc: Pushes the memory address of the given label onto the stack. Not used by Sega.
params:
- type: label
stack: push
- code: 0x4e
mnemonic: arg_pushs
doc: Pushes the given value onto the stack.
@ -2812,14 +2829,8 @@ opcodes:
- code: 0xf8dc
mnemonic: npc_action_string
params:
- type: reg_tup_ref
reg_tup: # TODO: determine type and access
- type: any
access: read
- type: reg_tup_ref
reg_tup: # TODO: determine type and access
- type: any
access: read
- type: dword
- type: dword
- type: string_label
- code: 0xf8dd

View File

@ -144,7 +144,7 @@ function params_to_code(params: any[]) {
switch (param.type) {
case "any":
type = "TYPE";
type = "TYPE_ANY";
break;
case "byte":
type = "TYPE_BYTE";
@ -173,6 +173,9 @@ function params_to_code(params: any[]) {
case "string":
type = "TYPE_STRING";
break;
case "instruction_label_var":
type = "TYPE_I_LABEL_VAR";
break;
case "reg_ref":
type = "TYPE_REG_REF";
break;

View File

@ -26,6 +26,20 @@ export abstract class AbstractWritableCursor extends AbstractCursor implements W
return this;
}
write_i8(value: number): this {
this.ensure_size(1);
this.dv.setInt8(this.position, value);
this._position += 1;
return this;
}
write_i16(value: number): this {
this.ensure_size(2);
this.dv.setInt16(this.position, value, this.little_endian);
this._position += 2;
return this;
}
write_i32(value: number): this {
this.ensure_size(4);
this.dv.setInt32(this.position, value, this.little_endian);

View File

@ -113,6 +113,8 @@ function test_integer_write(method_name: string): void {
test_integer_write("write_u8");
test_integer_write("write_u16");
test_integer_write("write_u32");
test_integer_write("write_i8");
test_integer_write("write_i16");
test_integer_write("write_i32");
/**

View File

@ -22,6 +22,16 @@ export interface WritableCursor extends Cursor {
*/
write_u32(value: number): this;
/**
* Writes a signed 8-bit integer and increments position by 1.
*/
write_i8(value: number): this;
/**
* Writes a signed 16-bit integer and increments position by 2.
*/
write_i16(value: number): this;
/**
* Writes a signed 32-bit integer and increments position by 4.
*/

View File

@ -7,6 +7,7 @@ import {
InstructionSegment,
Segment,
SegmentType,
StringSegment,
} from "../../../scripting/instructions";
import {
Opcode,
@ -48,7 +49,8 @@ export class BinFile {
}
const SEGMENT_PRIORITY: number[] = [];
SEGMENT_PRIORITY[SegmentType.Instructions] = 1;
SEGMENT_PRIORITY[SegmentType.Instructions] = 2;
SEGMENT_PRIORITY[SegmentType.String] = 1;
SEGMENT_PRIORITY[SegmentType.Data] = 0;
export function parse_bin(
@ -300,6 +302,9 @@ function parse_object_code(
case SegmentType.Data:
offset += segment.data.byteLength;
break;
case SegmentType.String:
offset += 2 * segment.value.length + 2;
break;
default:
throw new Error(`${SegmentType[segment!.type]} not implemented.`);
}
@ -386,6 +391,9 @@ function parse_segment(
case SegmentType.Data:
parse_data_segment(offset_to_segment, cursor, end_offset, labels);
break;
case SegmentType.String:
parse_string_segment(offset_to_segment, cursor, end_offset, labels);
break;
default:
throw new Error(`Segment type ${SegmentType[type]} not implemented.`);
}
@ -462,11 +470,11 @@ function parse_instructions_segment(
: instruction.args;
if (opcode.stack === StackInteraction.Push) {
for (const arg of args) {
stack.push(arg);
}
stack.push(...args);
} else {
for (let i = 0; i < params.length && i < args.length; i++) {
const len = Math.min(params.length, args.length);
for (let i = 0; i < len; i++) {
const param_type = params[i].type;
const label = args[i].value;
let segment_type: SegmentType;
@ -474,6 +482,22 @@ function parse_instructions_segment(
switch (param_type) {
case TYPE_I_LABEL:
segment_type = SegmentType.Instructions;
break;
case TYPE_I_LABEL_VAR:
segment_type = SegmentType.Instructions;
// Eat all remaining arguments.
for (; i < args.length; i++) {
parse_segment(
offset_to_segment,
label_holder,
cursor,
args[i].value,
segment_type,
lenient
);
}
break;
case TYPE_D_LABEL:
segment_type = SegmentType.Data;
@ -484,11 +508,6 @@ function parse_instructions_segment(
continue;
}
if (!Number.isInteger(label)) {
logger.error(`Expected label reference but got ${label}.`);
continue;
}
parse_segment(
offset_to_segment,
label_holder,
@ -502,19 +521,19 @@ function parse_instructions_segment(
}
// Recurse on label drop-through.
if (
next_label != undefined &&
instructions.length &&
instructions[instructions.length - 1].opcode !== Opcode.RET
) {
parse_segment(
offset_to_segment,
label_holder,
cursor,
next_label,
SegmentType.Instructions,
lenient
);
if (next_label != undefined && instructions.length) {
const last_opcode = instructions[instructions.length - 1].opcode;
if (last_opcode !== Opcode.RET && last_opcode !== Opcode.JMP) {
parse_segment(
offset_to_segment,
label_holder,
cursor,
next_label,
SegmentType.Instructions,
lenient
);
}
}
}
@ -533,60 +552,81 @@ function parse_data_segment(
offset_to_segment.set(start_offset, segment);
}
function parse_string_segment(
offset_to_segment: Map<number, Segment>,
cursor: Cursor,
end_offset: number,
labels: number[]
) {
const start_offset = cursor.position;
const segment: StringSegment = {
type: SegmentType.String,
labels,
value: cursor.string_utf16(end_offset - start_offset, true, true),
};
offset_to_segment.set(start_offset, segment);
}
function parse_instruction_arguments(cursor: Cursor, opcode: Opcode): Arg[] {
const args: Arg[] = [];
for (const param of opcode.params) {
switch (param.type) {
case TYPE_BYTE:
args.push({ value: cursor.u8(), size: 1 });
break;
case TYPE_WORD:
args.push({ value: cursor.u16(), size: 2 });
break;
case TYPE_DWORD:
args.push({ value: cursor.i32(), size: 4 });
break;
case TYPE_FLOAT:
args.push({ value: cursor.f32(), size: 4 });
break;
case TYPE_LABEL:
case TYPE_I_LABEL:
case TYPE_D_LABEL:
case TYPE_S_LABEL:
args.push({ value: cursor.u16(), size: 2 });
break;
case TYPE_STRING:
{
const start_pos = cursor.position;
args.push({
value: cursor.string_utf16(Math.min(4096, cursor.bytes_left), true, false),
size: cursor.position - start_pos,
});
}
break;
case TYPE_I_LABEL_VAR:
{
const arg_size = cursor.u8();
args.push(...cursor.u16_array(arg_size).map(value => ({ value, size: 2 })));
}
break;
case TYPE_REG_REF:
args.push({ value: cursor.u8(), size: 1 });
break;
case TYPE_REG_REF_VAR:
{
const arg_size = cursor.u8();
args.push(...cursor.u8_array(arg_size).map(value => ({ value, size: 1 })));
}
break;
default:
if (param.type instanceof RegTupRefType) {
if (opcode.stack !== StackInteraction.Pop) {
for (const param of opcode.params) {
switch (param.type) {
case TYPE_BYTE:
args.push({ value: cursor.u8(), size: 1 });
break;
} else {
throw new Error(`Parameter type ${param.type} not implemented.`);
}
case TYPE_WORD:
args.push({ value: cursor.u16(), size: 2 });
break;
case TYPE_DWORD:
args.push({ value: cursor.i32(), size: 4 });
break;
case TYPE_FLOAT:
args.push({ value: cursor.f32(), size: 4 });
break;
case TYPE_LABEL:
case TYPE_I_LABEL:
case TYPE_D_LABEL:
case TYPE_S_LABEL:
args.push({ value: cursor.u16(), size: 2 });
break;
case TYPE_STRING:
{
const start_pos = cursor.position;
args.push({
value: cursor.string_utf16(
Math.min(4096, cursor.bytes_left),
true,
false
),
size: cursor.position - start_pos,
});
}
break;
case TYPE_I_LABEL_VAR:
{
const arg_size = cursor.u8();
args.push(...cursor.u16_array(arg_size).map(value => ({ value, size: 2 })));
}
break;
case TYPE_REG_REF:
args.push({ value: cursor.u8(), size: 1 });
break;
case TYPE_REG_REF_VAR:
{
const arg_size = cursor.u8();
args.push(...cursor.u8_array(arg_size).map(value => ({ value, size: 1 })));
}
break;
default:
if (param.type instanceof RegTupRefType) {
args.push({ value: cursor.u8(), size: 1 });
break;
} else {
throw new Error(`Parameter type ${param.type} not implemented.`);
}
}
}
}
@ -616,59 +656,73 @@ function write_object_code(
cursor.write_u8(opcode.code & 0xff);
for (let i = 0; i < opcode.params.length; i++) {
const param = opcode.params[i];
const args = instruction.param_to_args[i];
const [arg] = args;
if (opcode.stack !== StackInteraction.Pop) {
for (let i = 0; i < opcode.params.length; i++) {
const param = opcode.params[i];
const args = instruction.param_to_args[i];
const [arg] = args;
switch (param.type) {
case TYPE_BYTE:
cursor.write_u8(arg.value);
break;
case TYPE_WORD:
cursor.write_u16(arg.value);
break;
case TYPE_DWORD:
if (arg.value >= 0) {
cursor.write_u32(arg.value);
} else {
cursor.write_i32(arg.value);
}
break;
case TYPE_FLOAT:
cursor.write_f32(arg.value);
break;
case TYPE_LABEL: // Abstract type
case TYPE_I_LABEL:
case TYPE_D_LABEL:
case TYPE_S_LABEL:
cursor.write_u16(arg.value);
break;
case TYPE_STRING:
cursor.write_string_utf16(arg.value, arg.size);
break;
case TYPE_I_LABEL_VAR:
cursor.write_u8(args.length);
cursor.write_u16_array(args.map(arg => arg.value));
break;
case TYPE_REF: // Abstract type
case TYPE_REG_REF:
cursor.write_u8(arg.value);
break;
case TYPE_REG_REF_VAR:
cursor.write_u8(args.length);
cursor.write_u8_array(args.map(arg => arg.value));
break;
default:
if (param.type instanceof RegTupRefType) {
switch (param.type) {
case TYPE_BYTE:
if (arg.value >= 0) {
cursor.write_u8(arg.value);
} else {
cursor.write_i8(arg.value);
}
break;
case TYPE_WORD:
if (arg.value >= 0) {
cursor.write_u16(arg.value);
} else {
cursor.write_i16(arg.value);
}
break;
case TYPE_DWORD:
if (arg.value >= 0) {
cursor.write_u32(arg.value);
} else {
cursor.write_i32(arg.value);
}
break;
case TYPE_FLOAT:
cursor.write_f32(arg.value);
break;
case TYPE_LABEL: // Abstract type
case TYPE_I_LABEL:
case TYPE_D_LABEL:
case TYPE_S_LABEL:
cursor.write_u16(arg.value);
break;
case TYPE_STRING:
cursor.write_string_utf16(arg.value, arg.size);
break;
case TYPE_I_LABEL_VAR:
cursor.write_u8(args.length);
cursor.write_u16_array(args.map(arg => arg.value));
break;
case TYPE_REF: // Abstract type
case TYPE_REG_REF:
cursor.write_u8(arg.value);
} else {
// TYPE_ANY and TYPE_POINTER cannot be serialized.
throw new Error(`Parameter type ${param.type} not implemented.`);
}
break;
case TYPE_REG_REF_VAR:
cursor.write_u8(args.length);
cursor.write_u8_array(args.map(arg => arg.value));
break;
default:
if (param.type instanceof RegTupRefType) {
cursor.write_u8(arg.value);
} else {
// TYPE_ANY and TYPE_POINTER cannot be serialized.
throw new Error(
`Parameter type ${param.type} not implemented.`
);
}
}
}
}
}
} else if (segment.type === SegmentType.String) {
cursor.write_string_utf16(segment.value, 2 * segment.value.length + 2);
} else {
cursor.write_cursor(new ArrayBufferCursor(segment.data, cursor.endianness));
}

View File

@ -9,6 +9,8 @@ import {
QuestNpc,
QuestObject,
} from "../../../domain";
import { Instruction, InstructionSegment, SegmentType } from "../../../scripting/instructions";
import { Opcode } from "../../../scripting/opcodes";
import { area_store } from "../../../stores/AreaStore";
import { prs_compress } from "../../compression/prs/compress";
import { prs_decompress } from "../../compression/prs/decompress";
@ -16,9 +18,8 @@ import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor";
import { Cursor } from "../../cursor/Cursor";
import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
import { Vec3 } from "../../vector";
import { BinFile, Instruction, InstructionSegment, parse_bin, SegmentType, write_bin } from "./bin";
import { BinFile, parse_bin, write_bin } from "./bin";
import { DatFile, DatNpc, DatObject, parse_dat, write_dat } from "./dat";
import { Opcode } from "../../../scripting/opcodes";
import { parse_qst, QstContainedFile, write_qst } from "./qst";
const logger = Logger.get("data_formats/parsing/quest");

View File

@ -1,8 +1,8 @@
import { action, computed, observable } from "mobx";
import { Segment } from "../data_formats/parsing/quest/bin";
import { DatUnknown } from "../data_formats/parsing/quest/dat";
import { Vec3 } from "../data_formats/vector";
import { enum_values } from "../enums";
import { Segment } from "../scripting/instructions";
import { ItemType } from "./items";
import { NpcType } from "./NpcType";
import { ObjectType } from "./ObjectType";

View File

@ -1,10 +1,10 @@
import { observable } from "mobx";
import { editor } from "monaco-editor";
import AssemblyWorker from "worker-loader!./assembly_worker";
import { Segment } from "../data_formats/parsing/quest/bin";
import { AssemblyChangeInput, NewAssemblyInput, ScriptWorkerOutput } from "./assembler_messages";
import { AssemblyError } from "./assembly";
import { disassemble } from "./disassembly";
import { Segment } from "./instructions";
export class AssemblyAnalyser {
@observable errors: AssemblyError[] = [];

View File

@ -6,6 +6,7 @@ export enum TokenType {
Label,
CodeSection,
DataSection,
StringSection,
InvalidSection,
String,
UnterminatedString,
@ -22,6 +23,7 @@ export type Token =
| LabelToken
| CodeSectionToken
| DataSectionToken
| StringSectionToken
| InvalidSectionToken
| StringToken
| UnterminatedStringToken
@ -75,6 +77,12 @@ export type DataSectionToken = {
len: number;
};
export type StringSectionToken = {
type: TokenType.StringSection;
col: number;
len: number;
};
export type InvalidSectionToken = {
type: TokenType.InvalidSection;
col: number;
@ -293,7 +301,11 @@ export class AssemblyLexer {
}
}
private tokenize_section(): CodeSectionToken | DataSectionToken | InvalidSectionToken {
private tokenize_section():
| CodeSectionToken
| DataSectionToken
| StringSectionToken
| InvalidSectionToken {
const col = this.col;
this.mark();
@ -310,6 +322,8 @@ export class AssemblyLexer {
return { type: TokenType.CodeSection, col, len: 5 };
case ".data":
return { type: TokenType.DataSection, col, len: 5 };
case ".string":
return { type: TokenType.DataSection, col, len: 7 };
default:
return { type: TokenType.InvalidSection, col, len: this.marked_len() };
}

View File

@ -9,6 +9,8 @@ import {
RegisterToken,
Token,
TokenType,
StringSectionToken,
StringToken,
} from "./AssemblyLexer";
import {
Segment,
@ -17,8 +19,27 @@ import {
SegmentType,
Instruction,
DataSegment,
StringSegment,
} from "./instructions";
import { Opcode, OPCODES_BY_MNEMONIC, Param } from "./opcodes";
import {
Opcode,
OPCODES_BY_MNEMONIC,
Param,
TYPE_I_LABEL_VAR,
TYPE_REG_REF_VAR,
StackInteraction,
TYPE_BYTE,
TYPE_DWORD,
TYPE_WORD,
TYPE_FLOAT,
TYPE_S_LABEL,
TYPE_D_LABEL,
TYPE_I_LABEL,
TYPE_LABEL,
TYPE_STRING,
TYPE_REG_REF,
RegTupRefType,
} from "./opcodes";
const logger = Logger.get("scripting/assembly");
@ -53,8 +74,7 @@ class Assembler {
private errors!: AssemblyError[];
// Encountered labels.
private labels!: Set<number>;
// True iff we're in a code section, false iff we're in a data section.
private code_section = true;
private section_type: SegmentType = SegmentType.Instructions;
constructor(private assembly: string[], private manual_stack: boolean) {}
@ -68,7 +88,8 @@ class Assembler {
this.warnings = [];
this.errors = [];
this.labels = new Set();
this.code_section = true;
// Need to cast SegmentType.Instructions because of TypeScript bug.
this.section_type = SegmentType.Instructions as SegmentType;
for (const line of this.assembly) {
this.tokens = this.lexer.tokenize_line(line);
@ -81,24 +102,34 @@ class Assembler {
this.parse_label(token);
break;
case TokenType.CodeSection:
this.parse_code_section(token);
break;
case TokenType.DataSection:
this.parse_data_section(token);
case TokenType.StringSection:
this.parse_section(token);
break;
case TokenType.Int:
if (this.code_section) {
if (this.section_type === SegmentType.Data) {
this.parse_bytes(token);
} else {
this.add_error({
col: token.col,
length: token.len,
message: "Unexpected token.",
});
}
break;
case TokenType.String:
if (this.section_type === SegmentType.String) {
this.parse_string(token);
} else {
this.parse_bytes(token);
this.add_error({
col: token.col,
length: token.len,
message: "Unexpected token.",
});
}
break;
case TokenType.Ident:
if (this.code_section) {
if (this.section_type === SegmentType.Instructions) {
this.parse_instruction(token);
} else {
this.add_error({
@ -181,6 +212,23 @@ class Assembler {
}
}
private add_string(str: string): void {
if (!this.segment) {
// Unadressable data, technically valid.
const string_segment: StringSegment = {
labels: [],
type: SegmentType.String,
value: str,
};
this.segment = string_segment;
this.object_code.push(string_segment);
} else {
const s_seg = this.segment as StringSegment;
s_seg.value += str;
}
}
private add_error({
col,
length,
@ -228,79 +276,101 @@ class Assembler {
const next_token = this.tokens.shift();
if (this.code_section) {
this.segment = {
type: SegmentType.Instructions,
labels: [label],
instructions: [],
};
this.object_code.push(this.segment);
switch (this.section_type) {
case SegmentType.Instructions:
this.segment = {
type: SegmentType.Instructions,
labels: [label],
instructions: [],
};
this.object_code.push(this.segment);
if (next_token) {
if (next_token.type === TokenType.Ident) {
this.parse_instruction(next_token);
} else {
this.add_error({
col: next_token.col,
length: next_token.len,
message: "Expected opcode mnemonic.",
});
if (next_token) {
if (next_token.type === TokenType.Ident) {
this.parse_instruction(next_token);
} else {
this.add_error({
col: next_token.col,
length: next_token.len,
message: "Expected opcode mnemonic.",
});
}
}
}
} else {
this.segment = {
type: SegmentType.Data,
labels: [label],
data: new ArrayBuffer(0),
};
this.object_code.push(this.segment);
if (next_token) {
if (next_token.type === TokenType.Int) {
this.parse_bytes(next_token);
} else {
this.add_error({
col: next_token.col,
length: next_token.len,
message: "Expected bytes.",
});
break;
case SegmentType.Data:
this.segment = {
type: SegmentType.Data,
labels: [label],
data: new ArrayBuffer(0),
};
this.object_code.push(this.segment);
if (next_token) {
if (next_token.type === TokenType.Int) {
this.parse_bytes(next_token);
} else {
this.add_error({
col: next_token.col,
length: next_token.len,
message: "Expected bytes.",
});
}
}
}
break;
case SegmentType.String:
this.segment = {
type: SegmentType.String,
labels: [label],
value: "",
};
this.object_code.push(this.segment);
if (next_token) {
if (next_token.type === TokenType.String) {
this.parse_string(next_token);
} else {
this.add_error({
col: next_token.col,
length: next_token.len,
message: "Expected a string.",
});
}
}
break;
}
}
private parse_code_section({ col, len }: CodeSectionToken): void {
if (this.code_section) {
private parse_section({
type,
col,
len,
}: CodeSectionToken | DataSectionToken | StringSectionToken): void {
let section_type!: SegmentType;
switch (type) {
case TokenType.CodeSection:
section_type = SegmentType.Instructions;
break;
case TokenType.DataSection:
section_type = SegmentType.Data;
break;
case TokenType.StringSection:
section_type = SegmentType.String;
break;
}
if (this.section_type === section_type) {
this.add_warning({
col,
length: len,
message: "Unnecessary code section marker.",
message: "Unnecessary section marker.",
});
}
this.code_section = true;
const next_token = this.tokens.shift();
if (next_token) {
this.add_error({
col: next_token.col,
length: next_token.len,
message: "Unexpected token.",
});
}
}
private parse_data_section({ col, len }: DataSectionToken): void {
if (!this.code_section) {
this.add_warning({
col,
length: len,
message: "Unnecessary data section marker.",
});
}
this.code_section = false;
this.section_type = section_type;
const next_token = this.tokens.shift();
@ -324,11 +394,14 @@ class Assembler {
});
} else {
const varargs =
opcode.params.findIndex(p => p.type === Type.U8Var || p.type === Type.ILabelVar) !==
-1;
opcode.params.findIndex(
p => p.type === TYPE_I_LABEL_VAR || p.type === TYPE_REG_REF_VAR
) !== -1;
const param_count =
opcode.params.length + (this.manual_stack ? 0 : opcode.stack_params.length);
this.manual_stack && opcode.stack === StackInteraction.Pop
? 0
: opcode.params.length;
let arg_count = 0;
@ -362,7 +435,7 @@ class Assembler {
});
return;
} else if (varargs || arg_count === opcode.params.length) {
} else if (opcode.stack !== StackInteraction.Pop) {
// Inline arguments.
if (!this.parse_args(opcode.params, ins_args)) {
return;
@ -371,12 +444,12 @@ class Assembler {
// Stack arguments.
const stack_args: Arg[] = [];
if (!this.parse_args(opcode.stack_params, stack_args)) {
if (!this.parse_args(opcode.params, stack_args)) {
return;
}
for (let i = 0; i < opcode.stack_params.length; i++) {
const param = opcode.stack_params[i];
for (let i = 0; i < opcode.params.length; i++) {
const param = opcode.params[i];
const arg = stack_args[i];
if (arg == undefined) {
@ -384,25 +457,32 @@ class Assembler {
}
switch (param.type) {
case Type.U8:
case Type.RegRef:
this.add_instruction(Opcode.arg_pushb, [arg]);
case TYPE_BYTE:
case TYPE_REG_REF:
this.add_instruction(Opcode.ARG_PUSHB, [arg]);
break;
case Type.U16:
case Type.ILabel:
case Type.DLabel:
this.add_instruction(Opcode.arg_pushw, [arg]);
case TYPE_WORD:
case TYPE_LABEL:
case TYPE_I_LABEL:
case TYPE_D_LABEL:
case TYPE_S_LABEL:
this.add_instruction(Opcode.ARG_PUSHW, [arg]);
break;
case Type.U32:
case Type.I32:
case Type.F32:
this.add_instruction(Opcode.arg_pushl, [arg]);
case TYPE_DWORD:
case TYPE_FLOAT:
this.add_instruction(Opcode.ARG_PUSHL, [arg]);
break;
case Type.String:
this.add_instruction(Opcode.arg_pushs, [arg]);
case TYPE_STRING:
this.add_instruction(Opcode.ARG_PUSHS, [arg]);
break;
default:
logger.error(`Type ${Type[param.type]} not implemented.`);
if (param.type instanceof RegTupRefType) {
this.add_instruction(Opcode.ARG_PUSHB, [arg]);
} else {
logger.error(`Type ${param.type} not implemented.`);
}
break;
}
}
}
@ -431,7 +511,7 @@ class Assembler {
message: "Expected an argument.",
});
} else {
if (param.type !== Type.U8Var && param.type !== Type.ILabelVar) {
if (param.type !== TYPE_I_LABEL_VAR && param.type !== TYPE_REG_REF_VAR) {
param_i++;
}
}
@ -456,27 +536,24 @@ class Assembler {
switch (token.type) {
case TokenType.Int:
switch (param.type) {
case Type.U8:
case Type.U8Var:
case TYPE_BYTE:
match = true;
this.parse_uint(1, token, args);
this.parse_int(1, token, args);
break;
case Type.U16:
case Type.ILabel:
case Type.ILabelVar:
case Type.DLabel:
case TYPE_WORD:
case TYPE_LABEL:
case TYPE_I_LABEL:
case TYPE_D_LABEL:
case TYPE_S_LABEL:
case TYPE_I_LABEL_VAR:
match = true;
this.parse_uint(2, token, args);
this.parse_int(2, token, args);
break;
case Type.U32:
case TYPE_DWORD:
match = true;
this.parse_uint(4, token, args);
this.parse_int(4, token, args);
break;
case Type.I32:
match = true;
this.parse_sint(4, token, args);
break;
case Type.F32:
case TYPE_FLOAT:
match = true;
args.push({
value: token.value,
@ -489,7 +566,7 @@ class Assembler {
}
break;
case TokenType.Float:
match = param.type === Type.F32;
match = param.type === TYPE_FLOAT;
if (match) {
args.push({
@ -500,11 +577,15 @@ class Assembler {
break;
case TokenType.Register:
match = param.type === Type.RegRef;
match =
param.type === TYPE_REG_REF ||
param.type === TYPE_REG_REF_VAR ||
param.type instanceof RegTupRefType;
this.parse_register(token, args);
break;
case TokenType.String:
match = param.type === Type.String;
match = param.type === TYPE_STRING;
if (match) {
args.push({
@ -522,49 +603,61 @@ class Assembler {
if (!match) {
semi_valid = false;
let type_str = Type[param.type];
let type_str: string | undefined;
switch (param.type) {
case Type.U8:
type_str = "an unsigned 8-bit integer";
case TYPE_BYTE:
type_str = "a 8-bit integer";
break;
case Type.U16:
type_str = "an unsigned 16-bit integer";
case TYPE_WORD:
type_str = "a 16-bit integer";
break;
case Type.U32:
type_str = "an unsigned 32-bit integer";
case TYPE_DWORD:
type_str = "a 32-bit integer";
break;
case Type.I32:
type_str = "a signed 32-bit integer";
break;
case Type.F32:
case TYPE_FLOAT:
type_str = "a float";
break;
case Type.RegRef:
type_str = "a register reference";
case TYPE_LABEL:
type_str = "a label";
break;
case Type.ILabel:
case TYPE_I_LABEL:
case TYPE_I_LABEL_VAR:
type_str = "an instruction label";
break;
case Type.DLabel:
case TYPE_D_LABEL:
type_str = "a data label";
break;
case Type.U8Var:
type_str = "an unsigned 8-bit integer";
case TYPE_S_LABEL:
type_str = "a string label";
break;
case Type.ILabelVar:
type_str = "an instruction label";
break;
case Type.String:
case TYPE_STRING:
type_str = "a string";
break;
case TYPE_REG_REF:
case TYPE_REG_REF_VAR:
type_str = "a register reference";
break;
default:
if (param.type instanceof RegTupRefType) {
type_str = "a register reference";
}
break;
}
this.add_error({
col: token.col,
length: token.len,
message: `Expected ${type_str}.`,
});
if (type_str) {
this.add_error({
col: token.col,
length: token.len,
message: `Expected ${type_str}.`,
});
} else {
this.add_error({
col: token.col,
length: token.len,
message: `Unexpected token.`,
});
}
}
}
}
@ -573,46 +666,22 @@ class Assembler {
return semi_valid;
}
private parse_uint(size: number, { col, len, value }: IntToken, args: Arg[]): void {
const bit_size = 8 * size;
const max_value = Math.pow(2, bit_size) - 1;
if (value < 0) {
this.add_error({
col,
length: len,
message: `Unsigned ${bit_size}-bit integer can't be less than 0.`,
});
} else if (value > max_value) {
this.add_error({
col,
length: len,
message: `Unsigned ${bit_size}-bit integer can't be greater than ${max_value}.`,
});
} else {
args.push({
value,
size,
});
}
}
private parse_sint(size: number, { col, len, value }: IntToken, args: Arg[]): void {
private parse_int(size: number, { col, len, value }: IntToken, args: Arg[]): void {
const bit_size = 8 * size;
const min_value = -Math.pow(2, bit_size - 1);
const max_value = Math.pow(2, bit_size - 1) - 1;
const max_value = Math.pow(2, bit_size) - 1;
if (value < min_value) {
this.add_error({
col,
length: len,
message: `Signed ${bit_size}-bit integer can't be less than ${min_value}.`,
message: `${bit_size}-Bit integer can't be less than ${min_value}.`,
});
} else if (value > max_value) {
this.add_error({
col,
length: len,
message: `Signed ${bit_size}-bit integer can't be greater than ${max_value}.`,
message: `${bit_size}-Bit integer can't be greater than ${max_value}.`,
});
} else {
args.push({
@ -676,4 +745,18 @@ class Assembler {
this.add_bytes(bytes);
}
private parse_string(token: StringToken): void {
const next_token = this.tokens.shift();
if (next_token) {
this.add_error({
col: next_token.col,
length: next_token.len,
message: "Unexpected token.",
});
}
this.add_string(token.value);
}
}

View File

@ -15,35 +15,40 @@ import {
export function disassemble(object_code: Segment[], manual_stack: boolean = false): string[] {
const lines: string[] = [];
const stack: Arg[] = [];
let code_block: boolean | undefined;
let section_type: SegmentType | undefined;
for (const segment of object_code) {
if (segment.type === SegmentType.Data) {
if (code_block !== false) {
code_block = false;
// Section marker.
let section_marker!: string;
if (lines.length) {
lines.push("");
}
lines.push(".data", "");
}
} else {
if (code_block !== true) {
code_block = true;
if (lines.length) {
lines.push("");
}
lines.push(".code", "");
}
switch (segment.type) {
case SegmentType.Instructions:
section_marker = ".code";
break;
case SegmentType.Data:
section_marker = ".data";
break;
case SegmentType.String:
section_marker = ".string";
break;
}
if (section_type !== segment.type) {
section_type = segment.type;
if (lines.length) {
lines.push("");
}
lines.push(section_marker, "");
}
// Labels.
for (const label of segment.labels) {
lines.push(`${label}:`);
}
// Code or data lines.
if (segment.type === SegmentType.Data) {
const bytes = new Uint8Array(segment.data);
let line = " ";
@ -63,7 +68,7 @@ export function disassemble(object_code: Segment[], manual_stack: boolean = fals
lines.push(line);
}
} else if (segment.type === SegmentType.String) {
lines.push(" " + segment.value);
lines.push(" " + JSON.stringify(segment.value));
} else {
for (const instruction of segment.instructions) {
if (!manual_stack && instruction.opcode.stack === StackInteraction.Push) {

View File

@ -18,15 +18,13 @@ export class Instruction {
readonly param_to_args: Arg[][] = [];
constructor(readonly opcode: Opcode, readonly args: Arg[]) {
for (let i = 0; i < opcode.params.length; i++) {
const len = Math.min(opcode.params.length, args.length);
for (let i = 0; i < len; i++) {
const type = opcode.params[i].type;
const arg = args[i];
this.param_to_args[i] = [];
if (arg == undefined) {
break;
}
switch (type) {
case TYPE_I_LABEL_VAR:
case TYPE_REG_REF_VAR:

View File

@ -1078,7 +1078,7 @@ export class Opcode {
undefined,
undefined
),
new Param(TYPE_I_LABEL, undefined, undefined),
new Param(TYPE_I_LABEL_VAR, undefined, undefined),
],
undefined
));
@ -1092,7 +1092,7 @@ export class Opcode {
undefined,
undefined
),
new Param(TYPE_I_LABEL, undefined, undefined),
new Param(TYPE_I_LABEL_VAR, undefined, undefined),
],
undefined
));
@ -1190,19 +1190,25 @@ export class Opcode {
[new Param(TYPE_WORD, undefined, undefined)],
StackInteraction.Push
));
static readonly UNKNOWN_4C = (OPCODES[0x4c] = new Opcode(
static readonly ARG_PUSHA = (OPCODES[0x4c] = new Opcode(
0x4c,
"unknown_4c",
undefined,
[],
undefined
"arg_pusha",
"Pushes the memory address of the given register onto the stack. Not used by Sega.",
[
new Param(
new RegTupRefType(new Param(TYPE_ANY, undefined, ParamAccess.Read)),
undefined,
undefined
),
],
StackInteraction.Push
));
static readonly UNKNOWN_4D = (OPCODES[0x4d] = new Opcode(
static readonly ARG_PUSHO = (OPCODES[0x4d] = new Opcode(
0x4d,
"unknown_4d",
undefined,
[],
undefined
"arg_pusho",
"Pushes the memory address of the given label onto the stack. Not used by Sega.",
[new Param(TYPE_LABEL, undefined, undefined)],
StackInteraction.Push
));
static readonly ARG_PUSHS = (OPCODES[0x4e] = new Opcode(
0x4e,
@ -5043,16 +5049,8 @@ export class Opcode {
"npc_action_string",
undefined,
[
new Param(
new RegTupRefType(new Param(TYPE_ANY, undefined, ParamAccess.Read)),
undefined,
undefined
),
new Param(
new RegTupRefType(new Param(TYPE_ANY, undefined, ParamAccess.Read)),
undefined,
undefined
),
new Param(TYPE_DWORD, undefined, undefined),
new Param(TYPE_DWORD, undefined, undefined),
new Param(TYPE_S_LABEL, undefined, undefined),
],
undefined

View File

@ -2,8 +2,8 @@ import { autorun } from "mobx";
import { editor, languages, MarkerSeverity } from "monaco-editor";
import React, { Component, createRef, ReactNode } from "react";
import { AutoSizer } from "react-virtualized";
import { OPCODES } from "../../data_formats/parsing/quest/bin";
import { AssemblyAnalyser } from "../../scripting/AssemblyAnalyser";
import { OPCODES } from "../../scripting/opcodes";
import { quest_editor_store } from "../../stores/QuestEditorStore";
import { Action } from "../../undo";
import styles from "./AssemblyEditorComponent.css";
@ -77,6 +77,11 @@ const KEYWORD_SUGGESTIONS = [
kind: languages.CompletionItemKind.Keyword,
insertText: "data",
},
{
label: ".string",
kind: languages.CompletionItemKind.Keyword,
insertText: "string",
},
] as languages.CompletionItem[];
languages.register({ id: "psoasm" });