// build command: kotlinc -cp json-simple.jar -Xinline-classes -include-runtime -jvm-target=20 -d mkacl.jar mkacl.kt // run command: java -cp "json-simple.jar:kotlin-stdlib.jar:mkacl.jar" MkaclKt // java的内存blt确实麻烦,计算速度也比较慢,生成内容就没做验证了,反正真的慢 import org.json.simple.* import java.nio.file.* import java.nio.* import java.util.* import kotlin.system.* import kotlin.time.* enum class ParserState { WaitLock, ReadLock, WaitHole, ReadHole, ReadKey, WaitPart, NeedPart, } typealias LockKey = ArrayList data class LockHole(val name: String, var keys: ArrayList) data class Lock(val name: String, var holes: ArrayList) data class CheatMap(val serial: String, val order: String) data class CheatSet(val code: UInt, val sets: ByteArray) data class CheatGrp(val serial: String, val grp: ArrayList) data class Entry(val id: UInt, val isKeys: Boolean, val strs: ArrayList?, var keys: ArrayList? ) data class EntData(val id:UInt, val loc: UInt, val len: UInt) data class AsData(val addr:UInt, val value: UInt); val PassHole = arrayListOf("off", "text") fun findIndex(t: ArrayList, tr: ByteArray): Int { val rlen = tr.size val tlen = t.size val limit = tlen - rlen for (i in 0.. { // 读取文件内容 val jsonContent = Files.readAllBytes( Paths.get("serial.json") ) val jsonText = String(jsonContent) // 解析 JSON val jso = JSONValue.parse(jsonText) as JSONObject var list = ArrayList() for (entry in jso) { val order = entry.key as String val serial = entry.value as String if (serial != "????") { list.add(CheatMap(serial, order)) } } return list } fun transform( list: ArrayList ): ArrayList { var retval = ArrayList() for (item in list) { val file = "./gba/${item.order}.u8" if (!Files.exists(Paths.get(file))) { retval.add(CheatGrp(item.serial, ArrayList())) } else { val chtdata = Files.readAllBytes( Paths.get(file) ); if (chtdata.size > 0) { var cheats = parse(chtdata, item.serial, item.order); retval.add(CheatGrp(item.serial, cheats)) } else retval.add(CheatGrp(item.serial, ArrayList())) } } return retval } inline fun makeID( a:UInt, b: UInt): UInt = a or (b shl 16) // 令人窒息的代码! inline fun align( n: UInt, base: UInt ): UInt = (n + base - 1u) and (base - 1u).inv() fun isSpace(tr: ArrayList): Boolean { var res = false for (ch in tr) { if (ch != ' ' && ch != '\t'){ res = true break } } return res } fun parse(data: ByteArray, serial: String, order: String): ArrayList { var retval = ArrayList() var state = ParserState.WaitLock var token = ArrayList() var line = 1 var locks = ArrayList() var currlock: Lock? = null var currhole: LockHole? = null fun tokenStr(): String { val retval = String( token.toCharArray() ) token.clear() return retval } fun error(msg: String) { println("Error: $msg at line $line in $order") exitProcess(1) } fun incr( ch: Char ) { when(ch) { '[' -> { if (state == ParserState.WaitLock || state == ParserState.ReadHole || state == ParserState.NeedPart) { state = ParserState.ReadLock if (currlock != null) { locks.add(currlock!!) } currlock = Lock("", ArrayList()) } else if (state == ParserState.WaitPart) {} else error("error occur [ on $state") } ']' -> { if (state == ParserState.ReadLock){ state = ParserState.WaitHole val name = tokenStr() if (name.lowercase() == "gameinfo"){ retval.add( lade(locks, serial, order) ); state = ParserState.WaitPart locks = ArrayList() currhole = null currlock = null } else if (name.length > 0) { currlock = Lock(name, ArrayList()) currhole = null } else error("empty lock name in $order") } else if (state == ParserState.WaitPart) {} else if (state == ParserState.NeedPart) { state = ParserState.WaitPart } else error("error occur ] on $state") } '\r', '\n' -> { if (state == ParserState.WaitHole) { state = ParserState.ReadHole } else if (state == ParserState.ReadKey) { val token = tokenStr() var pass = false if (currhole != null) { if (token.length > 0) { currhole!!.keys.get(currhole!!.keys.size - 1).add(token) } if (PassHole.contains(currhole!!.name.lowercase())) { pass = true } } if (!pass) { if (currlock != null) { currlock!!.holes.add(currhole!!) } currhole = null } state = ParserState.ReadHole } else if (state == ParserState.ReadLock) { error("error occur newline on $state") } else if (state == ParserState.ReadHole) { if (token.size > 0 && isSpace(token)) { error("error occur newline on $state") } else token.clear() } else if (state == ParserState.WaitPart) { state = ParserState.NeedPart } else {} if (ch == '\n') ++line } '=' -> { if (state == ParserState.ReadHole) { state = ParserState.ReadKey currhole = LockHole(tokenStr(), arrayListOf( LockKey() )) } else if (state == ParserState.ReadLock) { token.add(ch) } else if (state == ParserState.WaitPart) {} else if (state == ParserState.NeedPart) { state = ParserState.WaitPart } else error("error occur = on $state") } ',' -> { if (state == ParserState.ReadKey) { if (token.size > 0) { currhole!!.keys.get(currhole!!.keys.size - 1).add(tokenStr()) } } else if (state == ParserState.ReadHole) { var t = currlock!!.holes.removeAt(currlock!!.holes.size - 1) if (token.size > 0) t.keys.get(t.keys.size - 1).add(tokenStr()) currhole = t state = ParserState.ReadKey } else if (state == ParserState.ReadLock) { token.add(ch) } else if (state == ParserState.WaitPart) {} else if (state == ParserState.NeedPart) { state = ParserState.WaitPart } else error("error occur , on $state") } ';' -> { if (state == ParserState.ReadKey) { if (token.size > 0) currhole!!.keys.get(currhole!!.keys.size - 1).add(tokenStr()) currhole!!.keys.add(LockKey()) } else if (state == ParserState.ReadLock) { token.add(ch) } else if (state == ParserState.ReadHole) { var t = currlock!!.holes.removeAt(currlock!!.holes.size - 1) if (token.size > 0) t.keys.get(t.keys.size - 1).add(tokenStr()) t.keys.add(LockKey()) currhole = t state = ParserState.ReadKey } else if (state == ParserState.WaitPart) {} else if (state == ParserState.NeedPart) { state = ParserState.WaitPart } else error("error occur ; on $state") } ' ' -> { if (state == ParserState.ReadLock || state == ParserState.ReadHole) { token.add(ch) } else if (state == ParserState.NeedPart) { state = ParserState.WaitPart } else {} } else -> { if (state == ParserState.ReadLock || state == ParserState.ReadHole) { token.add(ch) } else if (state == ParserState.ReadKey) { if ( "0123456789abcdefABCDEF".indexOf(ch) >= 0) { token.add(ch) } else if (currhole != null) { if (currhole!!.name == "text") { token.add(ch) } } else error("error occur $ch on $state") } else if (state == ParserState.WaitPart) {} else if (state == ParserState.NeedPart) { state = ParserState.WaitPart } else error("error occur $ch on $state") } } } fun done() { if (state != ParserState.WaitPart && state != ParserState.NeedPart) { error("Unexpected end of file") } } val data = String(data, Charsets.UTF_8) for (ch in data) { incr(ch) } done() return retval } fun assembleCheat( list: ArrayList, dup: TreeMap, hole: UShort, order: String): ArrayList { fun fromTaddr(taddr: String): UInt { val n = taddr.toInt(16) if (n == 0 || n > 0x41fffff) { println("get an invalid address $taddr in $order") exitProcess(1) } if (n <= 0x3ffff) { return n.toUInt() or 0x2000000u } else if (0 == (n and 0xf000000)) { return (n.toUInt() and 0xffffu) or 0x3000000u } else return n.toUInt() } var addrval = TreeMap() for (command in list) { if (command.size == 0){ continue } val addr = fromTaddr(command[0]) var len = command.size - 1 for (pos in 0..() ordered.ensureCapacity(addrval.size) addrval.forEach { ordered.add(AsData(it.key, it.value)) } ordered.sortBy { it.addr } var blocks = ArrayList() var curr = arrayOf(0u, 0u, 0u) for (asd in ordered) { dup[asd.addr] = asd.value.toUShort() if (curr[2] == 0u) { curr[0] = asd.addr curr[1] = asd.value curr[2] = 1u } else { if (curr[1] == asd.value && curr[0] + curr[2] == asd.addr) { curr[2] += 1u } else { blocks.add(curr[0]) blocks.add( (curr[2] shl 8) or curr[1] ) curr[0] = asd.addr curr[1] = asd.value curr[2] = 1u } } } if (curr[2] != 0u) { blocks.add( curr[0] ) blocks.add( (curr[2] shl 8) or curr[1] ) } return blocks } fun lade(locks: ArrayList, serial: String, order: String): CheatSet { var rootstr = ArrayList() var names = ArrayList() var enttable = ArrayList() for ((i, lock) in locks.withIndex()) { rootstr.add(lock.name) names.add(lock.name) var holestr = ArrayList() var colname = lock.holes.size > 1; for ((index, hole) in lock.holes.withIndex()) { holestr.add(hole.name) if (colname) names.add(hole.name) val id = makeID(i.toUInt() + 1u, index.toUInt() + 1u) enttable.add(Entry(id, true, null, hole.keys)) } val id = makeID(i.toUInt() + 1u, 0u) enttable.add(Entry(id, false, holestr, null)) } enttable.add(Entry(0u, false, rootstr, null)) if (enttable.size > 0xffff) { println("entry count is overflow in file $order") exitProcess(1) } enttable.sortBy { it.id } names.sortWith( comparator = { a, b -> if (a.length == b.length) { a.compareTo(b) } else { b.length - a.length } }) var strtable = ArrayList() var idxtable = ArrayList() var cmdtable = ArrayList() var strcache = HashMap() fun utpush( tr: ByteArray ): UShort { val off = strtable.size strtable.ensureCapacity(off + tr.size + 1) strtable.addAll( arrayListOf(*tr.map { it.toUByte() }.toTypedArray()) ) strtable.add(0u.toUByte()) return off.toUShort() } fun utaddr( tr: String ): UShort { if (strcache.containsKey(tr)) { return strcache[tr]!! } else { val codes = tr.toByteArray(Charsets.UTF_8) var idx = findIndex(strtable, codes) var off:UShort = if (idx < 0) { utpush(codes) } else { idx.toUShort() } strcache[tr] = off return off } } fun addString(list: ArrayList): Array { var ret: Array = Array(list.size) {0u} for ((i,s) in list.withIndex()) { ret[i] = utaddr(s) } return ret } fun addStrIndex(list: Array) : UShort { if (list.size == 0) return 0u if (strtable.size > 0xffff) { println("String table overflow $order") } var ret = idxtable.size idxtable.ensureCapacity(ret + list.size) idxtable.addAll( list ) return (ret * 2).toUShort() } fun addCommand( list: ArrayList ): UInt { var ret = cmdtable.size cmdtable.ensureCapacity( ret + list.size ) cmdtable.addAll( list ) return (ret * 4).toUInt() } addString(names) var entbytelist = ArrayList() var emptylist = ArrayList() var holedup = TreeMap() for (entry in enttable) { val id = entry.id if (entry.isKeys) { val armbytecode = assembleCheat( entry.keys!!, holedup, (id and 0xffffu).toUShort(), order ) val location = addCommand( armbytecode ) entbytelist.add( EntData(id, location, armbytecode.size.toUInt()) ) } else { val idxlist = addString( if (id==0u) { entry.strs!! } else if (entry.strs!!.size > 1) { entry.strs!! } else { emptylist} ) val location = addStrIndex( idxlist ) entbytelist.add( EntData( id, location.toUInt(), idxlist.size.toUInt()) ) } } var code = 0u for (ch in serial) { code = (code shl 8) or (ch.code.toUInt() and 0xffu) } for (cmd in cmdtable) { code = code xor cmd } return CheatSet(code, pack(entbytelist, strtable, idxtable, cmdtable)) } fun pack( entlist: ArrayList, strlist: ArrayList, idxlist: ArrayList, cmdlist: ArrayList): ByteArray { val entrysize = 12 val strbase = 2 + entlist.size * entrysize val idxbase = strbase + strlist.size val cmdbase = idxbase + idxlist.size * 2 val datasize = cmdbase + cmdlist.size * 4 val size = align(datasize.toUInt(), 32u) var ret = ByteArray(size.toInt()) var mem = ByteBuffer.wrap(ret) mem.order(ByteOrder.LITTLE_ENDIAN) mem.putShort( 0, entlist.size.toShort() ) for ((i, entry) in entlist.withIndex()) { val offset = 2 + i * entrysize val locbase = if (entry.id > 0xffffu) { cmdbase } else { idxbase} mem.putInt(offset, entry.id.toInt()) mem.putInt(offset+4, locbase + entry.loc.toInt()) mem.putInt(offset+8, entry.len.toInt()) } // copy var mempos = strbase strlist.forEach { mem.put(mempos, it.toByte()) mempos += 1 } idxlist.forEach { mem.putShort(mempos, it.toShort()) mempos += 2 } cmdlist.forEach { mem.putInt(mempos, it.toInt()) mempos += 4 } return ret; } fun format( cheats: ArrayList ): ByteArray { cheats.sortBy { it.serial } println("valid rom has ${cheats.size}") var sers: ArrayList? = null var offs: ArrayList? = null var chtc: Int? = null var maxl: Int? = null fun step1() { var item1 = ArrayList() var item2 = ArrayList() var item3 = 0 var item4 = 0 var item5 = "" var last = 0 for (c in cheats) { val value = c.serial.substring(0, 3) if (item5 != value) { if (item2.size > 0 && item3 - last > item4) { item4 = item3 - last } val bytes = value.toByteArray() item1.add( bytes[0] ) item1.add( bytes[1] ) item1.add( bytes[2] ) item2.add( item3.toShort() ) last = item3 item5 = value } item3 += c.grp.size } if (item2.size > 0) { if (item3 - last > item4) { item4 = item3 - last } item2.add( item3.toShort() ) } sers = item1 offs = item2 chtc = item3 maxl = item4 } step1() var chts: ArrayList? = null var expanded: ArrayList? = null fun step2() { var item1 = ArrayList() var item2 = ArrayList() val item3 = align( (8 + sers!!.size + offs!!.size*2 + chtc!! * 8).toUInt(), 32u) for (c in cheats) { var value = Character.codePointAt(c.serial, 3) and 0xFF for (cht in c.grp) { val off = item3.toInt() + item2.size item1.add( value or (off shl 3) ) item1.add( cht.code.toInt() ) item2.ensureCapacity( item2.size + cht.sets.size ) for (t in 0..>() roms.forEach { if (it.grp.size == 0) return@forEach if (idx.containsKey(it.serial) == false) { idx[it.serial] = ArrayList() } idx[it.serial]!!.addAll(it.grp) } var nlist = ArrayList() idx.forEach { nlist.add( CheatGrp(it.key, it.value) ) } val content = format( nlist ) Files.write(Paths.get("gba.acl"), content) }