diff --git a/tools/cheat-builder/build.js b/tools/cheat-builder/build.js index fdcbb24..bd42276 100644 --- a/tools/cheat-builder/build.js +++ b/tools/cheat-builder/build.js @@ -4,157 +4,170 @@ const fs = require("fs"); const { readFile, writeFile, access } = require("fs/promises"); const loadlist = async () => { -/* const datfile = await readFile("./list.dat"); - const records = readxml( datfile.toString("utf-8") ); - const inform = rec => [rec.attributes.name.slice(0,4), rec.children[1].attributes.serial||"????", rec.attributes.name.slice(7)] - return records.children.filter( rec => rec.name=="game" && rec.attributes.name.charAt()!="x" ).map( inform );*/ - const datfile = await readFile( "./serial.json" ); - const datas = JSON.parse( datfile.toString() ); - return Object.entries( datas ).filter( n=>n[1]!="????" ); + const datfile = await readFile("./serial.json"); + const datas = JSON.parse(datfile.toString()); + return Object.entries(datas).filter(n => n[1] != "????"); } const trimcheat = (dat, info, order) => { let parts = [], start = 0, done = false; - while( !done ){ + while (!done) { let pos = dat.indexOf("[GameInfo]", start); - if( pos > 0 ) { + if (pos > 0) { pos = dat.indexOf("[", pos + "[GameInfo]".length); - if( pos > 0 ) { - parts.push( dat.slice( start, pos ) ); + if (pos > 0) { + parts.push(dat.slice(start, pos)); start = pos; } else { - parts.push( dat.slice( start, dat.length ) ); + parts.push(dat.slice(start, dat.length)); done = true; } } else { - parts.push( dat.slice( start, dat.length ) ); + parts.push(dat.slice(start, dat.length)); done = true; } } - return parts.map( tr => readCht(tr, info, order) ); + return parts.map(tr => readCht(tr, info, order)); } const loadcheat = async (order, serial, title, file) => { - try{ - const chtfile = await readFile( file ); + try { + const chtfile = await readFile(file); const cheats = chtfile.toString("utf-8"); let cheat = null; - if( cheats.indexOf("[GameInfo]") != cheats.lastIndexOf("[GameInfo]") ) - cheat = trimcheat( cheats, {serial}, order ); - else cheat = [ readCht( cheats, {serial}, order ) ]; + if (cheats.indexOf("[GameInfo]") != cheats.lastIndexOf("[GameInfo]")) + cheat = trimcheat(cheats, { serial }, order); + else cheat = [readCht(cheats, { serial }, order)]; return { serial, cheat } } - catch( e ) { - console.log( "bad cheat: %s[order=%d]\n%s", title, order, e.stack ) + catch (e) { + console.log("bad cheat: %s[order=%d]\n%s", title, order, e.stack) return { serial } }; } const format = valid => { - valid.sort( (a, b) => a.serial.localeCompare(b.serial) ); - console.info( `valid rom has ${valid.length}` ); - valid.forEach( ({serial, cheat}) => console.debug(`${cheat.length} in ${serial}`) ); + valid.sort((a, b) => a.serial.localeCompare(b.serial)); + console.info(`valid rom has ${valid.length}`); const enc = new TextEncoder(); - const align = (n, base) => base * Math.floor( (n+base-1) / base ); - const concat = (a,b) => { - const n = new ( Object.getPrototypeOf(a).constructor )( a.length+b.length ); - n.set( a, 0 ) - n.set( b, a.length ); - return n; + const size = Symbol("length"); + const concatUnit = 10240; + const align = (n, base) => base * Math.floor((n + base - 1) / base); + const concat = (a, b) => { + let len = (a[size] || 0) + b.length; + if (len > a.length) { + let n = new (Object.getPrototypeOf(a).constructor)(align(len, concatUnit) * 2); + n.set(a, 0); + n.set(b, a[size]); + n[size] = len; + return n; + } + else { + a.set(b, a[size]); + a[size] = len; + return a; + } } - const getnametable = list => list.reduce( (r,{serial, cheat}, idx, arr) => { + const getnametable = list => list.reduce((r, { serial, cheat }, idx, arr) => { const val = serial.slice(0, -1); - if( val != r[4] ){ - if( r[1].length > 0 && r[2] - r[1].at(-1) > r[3] ) - r[3] = r[2] - r[1].at(-1); - r[0] = concat( r[0], enc.encode(val) ) - r[1] = concat( r[1], [ r[2] ] ); + if (val != r[4]) { + if (r[1][size] > 0 && r[2] - r[1].at(r[1][size] - 1) > r[3]) + r[3] = r[2] - r[1].at(r[1][size] - 1); + r[0] = concat(r[0], enc.encode(val)) + r[1] = concat(r[1], [r[2]]); r[4] = val; } r[2] = r[2] + cheat.length; - if( idx+1 == arr.length ) { - if( r[2] - r[1].at(-1) > r[3] ) - r[3] = r[2] - r[1].at(-1); - r[1] = concat( r[1], [ r[2] ] ); + if (idx + 1 == arr.length) { + if (r[2] - r[1].at(r[1][size] - 1) > r[3]) + r[3] = r[2] - r[1].at(r[1][size] - 1); + r[1] = concat(r[1], [r[2]]); } return r; - }, [new Uint8Array(), new Uint16Array(), 0, 0, ""] ); + }, [concat(new Uint8Array(concatUnit), []), concat(new Uint16Array(concatUnit), []), 0, 0, ""]); - const expandcheat = (list, base) => list.reduce( (r, {cheat, serial}) => { + const expandcheat = (list, base) => list.reduce((r, { cheat, serial }) => { const val = serial.charCodeAt(3); - const [c, e] = cheat.reduce( (r,{id, bin}) => { - const off = r[2] + r[1].length; - r[0] = concat( r[0], [val | ( off<<3 ), id] ) - r[1] = concat( r[1], bin ); + return cheat.reduce((r, { id, bin }) => { + const off = r[2] + r[1][size]; + r[0] = concat(r[0], [val | (off << 3), id]) + r[1] = concat(r[1], bin); return r; - }, r ); - return [c, e, r[2]]; - }, [new Uint32Array(), new Uint8Array(), align(base, 32)] ); + }, r); + }, [concat(new Uint32Array(concatUnit), []), concat(new Uint8Array(concatUnit), []), align(base, 32)]); + + const linkbuffer = (arr, ...l) => { + for (let i of l) { + const arrBa = arr[i]; + arr[i] = arrBa.slice(0, arrBa[size]); + } + return arr; + } + + const [sers, offs, chtc, maxl] = linkbuffer(getnametable(valid), 0, 1); + const [cheats, expanded] = linkbuffer(expandcheat(valid, 8 + sers.length + offs.length * 2 + chtc * 8), 0, 1); + console.info(`name: ${sers.length} cheats: ${chtc} maxl: ${maxl}`); - const [sers, offs, chtc, maxl] = getnametable( valid ); - const [cheats, expanded] = expandcheat( valid, 8+sers.length+offs.length*2+chtc*8 ); - console.info( `name: ${sers.length} cheats: ${chtc} maxl: ${maxl}` ); - const serialbase = 8; const offsetbase = serialbase + sers.length; const cheatbase = offsetbase + offs.length * 2; const expandbase = align(cheatbase + cheats.length * 4, 32); const total = expandbase + expanded.length; - const output = new ArrayBuffer( total ); + const output = new ArrayBuffer(total); // header: magic number: ACL\1-serial length(u32) - const writter = new DataView( output ); - writter.setUint8(0, "A".charCodeAt() ); - writter.setUint8(1, "C".charCodeAt() ); - writter.setUint8(2, "L".charCodeAt() ); + const writter = new DataView(output); + writter.setUint8(0, "A".charCodeAt()); + writter.setUint8(1, "C".charCodeAt()); + writter.setUint8(2, "L".charCodeAt()); writter.setUint8(3, 1); - + const slen = sers.length / 3; writter.setUint16(4, slen, true); writter.setUint16(6, maxl, true); - - let ret = new Uint8Array( output ); - ret.set( sers, serialbase ); - ret.set( new Uint8Array(offs.buffer), offsetbase ); - ret.set( new Uint8Array(cheats.buffer), cheatbase ); - ret.set( expanded, expandbase ); - + + let ret = new Uint8Array(output); + ret.set(sers, serialbase); + ret.set(new Uint8Array(offs.buffer), offsetbase); + ret.set(new Uint8Array(cheats.buffer), cheatbase); + ret.set(expanded, expandbase); + return ret; } const hasfile = f => { - try{ - fs.accessSync(f); - return true; + try { + fs.accessSync(f); + return true; } - catch( e ){ - console.error( e.stack ) - return false; + catch (e) { + console.error(e.stack) + return false; } } const start = async () => { let list = await loadlist(); - let roms = await Promise.all( list.map( game => { - const [order, serial, title] = game; + let roms = await Promise.all(list.map(game => { + const [order, serial, title] = game; const file = `./gba/${order}.u8`; - if( hasfile(file) ) - return loadcheat( order, serial, title ?? order, file ); + if (hasfile(file)) + return loadcheat(order, serial, title ?? order, file); else return Promise.resolve(); - } ) ); - console.log( `all rom has ${roms.length}` ); - roms = Object.entries( roms.reduce( (r, data) => { - if( !data ) return r; + })); + console.log(`all rom has ${roms.length}`); + roms = Object.entries(roms.reduce((r, data) => { + if (!data) return r; - const {serial, cheat} = data; - if( !cheat || cheat.length == 0 ) return r; - if( serial in r ) r[serial] = [...r[serial], ...cheat]; + const { serial, cheat } = data; + if (!cheat || cheat.length == 0) return r; + if (serial in r) r[serial] = [...r[serial], ...cheat]; else r[serial] = cheat; return r; - }, {} ) ).map( ([serial, cheat]) => ({serial, cheat}) ); + }, {})).map(([serial, cheat]) => ({ serial, cheat })); const content = await format( roms ); await writeFile( "gba.acl", content ); diff --git a/tools/cheat-builder/cheats.tar.bz2 b/tools/cheat-builder/cheats.tar.bz2 index 1f13efe..f1e794c 100644 Binary files a/tools/cheat-builder/cheats.tar.bz2 and b/tools/cheat-builder/cheats.tar.bz2 differ diff --git a/tools/cheat-builder/cht.js b/tools/cheat-builder/cht.js index 568c5be..919c6cf 100644 --- a/tools/cheat-builder/cht.js +++ b/tools/cheat-builder/cht.js @@ -1,10 +1,10 @@ -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_LOCK = 1 +const READ_LOCK = 2 +const WAIT_HOLE = 3 +const READ_HOLE = 4 +const WAIT_KEY = 5 +const READ_KEY = 6 const STATE = ["", "WAIT_LOCK", "READ_LOCK", "WAIT_HOLE", "READ_HOLE", "WAIT_KEY", "READ_KEY"] const PASSHOLE = ["text", "off"] @@ -16,323 +16,351 @@ const a = "a".charCodeAt(); const f = "f".charCodeAt(); const conv = new TextEncoder(); -const findIndex = (t, tr) => { - const tlen = t.length, rlen = tr.length; - for( let i=0; i+rlen < tlen; ++i ){ - let eq = true; - for( j=0; j < rlen; ++j ){ - if( t[i+j] != tr[j] ){ - eq = false; - break; - } - } - if( eq && t[i+rlen]==0 ) return i; +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; + } } - return -1; + if (eq) return i; + } + return -1; } const read = (cht, info, order) => { - cht = cht.replace( /[-]{4,}/g, "" ); - const size = cht.length; - let state = WAIT_LOCK, token = [], line = 1; + cht = cht.replace(/[-]{4,}/g, ""); + const size = cht.length; + let state = WAIT_LOCK, token = [], line = 1; - let locks = [], currlock = null; - const token_str = () => { let r = String.fromCharCode.apply( String, token ); return (token.length=0,r);} - const error = msg => { throw new SyntaxError(`${msg} at line: ${line} in lock ${currlock?.name}`) } - for( let i=0; i <= size; ++i ){ - const ch = i == size ? "" : cht.charAt(i); - switch( ch ){ - case "[": - if( state == WAIT_LOCK || state == READ_HOLE ){ - state = READ_LOCK; - if( currlock ) locks.push( { name: currlock.name, keys: currlock.holes } ); - currlock = {} - } - 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" ) - i = size; // break; - } - 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; - } - 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.charCodeAt() ); - } - 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.charCodeAt() ); - } - 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.charCodeAt() ); - } - 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 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 error(`error occur eof on ${STATE[state]}`) - break; - case " ": - if( state == READ_LOCK || state == READ_HOLE ){ - token.push( ch.charCodeAt() ); - } - else if( state == READ_KEY ){ - if( currlock.name.toLowerCase() == "gameinfo" ){ - token.push( ch.charCodeAt() ); - } - } - break; - default: - if( state == READ_LOCK || state == READ_HOLE ){ - token.push( ch.charCodeAt() ); - } - else if( state == READ_KEY ){ - let digit = ch.toLowerCase().charCodeAt(); - if( (zero <= digit && digit <= nine) || - (a <= digit && digit <= f) || - currlock.name.toLowerCase() == "gameinfo" || - currlock.hole.toLowerCase() == "text" ) - { - token.push( digit ); - } - else error( `error occur ${ch}[${digit}] on ${STATE[state]}` ); - } - else error(`error occur ${ch} on ${STATE[state]}`); - break; + let locks = [], currlock = null; + const token_str = () => { let r = String.fromCharCode.apply(String, token); return (token.length = 0, r); } + const error = msg => { throw new SyntaxError(`${msg} at line: ${line} in lock ${currlock?.name}`) } + for (let i = 0; i <= size; ++i) { + const ch = i == size ? "" : cht.charAt(i); + switch (ch) { + case "[": + if (state == WAIT_LOCK || state == READ_HOLE) { + state = READ_LOCK; + if (currlock) locks.push({ name: currlock.name, keys: currlock.holes }); + currlock = {} } + 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") + i = size; // break; + } + 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; + } + 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.charCodeAt()); + } + 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.charCodeAt()); + } + 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.charCodeAt()); + } + 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 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 error(`error occur eof on ${STATE[state]}`) + break; + case " ": + if (state == READ_LOCK || state == READ_HOLE) { + token.push(ch.charCodeAt()); + } + else if (state == READ_KEY) { + if (currlock.name.toLowerCase() == "gameinfo") { + token.push(ch.charCodeAt()); + } + } + break; + default: + if (state == READ_LOCK || state == READ_HOLE) { + token.push(ch.charCodeAt()); + } + else if (state == READ_KEY) { + let digit = ch.toLowerCase().charCodeAt(); + if ((zero <= digit && digit <= nine) || + (a <= digit && digit <= f) || + currlock.name.toLowerCase() == "gameinfo" || + currlock.hole.toLowerCase() == "text") { + token.push(digit); + } + else error(`error occur ${ch}[${digit}] on ${STATE[state]}`); + } + else error(`error occur ${ch} on ${STATE[state]}`); + break; } - return lade( locks, info, order ); + } + return lade(locks, info, order); } 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; + 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; }; - const addrval = list.reduce( (r, command) => { + const addrval = list.reduce((r, command) => { let [taddr, ...vals] = command; - let addr = fromTaddr( taddr ); - vals.forEach( (val, pos) => { + let addr = fromTaddr(taddr); + vals.forEach((val, pos) => { let dest = addr + pos; let value = parseInt(val, 16); - r.set( dest, value ); - } ); + r.set(dest, value); + }); return r; - }, new Map() ); - const ordered = [...addrval].sort( (a,b) => a[0] - b[0] ); - const blocks = ordered.reduce( (arr, [addr, value]) => { + }, new Map()); + const ordered = [...addrval].sort((a, b) => a[0] - b[0]); + const blocks = ordered.reduce((arr, [addr, value]) => { dup.set(addr, hole); - if( arr.length == 0 ) { - arr.push({addr, value, count: 1});// cnt addr value + if (arr.length == 0) { + arr.push({ addr, value, count: 1 });// cnt addr value } else { let cur = arr.at(-1); - if( cur.value == value ){ + if (cur.value == value) { ++cur.count; } - else arr.push( {addr, value, count: 1} ); + else arr.push({ addr, value, count: 1 }); } return arr; - }, [] ); - return Uint32Array.from( blocks.reduce( (r,block)=>[...r, block.addr, (block.count<<8)|block.value], []) ) + }, []); + return Uint32Array.from(blocks.reduce((r, block) => [...r, block.addr, (block.count << 8) | block.value], [])) } const pack = (entlist, strlist, idxlist, cmdlist, xvalue) => { - const align = (n, base) => base * Math.floor( (n+base-1) / base ); + 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) ); + 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 - 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 ); + 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); }); // string table - let strings = new Uint8Array( result, strbase, strlist.length ); - strings.set( strlist, 0 ); + 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 ); + 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 ); + let commands = new Uint8Array(result, cmdbase, cmdlist.buffer.byteLength); + commands.set(new Uint8Array(cmdlist.buffer), 0); - return { id: xvalue, bin: new Uint8Array( result ) }; + return { id: xvalue, bin: new Uint8Array(result) }; } const lade = (list, info, order) => { let enttable = []; // 保存hole/key数据 - const makeID = (a,b) => a|b<<16; - const makeEntry = (id, str) => ({ id, data: str }); + const makeID = (a, b) => a | b << 16; + const makeEntry = (id, str) => ({ id, data: str }); // collect const collectEntry = (id, keys, list) => { - keys.forEach( ({name,cmd}, index) => { - list.push( name ); - enttable.push( makeEntry( makeID(id,index+1), cmd ) ); + keys.forEach(({ name, cmd }, index) => { + list.push(name); + enttable.push(makeEntry(makeID(id, index + 1), cmd)); }); } let rootstr = []; - list.forEach( (hole,index) => { - rootstr.push( hole.name ); + list.forEach((hole, index) => { + rootstr.push(hole.name); let holestr = []; - collectEntry( index+1, hole.keys, holestr ); - - let entry = makeEntry( makeID(index+1,0), holestr ); - enttable.push( entry ); - } ); - enttable.push( makeEntry( makeID(0,0), rootstr ) ); + collectEntry(index + 1, hole.keys, holestr); - enttable.sort( (a,b) => a.id - b.id ); + let entry = makeEntry(makeID(index + 1, 0), holestr); + enttable.push(entry); + }); + enttable.push(makeEntry(makeID(0, 0), rootstr)); - let strtable = Uint8Array.of(); // 保存字符串常量 - let idxtable = Uint16Array.of(); // 从字符串常量索引出来的字符串列表 - let cmdtable = Uint32Array.of(); // 保存选项对应指令 + enttable.sort((a, b) => a.id - b.id); + + let strtable = new Uint8Array(1024); // 保存字符串常量 + let idxtable = new Uint16Array(128); // 从字符串常量索引出来的字符串列表 + let cmdtable = new Uint32Array(4096); // 保存选项对应指令 + const size = {"str": 0, "cmd": 0, idx: 0}; const addString = list => { const ut_pushtr = tr => { - const off = strtable.length; - let nt = new Uint8Array( off+tr.length+1 ); - nt.set( strtable, 0 ); - nt.set( tr, off ); - strtable = nt; + 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 => { - const code = conv.encode( tr ); - const off = findIndex( strtable, code ); - return off < 0 ? ut_pushtr( code ) : off; + const code = conv.encode(tr); + const off = findIndex(strtable, code, size.str); + return off < 0 ? ut_pushtr(code) : off; }; - return list.map( ut_addtr ); + return list.map(ut_addtr); } const addStrIndex = list => { - if( list.length == 0 ) return 0; + if (list.length == 0) return 0; - if( strtable.length > 0xffff ) throw new RangeError(`string table is too big.`); - let ret = idxtable.buffer.byteLength; - let nt = new Uint16Array( idxtable.length + list.length ); - nt.set( idxtable, 0 ); - nt.set( list, idxtable.length ); - idxtable = nt; - return ret; + 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 => { - let ret = cmdtable.buffer.byteLength; - let nt = new Uint32Array( cmdtable.length + list.length ); - nt.set( cmdtable, 0 ); - nt.set( list, cmdtable.length ); - cmdtable = nt; - return ret; + 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 ){ + + if (enttable.length > 0xffff) { console.warn(`entry 数量超过上限[file=${order}]`); } // pack let holedup = new Map(); - let entbytelist = enttable.map( entry => { - let {id, data} = entry; - if( id < 0x10000 ){ // 收集str - let idxlist = addString(data.length > 1 ? data : []); // length==1就是开启 - let location = addStrIndex( idxlist ); - return Uint32Array.of( id, location, idxlist.length ); + let 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 ); + let armbytecode = assembleCheat(data, holedup, id & 0xffff); + let location = addCommand(armbytecode); + return 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 ); + 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;