mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-06 22:04:10 +08:00
对打包acl的代码做了优化的修bug。曾经50秒的打包时间如今只需4秒了
This commit is contained in:
parent
519264b053
commit
e97d8006a1
@ -4,99 +4,112 @@ const fs = require("fs");
|
|||||||
const { readFile, writeFile, access } = require("fs/promises");
|
const { readFile, writeFile, access } = require("fs/promises");
|
||||||
|
|
||||||
const loadlist = async () => {
|
const loadlist = async () => {
|
||||||
/* const datfile = await readFile("./list.dat");
|
const datfile = await readFile("./serial.json");
|
||||||
const records = readxml( datfile.toString("utf-8") );
|
const datas = JSON.parse(datfile.toString());
|
||||||
const inform = rec => [rec.attributes.name.slice(0,4), rec.children[1].attributes.serial||"????", rec.attributes.name.slice(7)]
|
return Object.entries(datas).filter(n => n[1] != "????");
|
||||||
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 trimcheat = (dat, info, order) => {
|
const trimcheat = (dat, info, order) => {
|
||||||
let parts = [], start = 0, done = false;
|
let parts = [], start = 0, done = false;
|
||||||
while( !done ){
|
while (!done) {
|
||||||
let pos = dat.indexOf("[GameInfo]", start);
|
let pos = dat.indexOf("[GameInfo]", start);
|
||||||
if( pos > 0 ) {
|
if (pos > 0) {
|
||||||
pos = dat.indexOf("[", pos + "[GameInfo]".length);
|
pos = dat.indexOf("[", pos + "[GameInfo]".length);
|
||||||
if( pos > 0 ) {
|
if (pos > 0) {
|
||||||
parts.push( dat.slice( start, pos ) );
|
parts.push(dat.slice(start, pos));
|
||||||
start = pos;
|
start = pos;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
parts.push( dat.slice( start, dat.length ) );
|
parts.push(dat.slice(start, dat.length));
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
parts.push( dat.slice( start, dat.length ) );
|
parts.push(dat.slice(start, dat.length));
|
||||||
done = true;
|
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) => {
|
const loadcheat = async (order, serial, title, file) => {
|
||||||
try{
|
try {
|
||||||
const chtfile = await readFile( file );
|
const chtfile = await readFile(file);
|
||||||
const cheats = chtfile.toString("utf-8");
|
const cheats = chtfile.toString("utf-8");
|
||||||
let cheat = null;
|
let cheat = null;
|
||||||
if( cheats.indexOf("[GameInfo]") != cheats.lastIndexOf("[GameInfo]") )
|
if (cheats.indexOf("[GameInfo]") != cheats.lastIndexOf("[GameInfo]"))
|
||||||
cheat = trimcheat( cheats, {serial}, order );
|
cheat = trimcheat(cheats, { serial }, order);
|
||||||
else cheat = [ readCht( cheats, {serial}, order ) ];
|
else cheat = [readCht(cheats, { serial }, order)];
|
||||||
return { serial, cheat }
|
return { serial, cheat }
|
||||||
}
|
}
|
||||||
catch( e ) {
|
catch (e) {
|
||||||
console.log( "bad cheat: %s[order=%d]\n%s", title, order, e.stack )
|
console.log("bad cheat: %s[order=%d]\n%s", title, order, e.stack)
|
||||||
return { serial }
|
return { serial }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const format = valid => {
|
const format = valid => {
|
||||||
valid.sort( (a, b) => a.serial.localeCompare(b.serial) );
|
valid.sort((a, b) => a.serial.localeCompare(b.serial));
|
||||||
console.info( `valid rom has ${valid.length}` );
|
console.info(`valid rom has ${valid.length}`);
|
||||||
valid.forEach( ({serial, cheat}) => console.debug(`${cheat.length} in ${serial}`) );
|
|
||||||
|
|
||||||
const enc = new TextEncoder();
|
const enc = new TextEncoder();
|
||||||
const align = (n, base) => base * Math.floor( (n+base-1) / base );
|
const size = Symbol("length");
|
||||||
const concat = (a,b) => {
|
const concatUnit = 10240;
|
||||||
const n = new ( Object.getPrototypeOf(a).constructor )( a.length+b.length );
|
const align = (n, base) => base * Math.floor((n + base - 1) / base);
|
||||||
n.set( a, 0 )
|
const concat = (a, b) => {
|
||||||
n.set( b, a.length );
|
let len = (a[size] || 0) + b.length;
|
||||||
return n;
|
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);
|
const val = serial.slice(0, -1);
|
||||||
if( val != r[4] ){
|
if (val != r[4]) {
|
||||||
if( r[1].length > 0 && r[2] - r[1].at(-1) > r[3] )
|
if (r[1][size] > 0 && r[2] - r[1].at(r[1][size] - 1) > r[3])
|
||||||
r[3] = r[2] - r[1].at(-1);
|
r[3] = r[2] - r[1].at(r[1][size] - 1);
|
||||||
r[0] = concat( r[0], enc.encode(val) )
|
r[0] = concat(r[0], enc.encode(val))
|
||||||
r[1] = concat( r[1], [ r[2] ] );
|
r[1] = concat(r[1], [r[2]]);
|
||||||
r[4] = val;
|
r[4] = val;
|
||||||
}
|
}
|
||||||
r[2] = r[2] + cheat.length;
|
r[2] = r[2] + cheat.length;
|
||||||
if( idx+1 == arr.length ) {
|
if (idx + 1 == arr.length) {
|
||||||
if( r[2] - r[1].at(-1) > r[3] )
|
if (r[2] - r[1].at(r[1][size] - 1) > r[3])
|
||||||
r[3] = r[2] - r[1].at(-1);
|
r[3] = r[2] - r[1].at(r[1][size] - 1);
|
||||||
r[1] = concat( r[1], [ r[2] ] );
|
r[1] = concat(r[1], [r[2]]);
|
||||||
}
|
}
|
||||||
return r;
|
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 val = serial.charCodeAt(3);
|
||||||
const [c, e] = cheat.reduce( (r,{id, bin}) => {
|
return cheat.reduce((r, { id, bin }) => {
|
||||||
const off = r[2] + r[1].length;
|
const off = r[2] + r[1][size];
|
||||||
r[0] = concat( r[0], [val | ( off<<3 ), id] )
|
r[0] = concat(r[0], [val | (off << 3), id])
|
||||||
r[1] = concat( r[1], bin );
|
r[1] = concat(r[1], bin);
|
||||||
return r;
|
return r;
|
||||||
}, r );
|
}, r);
|
||||||
return [c, e, r[2]];
|
}, [concat(new Uint32Array(concatUnit), []), concat(new Uint8Array(concatUnit), []), align(base, 32)]);
|
||||||
}, [new Uint32Array(), new Uint8Array(), align(base, 32)] );
|
|
||||||
|
|
||||||
const [sers, offs, chtc, maxl] = getnametable( valid );
|
const linkbuffer = (arr, ...l) => {
|
||||||
const [cheats, expanded] = expandcheat( valid, 8+sers.length+offs.length*2+chtc*8 );
|
for (let i of l) {
|
||||||
console.info( `name: ${sers.length} cheats: ${chtc} maxl: ${maxl}` );
|
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 serialbase = 8;
|
const serialbase = 8;
|
||||||
const offsetbase = serialbase + sers.length;
|
const offsetbase = serialbase + sers.length;
|
||||||
@ -104,57 +117,57 @@ const format = valid => {
|
|||||||
const expandbase = align(cheatbase + cheats.length * 4, 32);
|
const expandbase = align(cheatbase + cheats.length * 4, 32);
|
||||||
const total = expandbase + expanded.length;
|
const total = expandbase + expanded.length;
|
||||||
|
|
||||||
const output = new ArrayBuffer( total );
|
const output = new ArrayBuffer(total);
|
||||||
// header: magic number: ACL\1-serial length(u32)
|
// header: magic number: ACL\1-serial length(u32)
|
||||||
const writter = new DataView( output );
|
const writter = new DataView(output);
|
||||||
writter.setUint8(0, "A".charCodeAt() );
|
writter.setUint8(0, "A".charCodeAt());
|
||||||
writter.setUint8(1, "C".charCodeAt() );
|
writter.setUint8(1, "C".charCodeAt());
|
||||||
writter.setUint8(2, "L".charCodeAt() );
|
writter.setUint8(2, "L".charCodeAt());
|
||||||
writter.setUint8(3, 1);
|
writter.setUint8(3, 1);
|
||||||
|
|
||||||
const slen = sers.length / 3;
|
const slen = sers.length / 3;
|
||||||
writter.setUint16(4, slen, true);
|
writter.setUint16(4, slen, true);
|
||||||
writter.setUint16(6, maxl, true);
|
writter.setUint16(6, maxl, true);
|
||||||
|
|
||||||
let ret = new Uint8Array( output );
|
let ret = new Uint8Array(output);
|
||||||
ret.set( sers, serialbase );
|
ret.set(sers, serialbase);
|
||||||
ret.set( new Uint8Array(offs.buffer), offsetbase );
|
ret.set(new Uint8Array(offs.buffer), offsetbase);
|
||||||
ret.set( new Uint8Array(cheats.buffer), cheatbase );
|
ret.set(new Uint8Array(cheats.buffer), cheatbase);
|
||||||
ret.set( expanded, expandbase );
|
ret.set(expanded, expandbase);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasfile = f => {
|
const hasfile = f => {
|
||||||
try{
|
try {
|
||||||
fs.accessSync(f);
|
fs.accessSync(f);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch( e ){
|
catch (e) {
|
||||||
console.error( e.stack )
|
console.error(e.stack)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
let list = await loadlist();
|
let list = await loadlist();
|
||||||
let roms = await Promise.all( list.map( game => {
|
let roms = await Promise.all(list.map(game => {
|
||||||
const [order, serial, title] = game;
|
const [order, serial, title] = game;
|
||||||
const file = `./gba/${order}.u8`;
|
const file = `./gba/${order}.u8`;
|
||||||
if( hasfile(file) )
|
if (hasfile(file))
|
||||||
return loadcheat( order, serial, title ?? order, file );
|
return loadcheat(order, serial, title ?? order, file);
|
||||||
else return Promise.resolve();
|
else return Promise.resolve();
|
||||||
} ) );
|
}));
|
||||||
console.log( `all rom has ${roms.length}` );
|
console.log(`all rom has ${roms.length}`);
|
||||||
roms = Object.entries( roms.reduce( (r, data) => {
|
roms = Object.entries(roms.reduce((r, data) => {
|
||||||
if( !data ) return r;
|
if (!data) return r;
|
||||||
|
|
||||||
const {serial, cheat} = data;
|
const { serial, cheat } = data;
|
||||||
if( !cheat || cheat.length == 0 ) return r;
|
if (!cheat || cheat.length == 0) return r;
|
||||||
if( serial in r ) r[serial] = [...r[serial], ...cheat];
|
if (serial in r) r[serial] = [...r[serial], ...cheat];
|
||||||
else r[serial] = cheat;
|
else r[serial] = cheat;
|
||||||
return r;
|
return r;
|
||||||
}, {} ) ).map( ([serial, cheat]) => ({serial, cheat}) );
|
}, {})).map(([serial, cheat]) => ({ serial, cheat }));
|
||||||
|
|
||||||
const content = await format( roms );
|
const content = await format( roms );
|
||||||
await writeFile( "gba.acl", content );
|
await writeFile( "gba.acl", content );
|
||||||
|
Binary file not shown.
@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
const WAIT_LOCK = 1
|
const WAIT_LOCK = 1
|
||||||
const READ_LOCK = 2
|
const READ_LOCK = 2
|
||||||
const WAIT_HOLE = 3
|
const WAIT_HOLE = 3
|
||||||
const READ_HOLE = 4
|
const READ_HOLE = 4
|
||||||
const WAIT_KEY = 5
|
const WAIT_KEY = 5
|
||||||
const READ_KEY = 6
|
const READ_KEY = 6
|
||||||
|
|
||||||
const STATE = ["", "WAIT_LOCK", "READ_LOCK", "WAIT_HOLE", "READ_HOLE", "WAIT_KEY", "READ_KEY"]
|
const STATE = ["", "WAIT_LOCK", "READ_LOCK", "WAIT_HOLE", "READ_HOLE", "WAIT_KEY", "READ_KEY"]
|
||||||
const PASSHOLE = ["text", "off"]
|
const PASSHOLE = ["text", "off"]
|
||||||
@ -16,323 +16,351 @@ const a = "a".charCodeAt();
|
|||||||
const f = "f".charCodeAt();
|
const f = "f".charCodeAt();
|
||||||
const conv = new TextEncoder();
|
const conv = new TextEncoder();
|
||||||
|
|
||||||
const findIndex = (t, tr) => {
|
const findIndex = (t, tr, tlen) => {
|
||||||
const tlen = t.length, rlen = tr.length;
|
const rlen = tr.length, limit = tlen - rlen;
|
||||||
for( let i=0; i+rlen < tlen; ++i ){
|
for (let i = 0; i < limit; ++i) {
|
||||||
let eq = true;
|
if (t[i + rlen] != 0) continue;
|
||||||
for( j=0; j < rlen; ++j ){
|
|
||||||
if( t[i+j] != tr[j] ){
|
let eq = true, j = 0, k = i + j;
|
||||||
eq = false;
|
for (; j < rlen; ++j, ++k) {
|
||||||
break;
|
if (t[k] != tr[j]) {
|
||||||
}
|
eq = false;
|
||||||
}
|
break;
|
||||||
if( eq && t[i+rlen]==0 ) return i;
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
if (eq) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const read = (cht, info, order) => {
|
const read = (cht, info, order) => {
|
||||||
cht = cht.replace( /[-]{4,}/g, "" );
|
cht = cht.replace(/[-]{4,}/g, "");
|
||||||
const size = cht.length;
|
const size = cht.length;
|
||||||
let state = WAIT_LOCK, token = [], line = 1;
|
let state = WAIT_LOCK, token = [], line = 1;
|
||||||
|
|
||||||
let locks = [], currlock = null;
|
let locks = [], currlock = null;
|
||||||
const token_str = () => { let r = String.fromCharCode.apply( String, token ); return (token.length=0,r);}
|
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}`) }
|
const error = msg => { throw new SyntaxError(`${msg} at line: ${line} in lock ${currlock?.name}`) }
|
||||||
for( let i=0; i <= size; ++i ){
|
for (let i = 0; i <= size; ++i) {
|
||||||
const ch = i == size ? "" : cht.charAt(i);
|
const ch = i == size ? "" : cht.charAt(i);
|
||||||
switch( ch ){
|
switch (ch) {
|
||||||
case "[":
|
case "[":
|
||||||
if( state == WAIT_LOCK || state == READ_HOLE ){
|
if (state == WAIT_LOCK || state == READ_HOLE) {
|
||||||
state = READ_LOCK;
|
state = READ_LOCK;
|
||||||
if( currlock ) locks.push( { name: currlock.name, keys: currlock.holes } );
|
if (currlock) locks.push({ name: currlock.name, keys: currlock.holes });
|
||||||
currlock = {}
|
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;
|
|
||||||
}
|
}
|
||||||
|
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 assembleCheat = (list, dup, hole) => {
|
||||||
// 建立 地址:数值 的映射关系
|
// 建立 地址:数值 的映射关系
|
||||||
const fromTaddr = taddr => {
|
const fromTaddr = taddr => {
|
||||||
const n = parseInt(taddr, 16);
|
const n = parseInt(taddr, 16);
|
||||||
if( n==0 || n>0x41FFFFF ) throw new Error( `get a invalid address: ${taddr}` );
|
if (n == 0 || n > 0x41FFFFF) throw new Error(`get a invalid address: ${taddr}`);
|
||||||
if( n <= 0x3ffff ) return n | 0x2000000;
|
if (n <= 0x3ffff) return n | 0x2000000;
|
||||||
else if( 0 == (n & 0xf000000) ) return (n & 0xffff) | 0x3000000;
|
else if (0 == (n & 0xf000000)) return (n & 0xffff) | 0x3000000;
|
||||||
else return n;
|
else return n;
|
||||||
};
|
};
|
||||||
const addrval = list.reduce( (r, command) => {
|
const addrval = list.reduce((r, command) => {
|
||||||
let [taddr, ...vals] = command;
|
let [taddr, ...vals] = command;
|
||||||
let addr = fromTaddr( taddr );
|
let addr = fromTaddr(taddr);
|
||||||
vals.forEach( (val, pos) => {
|
vals.forEach((val, pos) => {
|
||||||
let dest = addr + pos;
|
let dest = addr + pos;
|
||||||
let value = parseInt(val, 16);
|
let value = parseInt(val, 16);
|
||||||
r.set( dest, value );
|
r.set(dest, value);
|
||||||
} );
|
});
|
||||||
return r;
|
return r;
|
||||||
}, new Map() );
|
}, new Map());
|
||||||
const ordered = [...addrval].sort( (a,b) => a[0] - b[0] );
|
const ordered = [...addrval].sort((a, b) => a[0] - b[0]);
|
||||||
const blocks = ordered.reduce( (arr, [addr, value]) => {
|
const blocks = ordered.reduce((arr, [addr, value]) => {
|
||||||
dup.set(addr, hole);
|
dup.set(addr, hole);
|
||||||
if( arr.length == 0 ) {
|
if (arr.length == 0) {
|
||||||
arr.push({addr, value, count: 1});// cnt addr value
|
arr.push({ addr, value, count: 1 });// cnt addr value
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let cur = arr.at(-1);
|
let cur = arr.at(-1);
|
||||||
if( cur.value == value ){
|
if (cur.value == value) {
|
||||||
++cur.count;
|
++cur.count;
|
||||||
}
|
}
|
||||||
else arr.push( {addr, value, count: 1} );
|
else arr.push({ addr, value, count: 1 });
|
||||||
}
|
}
|
||||||
return arr;
|
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 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 entrysize = entlist[0].buffer.byteLength;
|
||||||
const strbase = 2 + entlist.length * entrysize;
|
const strbase = 2 + entlist.length * entrysize;
|
||||||
const idxbase = strbase + strlist.buffer.byteLength;
|
const idxbase = strbase + strlist.buffer.byteLength;
|
||||||
const cmdbase = idxbase + idxlist.buffer.byteLength;
|
const cmdbase = idxbase + idxlist.buffer.byteLength;
|
||||||
const size = cmdbase + cmdlist.buffer.byteLength;;
|
const size = cmdbase + cmdlist.buffer.byteLength;
|
||||||
let result = new ArrayBuffer( align(size, 32) );
|
let result = new ArrayBuffer(align(size, 32));
|
||||||
|
|
||||||
// entry count
|
// entry count
|
||||||
let view = new DataView(result);
|
let view = new DataView(result);
|
||||||
view.setUint16(0, entlist.length, true);
|
view.setUint16(0, entlist.length, true);
|
||||||
|
|
||||||
// entry data
|
// entry data
|
||||||
entlist.forEach( (entry,index) => {
|
entlist.forEach((entry, index) => {
|
||||||
let offset = 2 + index*entrysize;
|
let offset = 2 + index * entrysize;
|
||||||
view.setUint32( offset, entry[0], true );
|
view.setUint32(offset, entry[0], true);
|
||||||
view.setUint32( offset+4, entry[1] + (entry[0] > 0xffff ? cmdbase : idxbase), true );
|
view.setUint32(offset + 4, entry[1] + (entry[0] > 0xffff ? cmdbase : idxbase), true);
|
||||||
view.setUint32( offset+8, entry[2], true );
|
view.setUint32(offset + 8, entry[2], true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// string table
|
// string table
|
||||||
let strings = new Uint8Array( result, strbase, strlist.length );
|
let strings = new Uint8Array(result, strbase, strlist.length);
|
||||||
strings.set( strlist, 0 );
|
strings.set(strlist, 0);
|
||||||
|
|
||||||
// indexes table
|
// indexes table
|
||||||
let indexes = new Uint8Array( result, idxbase, idxlist.buffer.byteLength );
|
let indexes = new Uint8Array(result, idxbase, idxlist.buffer.byteLength);
|
||||||
indexes.set( new Uint8Array(idxlist.buffer), 0 );
|
indexes.set(new Uint8Array(idxlist.buffer), 0);
|
||||||
|
|
||||||
// instruction table
|
// instruction table
|
||||||
let commands = new Uint8Array( result, cmdbase, cmdlist.buffer.byteLength );
|
let commands = new Uint8Array(result, cmdbase, cmdlist.buffer.byteLength);
|
||||||
commands.set( new Uint8Array(cmdlist.buffer), 0 );
|
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) => {
|
const lade = (list, info, order) => {
|
||||||
let enttable = []; // 保存hole/key数据
|
let enttable = []; // 保存hole/key数据
|
||||||
|
|
||||||
const makeID = (a,b) => a|b<<16;
|
const makeID = (a, b) => a | b << 16;
|
||||||
const makeEntry = (id, str) => ({ id, data: str });
|
const makeEntry = (id, str) => ({ id, data: str });
|
||||||
|
|
||||||
// collect
|
// collect
|
||||||
const collectEntry = (id, keys, list) => {
|
const collectEntry = (id, keys, list) => {
|
||||||
keys.forEach( ({name,cmd}, index) => {
|
keys.forEach(({ name, cmd }, index) => {
|
||||||
list.push( name );
|
list.push(name);
|
||||||
enttable.push( makeEntry( makeID(id,index+1), cmd ) );
|
enttable.push(makeEntry(makeID(id, index + 1), cmd));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let rootstr = [];
|
let rootstr = [];
|
||||||
list.forEach( (hole,index) => {
|
list.forEach((hole, index) => {
|
||||||
rootstr.push( hole.name );
|
rootstr.push(hole.name);
|
||||||
|
|
||||||
let holestr = [];
|
let holestr = [];
|
||||||
collectEntry( index+1, hole.keys, holestr );
|
collectEntry(index + 1, hole.keys, holestr);
|
||||||
|
|
||||||
let entry = makeEntry( makeID(index+1,0), holestr );
|
let entry = makeEntry(makeID(index + 1, 0), holestr);
|
||||||
enttable.push( entry );
|
enttable.push(entry);
|
||||||
} );
|
});
|
||||||
enttable.push( makeEntry( makeID(0,0), rootstr ) );
|
enttable.push(makeEntry(makeID(0, 0), rootstr));
|
||||||
|
|
||||||
enttable.sort( (a,b) => a.id - b.id );
|
enttable.sort((a, b) => a.id - b.id);
|
||||||
|
|
||||||
let strtable = Uint8Array.of(); // 保存字符串常量
|
let strtable = new Uint8Array(1024); // 保存字符串常量
|
||||||
let idxtable = Uint16Array.of(); // 从字符串常量索引出来的字符串列表
|
let idxtable = new Uint16Array(128); // 从字符串常量索引出来的字符串列表
|
||||||
let cmdtable = Uint32Array.of(); // 保存选项对应指令
|
let cmdtable = new Uint32Array(4096); // 保存选项对应指令
|
||||||
|
const size = {"str": 0, "cmd": 0, idx: 0};
|
||||||
const addString = list => {
|
const addString = list => {
|
||||||
const ut_pushtr = tr => {
|
const ut_pushtr = tr => {
|
||||||
const off = strtable.length;
|
const off = size.str;
|
||||||
let nt = new Uint8Array( off+tr.length+1 );
|
const len = off + tr.length + 1;
|
||||||
nt.set( strtable, 0 );
|
if (len > strtable.length) {
|
||||||
nt.set( tr, off );
|
let nt = new Uint8Array(Math.min(strtable.length * 2, off + tr.length + 1024));
|
||||||
strtable = nt;
|
nt.set(strtable, 0);
|
||||||
|
nt.set(tr, off);
|
||||||
|
strtable = nt;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strtable.set(tr, off);
|
||||||
|
}
|
||||||
|
size.str = len;
|
||||||
return off;
|
return off;
|
||||||
};
|
};
|
||||||
const ut_addtr = tr => {
|
const ut_addtr = tr => {
|
||||||
const code = conv.encode( tr );
|
const code = conv.encode(tr);
|
||||||
const off = findIndex( strtable, code );
|
const off = findIndex(strtable, code, size.str);
|
||||||
return off < 0 ? ut_pushtr( code ) : off;
|
return off < 0 ? ut_pushtr(code) : off;
|
||||||
};
|
};
|
||||||
return list.map( ut_addtr );
|
return list.map(ut_addtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addStrIndex = list => {
|
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.`);
|
if (size.str > 0xffff) throw new RangeError(`string table is too big.`);
|
||||||
let ret = idxtable.buffer.byteLength;
|
|
||||||
let nt = new Uint16Array( idxtable.length + list.length );
|
const ret = size.idx;
|
||||||
nt.set( idxtable, 0 );
|
const len = ret + list.length;
|
||||||
nt.set( list, idxtable.length );
|
if( len > idxtable.length )
|
||||||
idxtable = nt;
|
{
|
||||||
return ret;
|
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 addCommand = list => {
|
||||||
let ret = cmdtable.buffer.byteLength;
|
const ret = size.cmd;
|
||||||
let nt = new Uint32Array( cmdtable.length + list.length );
|
const len = ret + list.length;
|
||||||
nt.set( cmdtable, 0 );
|
if (len > cmdtable.length) {
|
||||||
nt.set( list, cmdtable.length );
|
let n = new Uint32Array(Math.max(cmdtable.length * 8, len));
|
||||||
cmdtable = nt;
|
n.set(cmdtable, 0);
|
||||||
return ret;
|
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}]`);
|
console.warn(`entry 数量超过上限[file=${order}]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pack
|
// pack
|
||||||
let holedup = new Map();
|
let holedup = new Map();
|
||||||
let entbytelist = enttable.map( entry => {
|
let entbytelist = enttable.map(entry => {
|
||||||
let {id, data} = entry;
|
let { id, data } = entry;
|
||||||
if( id < 0x10000 ){ // 收集str
|
if (id < 0x10000) { // 收集str
|
||||||
let idxlist = addString(data.length > 1 ? data : []); // length==1就是开启
|
let idxlist = id == 0 ? addString( data ) : addString(data.length > 1 ? data : []); // length==1就是开启
|
||||||
let location = addStrIndex( idxlist );
|
let location = addStrIndex(idxlist);
|
||||||
return Uint32Array.of( id, location, idxlist.length );
|
return Uint32Array.of(id, location, idxlist.length);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let armbytecode = assembleCheat( data, holedup, id & 0xffff );
|
let armbytecode = assembleCheat(data, holedup, id & 0xffff);
|
||||||
let location = addCommand( armbytecode );
|
let location = addCommand(armbytecode);
|
||||||
return Uint32Array.of( id, location, armbytecode.length );
|
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 n = info.serial.split("").reduce((r, v) => (v.charCodeAt() | r << 8), 0);
|
||||||
let xv = cmdtable.reduce( (r,v) => r^v, n );
|
let xv = cmdtable.reduce((r, v) => r ^ v, n);
|
||||||
return pack( entbytelist, strtable, idxtable, cmdtable, xv );
|
return pack(entbytelist, strtable, idxtable, cmdtable, xv);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = read;
|
module.exports = read;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user