mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-21 09:09:01 +00:00
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:
parent
59e41c87d6
commit
000f369751
3 changed files with 73 additions and 66 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue