Correctly parse command opcodes

This moves to using a node Buffer to hold the command bytecode which resolves a character encoding issue we were having
This commit is contained in:
Jordan Eldredge 2019-06-15 17:24:33 -07:00
parent 59e41c87d6
commit 000f369751
3 changed files with 73 additions and 66 deletions

View file

@ -8,40 +8,26 @@ class Variable {
}
}
function decodeUInt32(code) {
// prettier-ignore
return (
(code.charCodeAt(3) << 24) +
(code.charCodeAt(2) << 16) +
(code.charCodeAt(1) << 8) +
code.charCodeAt(0)
);
}
function decodeInt32(code) {
let num = decodeUInt32(code);
if (num > 0x7fffffff) {
num = num - 0xffffffff - 1;
}
return num;
}
class Command {
constructor({
functionsCode,
commandsBuffer,
pos,
types,
variables,
functionNames,
localFunctions
}) {
const opcode = functionsCode.charCodeAt(pos);
const opcode = commandsBuffer.readInt8(pos);
this.pos = pos;
this.opcode = opcode;
this.arguments = [];
this.command = COMMANDS[opcode];
this.size = 1;
if (this.command == null) {
// I don't think this ever happens in a real .maki file
// throw new Error(`Unknown opcode "${opcode}"`);
// But the decompiler tries to do something...
this.command = {
name: `unknown ${opcode}`,
in: 0,
@ -59,22 +45,22 @@ class Command {
let arg = null;
switch (argType) {
case "var": {
const variable = decodeInt32(functionsCode.substr(pos + 1, 4));
const variable = commandsBuffer.readUInt32LE(pos + 1);
if (variables[variable] != null) {
arg = variables[variable];
} else {
arg = new Variable(-1, -0, "unknown");
}
this.name = `Unknown ${variable}`;
arg.name = `Unknown ${variable}`;
break;
}
case "line": {
const variable = decodeInt32(functionsCode.substr(pos + 1, 4)) + 5;
const variable = commandsBuffer.readUInt32LE(pos + 1) + 5;
arg = { name: variable, line: variable };
break;
}
case "objFunc": {
const variable = decodeInt32(functionsCode.substr(pos + 1, 4));
const variable = commandsBuffer.readUInt32LE(pos + 1);
if (functionNames[variable] != null) {
arg = functionNames[variable].function;
} else {
@ -88,28 +74,23 @@ class Command {
}
case "func": {
// Note in the perl code here: "todo, something strange going on here..."
const variable = decodeInt32(functionsCode.substr(pos + 1, 4)) + 5;
const s = functionsCode.substr(pos + 1, 1);
console.log(functionsCode.length);
console.log(s.charCodeAt(0));
const variable = commandsBuffer.readUInt32LE(pos + 1) + 5;
const offset = variable + pos;
arg = {
name: `func${variable + pos}`,
name: `func${offset}`,
code: [],
offset: variable + pos
offset
};
if (opcode === 25) {
// console.log(variable);
}
if (localFunctions[variable + pos] == null) {
localFunctions[variable + pos] = {
if (localFunctions[offset] == null) {
localFunctions[offset] = {
function: arg,
offset: variable + pos
offset
};
}
break;
}
case "obj": {
const variable = decodeInt32(functionsCode.substr(pos + 1, 4));
const variable = commandsBuffer.readUInt32LE(pos + 1);
arg = types[variable];
break;
}
@ -118,14 +99,14 @@ class Command {
this.arguments = [arg];
this.size = 5;
/*
# look forward for a stack protection block
# (why do I have to look FORWARD. stupid nullsoft)
if( length($code) > $pos+5+4 &&
decodeUInt32( substr( $code, $pos+5, 4 ) ) >= 0xffff0000 ) {
$self->{size} += 4;
// From perl: look forward for a stack protection block
// (why do I have to look FORWARD. stupid nullsoft)
if (
commandsBuffer.length > pos + 5 + 4 &&
commandsBuffer.readUInt32LE(pos + 5) >= 0xffff0000
) {
this.size += 4;
}
*/
if (opcode === 112) {
this.size += 1;

View file

@ -1,7 +1,7 @@
const { COMMANDS } = require("./constants");
const Command = require("./command");
const MAGIC = "FG";
const ENCODING = "ascii";
const ENCODING = "binary";
class Parser {
_readMagic() {
@ -125,11 +125,6 @@ class Parser {
}
}
_readFunctionsCode() {
const code = this._readStringOfLength(this._readUInt32LE());
return code;
}
_readUInt32LE() {
const int = this._buffer.readUInt32LE(this._i);
this._i += 4;
@ -158,15 +153,17 @@ class Parser {
return this._readStringOfLength(this._readUInt16LE());
}
_decodeCode({ functionsCode, types, variables, functionNames, functions }) {
_decodeCode({ types, variables, functionNames, functions }) {
const length = this._readUInt32LE();
const commandsBuffer = this._buffer.slice(this._i, this._i + length);
this._i += length;
let pos = 0;
const localFunctions = {};
const results = [];
const poss = [];
while (pos < functionsCode.length) {
poss.push(pos);
while (pos < commandsBuffer.length) {
const command = new Command({
functionsCode,
commandsBuffer,
pos,
types,
variables,
@ -201,22 +198,18 @@ class Parser {
const variables = this._readVariables();
const constants = this._readConstants();
const functions = this._readFunctions();
const functionsCode = this._readFunctionsCode();
const decoding = this._decodeCode({
functionsCode,
types,
variables,
functionNames,
functions
});
this._readCommands(functionsCode);
return {
magic,
types,
functionNames,
variables,
constants,
functionsCode,
functions,
decoding
};

View file

@ -67,15 +67,48 @@ describe("standardframe.maki", () => {
]);
});
test("can read function code", () => {
expect(maki.functionsCode.length).toBe(1004);
});
test("can read function code", () => {
expect(maki.functionsCode.length).toBe(1004);
});
// [opcode, size] as output by the Perl decompiler
// prettier-ignore
const expectedCommands = [
[1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5],
[1, 5], [1, 5], [1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5], [1, 5], [1, 5],
[1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5], [1, 5], [1, 5], [1, 5], [24, 5], [48, 1],
[2, 1], [1, 5], [1, 5], [1, 5], [1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5], [1, 5],
[1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5], [1, 5], [1, 5], [1, 5], [24, 5], [48, 1],
[2, 1], [1, 5], [1, 5], [1, 5], [1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5], [1, 5],
[1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [33, 1], [3, 5], [3, 5], [1, 5], [1, 5], [8, 1],
[16, 5], [1, 5], [25, 5], [2, 1], [1, 5], [1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5],
[9, 1], [16, 5], [1, 5], [1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5], [8, 1], [1, 5], [1, 5],
[8, 1], [81, 1], [16, 5], [1, 5], [1, 5], [9, 1], [16, 5], [1, 5], [1, 5], [1, 5], [24, 5], [2, 1],
[1, 5], [1, 5], [8, 1], [16, 5], [1, 5], [1, 5], [9, 1], [16, 5], [1, 5], [1, 5], [1, 5], [64, 1], [1, 5],
[24, 5], [2, 1], [18, 5], [1, 5], [1, 5], [1, 5], [1, 5], [1, 5], [24, 5], [2, 1], [1, 5], [33, 1],
[3, 5], [3, 5], [3, 5], [3, 5], [1, 5], [1, 5], [1, 5], [1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5],
[1, 5], [1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5], [8, 1], [1, 5], [1, 5], [8, 1], [81, 1], [1, 5],
[1, 5], [8, 1], [81, 1], [1, 5], [1, 5], [8, 1], [81, 1], [16, 5], [1, 5], [1, 5], [1, 5], [24, 5], [2, 1], [1, 5],
[33, 1], [3, 5], [1, 5], [1, 5], [1, 5], [24, 5], [48, 1], [2, 1], [1, 5], [1, 5], [8, 1], [16, 5], [1, 5], [1, 5],
[1, 5], [1, 5], [1, 5], [1, 5], [64, 1], [1, 5], [64, 1], [24, 5], [2, 1], [1, 5], [33, 1], [1, 5], [1, 5], [1, 5],
[24, 5], [2, 1], [1, 5], [1, 5], [1, 5], [24, 5], [2, 1], [1, 5], [1, 5], [1, 5], [24, 5], [2, 1], [1, 5], [1, 5], [1, 5],
[24, 5], [2, 1], [1, 5], [1, 5], [1, 5], [24, 5], [2, 1], [1, 5], [1, 5], [1, 5], [24, 5], [2, 1], [1, 5],
[1, 5], [1, 5], [24, 5], [2, 1], [1, 5], [1, 5], [1, 5], [24, 5], [2, 1], [1, 5], [1, 5], [24, 5], [2, 1], [1, 5],
[33, 1]
];
test("can read decoding", () => {
maki.decoding.forEach((command, i) => {
const [expectedOpcode, expectedSize] = expectedCommands[i];
if (expectedOpcode !== command.opcode) {
throw new Error(
`Command ${i} reported opcode ${
command.opcode
}. Expected ${expectedOpcode}`
);
}
if (expectedSize !== command.size) {
throw new Error(
`Command ${i} reported size ${command.size}. Expected ${expectedSize}`
);
}
});
expect(maki.decoding.length).toBe(256);
});
});