diff --git a/experiments/maki-interpreter/command.js b/experiments/maki-interpreter/command.js index a0e7aa34..8479096b 100644 --- a/experiments/maki-interpreter/command.js +++ b/experiments/maki-interpreter/command.js @@ -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; diff --git a/experiments/maki-interpreter/index.js b/experiments/maki-interpreter/index.js index ec249f2f..b712d0d1 100644 --- a/experiments/maki-interpreter/index.js +++ b/experiments/maki-interpreter/index.js @@ -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 }; diff --git a/experiments/maki-interpreter/index.test.js b/experiments/maki-interpreter/index.test.js index 762bba3e..9a14a1f4 100644 --- a/experiments/maki-interpreter/index.test.js +++ b/experiments/maki-interpreter/index.test.js @@ -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); }); });