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; currlock.name = token_str(); currlock.holes = []; if (currlock.name.toLowerCase() == "gameinfo"){ retval.push( lade(locks, info, order) ); state = WAIT_PART; token = []; locks = []; currlock = null; } } 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) => b.length - a.length ); let strtable = new Uint8Array(1024); // 保存字符串常量 let idxtable = new Uint16Array(128); // 从字符串常量索引出来的字符串列表 let cmdtable = new Uint32Array(4096); // 保存选项对应指令 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;