541 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;