mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-06 05:44:11 +08:00
541 lines
17 KiB
JavaScript
541 lines
17 KiB
JavaScript
|
||
const WAIT_LOCK = 1
|
||
const READ_LOCK = 2
|
||
const WAIT_HOLE = 3
|
||
const READ_HOLE = 4
|
||
const WAIT_KEY = 5
|
||
const READ_KEY = 6
|
||
const WAIT_PART = 7
|
||
const NEED_PART = 8
|
||
|
||
const STATE = ["", "WAIT_LOCK", "READ_LOCK", "WAIT_HOLE", "READ_HOLE", "WAIT_KEY", "READ_KEY"]
|
||
const PASSHOLE = ["text", "off"]
|
||
const space = " ";
|
||
const tab = "\t";
|
||
const hex = "0123456789abcdef";
|
||
const zero = "0".charCodeAt();
|
||
const nine = "9".charCodeAt();
|
||
const a = "a".charCodeAt();
|
||
const f = "f".charCodeAt();
|
||
const conv = new TextEncoder();
|
||
const noiter = process.env.OAFCHT_NO_ITER==='1';
|
||
|
||
/**
|
||
* 这个函数做了一些数据复用处理,能省下来一点文件容量
|
||
*
|
||
* 具体来说,字符串表里面,如果有两个字符串'1'和'11'
|
||
* 那么在字符串表里面,只会有一个索引指向'11',而'1'会
|
||
* 复用'11'的数据,方案是索引的位置放到'11'的最后一个1前面
|
||
*/
|
||
const findIndex = (t, tr, tlen) => {
|
||
const rlen = tr.length, limit = tlen - rlen;
|
||
for (let i = 0; i < limit; ++i) {
|
||
if (t[i + rlen] != 0) continue;
|
||
|
||
let eq = true, j = 0, k = i + j;
|
||
for (; j < rlen; ++j, ++k) {
|
||
if (t[k] != tr[j]) {
|
||
eq = false;
|
||
break;
|
||
}
|
||
}
|
||
if (eq) return i;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
const read = (cht, info, order) => {
|
||
let state = WAIT_LOCK, token = [], line = 1;
|
||
|
||
let locks = [], currlock = null;
|
||
const retval = [];
|
||
|
||
const token_str = () => { const r = token.join(""); return (token = [], r); }
|
||
const error = msg => { throw new SyntaxError(`${msg} at line: ${line} in lock ${currlock?.name}`) }
|
||
const incr = ch => {
|
||
switch (ch) {
|
||
case "[":
|
||
if (state == WAIT_LOCK || state == READ_HOLE || state == NEED_PART) {
|
||
state = READ_LOCK;
|
||
if (currlock) locks.push({ name: currlock.name, keys: currlock.holes });
|
||
currlock = {}
|
||
}
|
||
else if( state == WAIT_PART ) {}
|
||
else error(`error occur [ on ${STATE[state]}`)
|
||
break;
|
||
case "]":
|
||
if (state == READ_LOCK) {
|
||
state = WAIT_HOLE;
|
||
const name = token_str();
|
||
if (name.toLowerCase() == "gameinfo"){
|
||
retval.push( lade(locks, info, order) );
|
||
state = WAIT_PART;
|
||
token = [];
|
||
locks = [];
|
||
currlock = null;
|
||
}
|
||
else {
|
||
currlock.name = name;
|
||
currlock.holes = [];
|
||
}
|
||
}
|
||
else if(state == WAIT_PART) {}
|
||
else if (state == NEED_PART) state = WAIT_PART;
|
||
else error(`error occur ] on ${STATE[state]}`)
|
||
break;
|
||
case "\r": case "\n":
|
||
if (state == WAIT_HOLE) {
|
||
state = READ_HOLE;
|
||
}
|
||
else if (state == READ_KEY) {
|
||
state = READ_HOLE;
|
||
if (token.length) currlock.keys.at(-1).push(token_str());
|
||
if (!PASSHOLE.includes(currlock.hole.toLowerCase())) {
|
||
currlock.holes.push({ name: currlock.hole, cmd: currlock.keys });
|
||
}
|
||
}
|
||
else if (state == READ_LOCK) {
|
||
error(`error occur newline on ${STATE[state]}`);
|
||
}
|
||
else if (state == READ_HOLE) {
|
||
if (token.length > 0 && token.find(c => c != space && c != tab)) {
|
||
error(`error occur newline on ${STATE[state]}`);
|
||
}
|
||
else token.length = 0;
|
||
}
|
||
else if(state == WAIT_PART ) { state = NEED_PART; }
|
||
if (ch == "\n") ++line;
|
||
break;
|
||
case "=":
|
||
if (state == READ_HOLE) {
|
||
state = READ_KEY;
|
||
currlock.hole = token_str();
|
||
currlock.keys = [new Array()];
|
||
}
|
||
else if (state == READ_LOCK) {
|
||
token.push(ch);
|
||
}
|
||
else if (state == WAIT_PART) {}
|
||
else if (state == NEED_PART) state = WAIT_PART;
|
||
else error(`error occur = on ${STATE[state]}`);
|
||
break;
|
||
case ",":
|
||
if (state == READ_KEY) {
|
||
if (token.length) currlock.keys.at(-1).push(token_str());
|
||
}
|
||
else if (state == READ_HOLE) {// MULTILINE KEY
|
||
let { name, cmd } = currlock.holes.pop();
|
||
currlock.hole = name;
|
||
currlock.keys = cmd;
|
||
if (token.length) currlock.keys.at(-1).push(token_str());
|
||
state = READ_KEY;
|
||
}
|
||
else if (state == READ_LOCK) {
|
||
token.push(ch);
|
||
}
|
||
else if (state == WAIT_PART) {}
|
||
else if (state == NEED_PART) state = WAIT_PART;
|
||
else error(`error occur , on ${STATE[state]}`);
|
||
break;
|
||
case ";":
|
||
if (state == READ_KEY) {
|
||
if (token.length) currlock.keys.at(-1).push(token_str());
|
||
currlock.keys.push(new Array());
|
||
}
|
||
else if (state == READ_LOCK) {
|
||
token.push(ch);
|
||
}
|
||
else if (state == READ_HOLE) {// MULTILINE KEY
|
||
let { name, cmd } = currlock.holes.pop();
|
||
currlock.hole = name;
|
||
currlock.keys = cmd;
|
||
if (token.length) currlock.keys.at(-1).push(token_str());
|
||
currlock.keys.push(new Array());
|
||
state = READ_KEY;
|
||
}
|
||
else if (state == WAIT_PART) {}
|
||
else if (state == NEED_PART) state = WAIT_PART;
|
||
else error(`error occur ; on ${STATE[state]}`);
|
||
break;
|
||
case "": // END OF FILE
|
||
if (state == READ_KEY) {
|
||
if (token.length) currlock.keys.at(-1).push(token_str());
|
||
}
|
||
else if (state == READ_HOLE && token.length == 0) {
|
||
// safe
|
||
}
|
||
else if (state == WAIT_PART || state == NEED_PART) {
|
||
// safe
|
||
}
|
||
else error(`error occur eof on ${STATE[state]}`)
|
||
break;
|
||
case " ":
|
||
if (state == READ_LOCK || state == READ_HOLE) {
|
||
token.push(ch);
|
||
}
|
||
else if (state == READ_KEY) {}
|
||
else if (state == WAIT_PART) {}
|
||
else if (state == NEED_PART) state = WAIT_PART;
|
||
break;
|
||
default:
|
||
if (state == READ_LOCK || state == READ_HOLE) {
|
||
token.push(ch);
|
||
}
|
||
else if (state == READ_KEY) {
|
||
if (hex.includes(ch.toLowerCase()) ||
|
||
currlock.hole.toLowerCase() == "text") {
|
||
token.push(ch);
|
||
}
|
||
else error(`error occur ${ch}[${ch.charCodeAt()}] on ${STATE[state]}`);
|
||
}
|
||
else if (state == WAIT_PART) {}
|
||
else if (state == NEED_PART) state = WAIT_PART;
|
||
else error(`error occur ${ch} on ${STATE[state]}`);
|
||
break;
|
||
}
|
||
};
|
||
|
||
/** 注释掉的这个代码是用流的方式处理,但是比不过readFileSync的速度
|
||
return new Promise( ok => {
|
||
cht.on("data", data => {
|
||
const size = data.length;
|
||
for( let i=0; i < size; ++i ) {
|
||
incr( data.charAt(i) );
|
||
}
|
||
});
|
||
cht.on("end", () => {
|
||
incr("");
|
||
ok(retval);
|
||
});
|
||
});*/
|
||
const size = cht.length;
|
||
for(let i=0; i < size; ++i ) // 这样比for(let c of cht)要快!
|
||
incr( cht.charAt(i) );
|
||
incr("");
|
||
return retval;
|
||
}
|
||
|
||
const assembleCheat = (list, dup, hole) => {
|
||
// 建立 地址:数值 的映射关系
|
||
const fromTaddr = taddr => {
|
||
const n = parseInt(taddr, 16);
|
||
if (n == 0 || n > 0x41FFFFF) throw new Error(`get a invalid address: ${taddr}`);
|
||
if (n <= 0x3ffff) return n | 0x2000000;
|
||
else if (0 == (n & 0xf000000)) return (n & 0xffff) | 0x3000000;
|
||
else return n;
|
||
};
|
||
// 避免使用迭代器方法,减少CPU(已走过performance对比流程)
|
||
// ---------------原来的代码在这里------------------
|
||
const addrval = new Map();
|
||
if( noiter === false ){
|
||
list.forEach((command) => {
|
||
let addr = fromTaddr(command[0]);
|
||
let len = command.length - 1;
|
||
for( let pos=0; pos < len; ++pos) {
|
||
addrval.set(addr+pos, parseInt(command[pos+1], 16))
|
||
}
|
||
});
|
||
}
|
||
// ---------------修改的代码在这里------------------
|
||
else {
|
||
for( let i=0; i < list.length; ++i ){
|
||
const command = list[i];
|
||
const addr = fromTaddr(command[0]);
|
||
const len = command.length - 1;
|
||
for( let pos=0; pos < len; ++pos) {
|
||
addrval.set(addr+pos, parseInt(command[pos+1], 16));
|
||
}
|
||
}
|
||
}
|
||
// ---------------修改的代码完结处------------------
|
||
const ordered = Array.from(addrval).sort((a, b) => a[0] - b[0]);
|
||
// 避免使用迭代器方法,减少CPU(已走过performance对比流程)
|
||
// ---------------原来的代码在这里------------------
|
||
if( noiter === false ){
|
||
const blocks = ordered.reduce((arr, [addr, value]) => {
|
||
dup.set(addr, hole);
|
||
if (arr.length == 0) {
|
||
arr.push({ addr, value, count: 1 });// cnt addr value
|
||
}
|
||
else {
|
||
let cur = arr.at(-1);
|
||
if (cur.value == value && cur.addr + cur.count == addr) {
|
||
++cur.count;
|
||
}
|
||
else arr.push({ addr, value, count: 1 });
|
||
}
|
||
return arr;
|
||
}, []);
|
||
return Uint32Array.from(blocks.reduce((r, block) => {
|
||
r.push( block.addr );
|
||
r.push( (block.count << 8) | block.value )
|
||
return r;
|
||
}, []));
|
||
}
|
||
// ---------------修改的代码在这里------------------
|
||
else {
|
||
const blocks = new Array();
|
||
let curr = null;
|
||
for( let i=0; i < ordered.length; ++i ){
|
||
const [addr, value] = ordered[i];
|
||
dup.set(addr, hole);
|
||
if( !curr ) {
|
||
curr = { addr, value, count: 1 };
|
||
}
|
||
else {
|
||
if( curr.value == value && curr.addr + curr.count == addr ) {
|
||
++curr.count;
|
||
}
|
||
else {
|
||
blocks.push( curr.addr, (curr.count << 8) | curr.value );
|
||
curr = { addr, value, count: 1 };
|
||
}
|
||
}
|
||
}
|
||
if( curr ) blocks.push( curr.addr, (curr.count << 8) | curr.value );
|
||
return Uint32Array.from(blocks);
|
||
}
|
||
// ---------------修改的代码完结处------------------
|
||
}
|
||
|
||
const pack = (entlist, strlist, idxlist, cmdlist, xvalue) => {
|
||
const align = (n, base) => base * Math.floor((n + base - 1) / base);
|
||
const entrysize = entlist[0].buffer.byteLength;
|
||
const strbase = 2 + entlist.length * entrysize;
|
||
const idxbase = strbase + strlist.buffer.byteLength;
|
||
const cmdbase = idxbase + idxlist.buffer.byteLength;
|
||
const size = cmdbase + cmdlist.buffer.byteLength;
|
||
let result = new ArrayBuffer(align(size, 32));
|
||
|
||
// entry count
|
||
let view = new DataView(result);
|
||
view.setUint16(0, entlist.length, true);
|
||
|
||
// entry data
|
||
if( noiter === false ){
|
||
entlist.forEach((entry, index) => {
|
||
let offset = 2 + index * entrysize;
|
||
view.setUint32(offset, entry[0], true);
|
||
view.setUint32(offset + 4, entry[1] + (entry[0] > 0xffff ? cmdbase : idxbase), true);
|
||
view.setUint32(offset + 8, entry[2], true);
|
||
});
|
||
}
|
||
else {
|
||
for( let i=0; i < entlist.length; ++i ){
|
||
const entry = entlist[i];
|
||
let offset = 2 + i * entrysize;
|
||
view.setUint32(offset, entry[0], true);
|
||
view.setUint32(offset + 4, entry[1] + (entry[0] > 0xffff ? cmdbase : idxbase), true);
|
||
view.setUint32(offset + 8, entry[2], true);
|
||
}
|
||
}
|
||
|
||
// string table
|
||
let strings = new Uint8Array(result, strbase, strlist.length);
|
||
strings.set(strlist, 0);
|
||
|
||
// indexes table
|
||
let indexes = new Uint8Array(result, idxbase, idxlist.buffer.byteLength);
|
||
indexes.set(new Uint8Array(idxlist.buffer), 0);
|
||
|
||
// instruction table
|
||
let commands = new Uint8Array(result, cmdbase, cmdlist.buffer.byteLength);
|
||
commands.set(new Uint8Array(cmdlist.buffer), 0);
|
||
|
||
return { id: xvalue, bin: new Uint8Array(result) };
|
||
}
|
||
|
||
const lade = (list, info, order) => {
|
||
let enttable = []; // 保存hole/key数据
|
||
let rootstr = [], names = [];
|
||
|
||
const makeID = (a, b) => a | b << 16;
|
||
const makeEntry = (id, str) => ({ id, data: str });
|
||
|
||
// collect
|
||
const collectEntry = noiter === false ? (id, keys, list, colname) => {
|
||
keys.forEach(({ name, cmd }, index) => {
|
||
list[index] = name;
|
||
if( colname ) names.push( name );
|
||
enttable.push(makeEntry(makeID(id, index + 1), cmd));
|
||
});
|
||
} : (id, keys, list, colname) => {
|
||
for( let i=0; i < keys.length; ++i ){
|
||
const { name, cmd } = keys[i];
|
||
list[i] = name;
|
||
if( colname ) names.push( name );
|
||
enttable.push(makeEntry(makeID(id, i + 1), cmd));
|
||
}
|
||
};
|
||
|
||
if( noiter === false ){
|
||
list.forEach((hole, index) => {
|
||
rootstr.push(hole.name);
|
||
names.push(hole.name);
|
||
|
||
let holestr = new Array(hole.keys.length);
|
||
collectEntry(index + 1, hole.keys, holestr, hole.keys.length > 1);
|
||
|
||
let entry = makeEntry(makeID(index + 1, 0), holestr);
|
||
enttable.push(entry);
|
||
});
|
||
}
|
||
else {
|
||
for( let i=0; i < list.length; ++i ){
|
||
const hole = list[i];
|
||
rootstr.push(hole.name);
|
||
names.push(hole.name);
|
||
|
||
let holestr = new Array(hole.keys.length);
|
||
collectEntry(i + 1, hole.keys, holestr, hole.keys.length > 1);
|
||
|
||
let entry = makeEntry(makeID(i + 1, 0), holestr);
|
||
enttable.push(entry);
|
||
}
|
||
}
|
||
enttable.push(makeEntry(makeID(0, 0), rootstr));
|
||
|
||
enttable.sort((a, b) => a.id - b.id);
|
||
names.sort( (a,b) => a.length == b.length ? a.localeCompare(b) : b.length - a.length );
|
||
|
||
let strtable = new Uint8Array(1024); // 保存字符串常量
|
||
let idxtable = new Uint16Array(128); // 从字符串常量索引出来的字符串列表
|
||
let cmdtable = new Uint32Array(2048); // 保存选项对应指令
|
||
|
||
const strOffsetCache = {};
|
||
const size = {"str": 0, "cmd": 0, "idx": 0};
|
||
const addString = list => {
|
||
const ut_pushtr = tr => {
|
||
const off = size.str;
|
||
const len = off + tr.length + 1;
|
||
if (len > strtable.length) {
|
||
let nt = new Uint8Array(Math.min(strtable.length * 2, off + tr.length + 1024));
|
||
nt.set(strtable, 0);
|
||
nt.set(tr, off);
|
||
strtable = nt;
|
||
}
|
||
else {
|
||
strtable.set(tr, off);
|
||
}
|
||
size.str = len;
|
||
return off;
|
||
};
|
||
const ut_addtr = tr => {
|
||
if (strOffsetCache[tr] != undefined) {
|
||
return strOffsetCache[tr];
|
||
}
|
||
else {
|
||
const code = conv.encode(tr);
|
||
let off = findIndex(strtable, code, size.str);
|
||
off = off < 0 ? ut_pushtr(code) : off;
|
||
strOffsetCache[tr] = off;
|
||
return off;
|
||
}
|
||
};
|
||
// 避免使用迭代器方法,减少CPU(已走过performance对比流程)
|
||
// ---------------原来的代码在这里------------------
|
||
if( noiter === false ){
|
||
return list.map(ut_addtr);
|
||
}
|
||
// ---------------修改的代码在这里------------------
|
||
else {
|
||
let res = new Array(list.length);
|
||
for( let i=0; i < list.length; ++i ){
|
||
res[i] = ut_addtr(list[i]);
|
||
}
|
||
return res;
|
||
}
|
||
// ---------------修改的代码完结处------------------
|
||
}
|
||
|
||
const addStrIndex = list => {
|
||
if (list.length == 0) return 0;
|
||
|
||
if (size.str > 0xffff) throw new RangeError(`string table is too big.`);
|
||
|
||
const ret = size.idx;
|
||
const len = ret + list.length;
|
||
if( len > idxtable.length )
|
||
{
|
||
let nt = new Uint16Array( Math.max( len+128, idxtable.length * 2 ) );
|
||
nt.set(idxtable, 0);
|
||
nt.set(list, ret);
|
||
idxtable = nt;
|
||
}
|
||
else {
|
||
idxtable.set( list, ret );
|
||
}
|
||
size.idx = len;
|
||
return ret * 2;
|
||
}
|
||
|
||
const addCommand = list => {
|
||
const ret = size.cmd;
|
||
const len = ret + list.length;
|
||
if (len > cmdtable.length) {
|
||
let n = new Uint32Array(Math.max(cmdtable.length * 8, len));
|
||
n.set(cmdtable, 0);
|
||
n.set(list, ret);
|
||
cmdtable = n;
|
||
}
|
||
else {
|
||
cmdtable.set(list, ret);
|
||
}
|
||
size.cmd = len;
|
||
return ret * 4;
|
||
}
|
||
|
||
if (enttable.length > 0xffff) {
|
||
console.warn(`entry 数量超过上限[file=${order}]`);
|
||
}
|
||
|
||
// pack
|
||
let holedup = new Map();
|
||
// 避免使用迭代器方法,减少CPU(已走过performance对比流程)
|
||
// ---------------原来的代码在这里------------------
|
||
let entbytelist;
|
||
addString(names); //
|
||
if( noiter === false ){
|
||
entbytelist = enttable.map(entry => {
|
||
let { id, data } = entry;
|
||
if (id < 0x10000) { // 收集str
|
||
let idxlist = id == 0 ? addString( data ) : addString(data.length > 1 ? data : []); // length==1就是开启
|
||
let location = addStrIndex(idxlist);
|
||
return Uint32Array.of(id, location, idxlist.length);
|
||
}
|
||
else {
|
||
let armbytecode = assembleCheat(data, holedup, id & 0xffff);
|
||
let location = addCommand(armbytecode);
|
||
return Uint32Array.of(id, location, armbytecode.length);
|
||
}
|
||
});
|
||
}
|
||
// ---------------修改的代码在这里------------------
|
||
else {
|
||
entbytelist = new Array(enttable.length);
|
||
for( let i=0; i < enttable.length; i++ ){
|
||
let { id, data } = enttable[i];
|
||
if (id < 0x10000) {
|
||
let idxlist = id == 0 ? addString( data ) : addString(data.length > 1 ? data : []); // length==1就是开启
|
||
let location = addStrIndex(idxlist);
|
||
entbytelist[i] = Uint32Array.of(id, location, idxlist.length);
|
||
}
|
||
else {
|
||
let armbytecode = assembleCheat(data, holedup, id & 0xffff);
|
||
let location = addCommand(armbytecode);
|
||
entbytelist[i] = Uint32Array.of(id, location, armbytecode.length);
|
||
}
|
||
}
|
||
}
|
||
// ---------------修改的代码完结处------------------
|
||
cmdtable = cmdtable.slice(0, size.cmd);
|
||
strtable = strtable.slice(0, size.str);
|
||
idxtable = idxtable.slice(0, size.idx);
|
||
|
||
let n = info.serial.split("").reduce((r, v) => (v.charCodeAt() | r << 8), 0);
|
||
let xv = cmdtable.reduce((r, v) => r ^ v, n);
|
||
return pack(entbytelist, strtable, idxtable, cmdtable, xv);
|
||
}
|
||
|
||
module.exports = read;
|