diff --git a/tools/cheat-builder/build.js b/tools/cheat-builder/build.js index d2c2faf..0f27413 100644 --- a/tools/cheat-builder/build.js +++ b/tools/cheat-builder/build.js @@ -1,72 +1,16 @@ const readxml = require("./xml"); const readCht = require("./cht"); -const { readFile, writeFile } = require("fs/promises"); - -const XML = node => { - const helper = { - get: function(obj, key) { - if( !(key in obj) ){ - return map => { - if( !map ) return XML( obj.children.find( n => n.name==key ) ); - else return XML( obj.children.find( n => n.name == key && Object.keys(map).every(k=>n.attributes[k]==map[k]) ) ); - } - } - else return obj[key] - } - } - return node ? new Proxy(node, helper) : null; -} - -const slotmap = (type,size) => { - const map = { - gba_eeprom_4k:0, gba_yoshiug: 0, gba_eeprom: 0, - gba_eeprom_64k: 2, gba_boktai: 2, - gba_flash_rtc: 8, - gba_flash: 9, gba_flash_512: 9, - gba_flash_1m_rtc: 10, - gba_flash_1m: 11, - gba_sram: 14, gba_drilldoz: 14, gba_wariotws: 14 - } - return type in map ? (0 == (map[type] & 4) ? (size>0x1000000 ? map[type]+1 : map[type]) : map[type] ) : 15 -} - -const romdesc = data => { - const getserial = tr => ["AGB-","AGP-"].includes(tr.slice(0,4).toUpperCase())?tr.slice(4,8).toUpperCase() : ""; - const getslot = n => n ? slotmap( n.attributes.value ) : 15 - - const rom = XML(data); - - // title - let title = rom.info( {name: "alt_title"} ); - // description - let desc = rom.description(); - // serial - let serial = rom.info( {name: "serial"} ); - // slot - let part = rom.part( {name: "cart"} ); - - return { - title: title ? title.attributes.value : desc.children[0], - desc: desc.children[0], - serial: serial ? getserial(serial.attributes.value) : "", - slot: part ? getslot( part.feature({name: "slot"}), part.dataarea().attributes.size ) : 15, - hash: part ? part.dataarea().rom().attributes.crc : "", - } -} - -const loadroms = async () => { - const xmlfile = await readFile("./gba.xml") - const xmldata = readxml( xmlfile.toString("utf-8") ); - return xmldata.children.map( romdesc ).filter( n => !!n ) -} +const fs = require("fs"); +const { readFile, writeFile, access } = require("fs/promises"); const loadlist = async () => { - const datfile = await readFile("./gba.dat"); - const records = datfile.toString("utf-8").split("\r\n"); - return records.filter( rec => rec.includes(";") ).map( rec => rec.split(";") ); + 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 trimcheat = (dat, rom, order) => { +const trimcheat = (dat, info, order) => { let parts = [], start = 0, done = false; while( !done ){ let pos = dat.indexOf("[GameInfo]", start); @@ -86,24 +30,29 @@ const trimcheat = (dat, rom, order) => { done = true; } } - return parts.map( tr => readCht(tr, rom, order) ); + return parts.map( tr => readCht(tr, info, order) ); } -const loadcheat = async (rom, order) => { +const loadcheat = async (order, serial, title, file) => { try{ - const chtfile = await readFile(`./gba/${order.padStart(4,"0").slice(-4)}.u8`); + const chtfile = await readFile( file ); const cheats = chtfile.toString("utf-8"); + let cheat = null; if( cheats.indexOf("[GameInfo]") != cheats.lastIndexOf("[GameInfo]") ) - rom.cheat = trimcheat( cheats, rom, order ); - else rom.cheat = [ readCht( cheats, rom, order ) ]; + 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", rom.title, order, e.stack ) }; + catch( e ) { + console.log( "bad cheat: %s[order=%d]\n%s", title, order, e.stack ) + return { serial } + }; } -const format = roms => { - roms.sort( (a, b) => a.serial.localeCompare(b.serial) ); - const valid = roms.filter( r => r.serial.length == 4 && !!r.cheat ); - console.info( `all rom has ${roms.length}, valid rom has ${valid.length}` ); +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}`) ); const enc = new TextEncoder(); const align = (n, base) => base * Math.floor( (n+base-1) / base ); @@ -173,17 +122,37 @@ const format = roms => { return ret; } +const hasfile = f => { + try{ + fs.accessSync(f); + return true; + } + catch( e ){ + console.error( e.stack ) + return false; + } +} + const start = async () => { - let roms = await loadroms(); - let indx = roms.reduce( (r,v) => (v.hash && r.set(v.hash,v), r), new Map() ); let list = await loadlist(); - await Promise.all( list.map( game => { - if( !indx.has(game[8].toLowerCase()) ) { - return Promise.resolve(); - } - else return loadcheat( indx.get(game[8].toLowerCase()), game[0] ); + 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, file ); + else return Promise.resolve(); } ) ); - + 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]; + else r[serial] = cheat; + return r; + }, {} ) ).map( ([serial, cheat]) => ({serial, cheat}) ); + const content = await format( roms ); await writeFile( "gba.acl", content ); }