648 lines
26 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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<String>
data class LockHole(val name: String, var keys: ArrayList<LockKey>)
data class Lock(val name: String, var holes: ArrayList<LockHole>)
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<CheatSet>)
data class Entry(val id: UInt, val isKeys: Boolean, val strs: ArrayList<String>?, var keys: ArrayList<LockKey>? )
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<UByte>, tr: ByteArray): Int {
val rlen = tr.size
val tlen = t.size
val limit = tlen - rlen
for (i in 0..<limit) {
if (t[i+rlen].toUInt() != 0u) continue;
var eq = true
for (j in 0..<rlen) {
if (t[i+j].toByte() != tr[j]) {
eq = false
break
}
}
if (eq) return i
}
return -1
}
fun loadList(): ArrayList<CheatMap> {
// 读取文件内容
val jsonContent = Files.readAllBytes( Paths.get("serial.json") )
val jsonText = String(jsonContent)
// 解析 JSON
val jso = JSONValue.parse(jsonText) as JSONObject
var list = ArrayList<CheatMap>()
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<CheatMap> ): ArrayList<CheatGrp> {
var retval = ArrayList<CheatGrp>()
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<Char>): 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<CheatSet> {
var retval = ArrayList<CheatSet>()
var state = ParserState.WaitLock
var token = ArrayList<Char>()
var line = 1
var locks = ArrayList<Lock>()
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<LockKey>, dup: TreeMap<UInt, UShort>, hole: UShort, order: String): ArrayList<UInt> {
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<UInt, UInt>()
for (command in list) {
if (command.size == 0){
continue
}
val addr = fromTaddr(command[0])
var len = command.size - 1
for (pos in 0..<len) {
addrval[addr + pos.toUInt()] = command[pos + 1].toUInt(16)
}
}
val ordered = ArrayList<AsData>()
ordered.ensureCapacity(addrval.size)
addrval.forEach {
ordered.add(AsData(it.key, it.value))
}
ordered.sortBy { it.addr }
var blocks = ArrayList<UInt>()
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<Lock>, serial: String, order: String): CheatSet {
var rootstr = ArrayList<String>()
var names = ArrayList<String>()
var enttable = ArrayList<Entry>()
for ((i, lock) in locks.withIndex()) {
rootstr.add(lock.name)
names.add(lock.name)
var holestr = ArrayList<String>()
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<UByte>()
var idxtable = ArrayList<UShort>()
var cmdtable = ArrayList<UInt>()
var strcache = HashMap<String, UShort>()
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<String>): Array<UShort> {
var ret: Array<UShort> = Array<UShort>(list.size) {0u}
for ((i,s) in list.withIndex()) {
ret[i] = utaddr(s)
}
return ret
}
fun addStrIndex(list: Array<UShort>) : 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> ): UInt {
var ret = cmdtable.size
cmdtable.ensureCapacity( ret + list.size )
cmdtable.addAll( list )
return (ret * 4).toUInt()
}
addString(names)
var entbytelist = ArrayList<EntData>()
var emptylist = ArrayList<String>()
var holedup = TreeMap<UInt, UShort>()
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<EntData>, strlist: ArrayList<UByte>, idxlist: ArrayList<UShort>, cmdlist: ArrayList<UInt>): 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<CheatGrp> ): ByteArray {
cheats.sortBy { it.serial }
println("valid rom has ${cheats.size}")
var sers: ArrayList<Byte>? = null
var offs: ArrayList<Short>? = null
var chtc: Int? = null
var maxl: Int? = null
fun step1()
{
var item1 = ArrayList<Byte>()
var item2 = ArrayList<Short>()
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<Int>? = null
var expanded: ArrayList<Byte>? = null
fun step2() {
var item1 = ArrayList<Int>()
var item2 = ArrayList<Byte>()
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..<cht.sets.size) {
item2.add( cht.sets[t] )
}
}
}
chts = item1
expanded = item2
}
step2()
println("name: ${sers!!.size} cheats: $chtc maxl: $maxl")
val serialbase = 8
val offsetbase = serialbase + sers!!.size
val cheatbase = offsetbase + offs!!.size * 2
val cheatend = cheatbase + chts!!.size * 4
val expandbase = align(cheatend.toUInt(), 32u).toInt()
val total = expandbase + expanded!!.size
var retval = ByteArray(total)
retval[0] = Character.codePointAt("ACL", 0).toByte()
retval[1] = Character.codePointAt("ACL", 1).toByte()
retval[2] = Character.codePointAt("ACL", 2).toByte()
retval[3] = 1
var mem = ByteBuffer.wrap(retval, 4, total - 4)
mem.putShort( (sers!!.size / 3).toShort() )
mem.putShort( maxl!!.toShort() )
var mempos = serialbase
sers!!.forEach {
mem.put( mempos, it.toByte() )
mempos++
}
mempos = offsetbase
offs!!.forEach {
mem.putShort( mempos, it.toShort() )
mempos += 2
}
mempos = cheatbase
chts!!.forEach {
mem.put( mempos, it.toByte() )
mempos++
}
return retval
}
fun main() {
val tsrc = TimeSource.Monotonic
val start = tsrc.markNow()
val list = loadList()
val roms = transform( list );
println("all rom has ${roms.size} and time ${start.elapsedNow()}")
var idx = TreeMap<String, ArrayList<CheatSet>>()
roms.forEach {
if (it.grp.size == 0) return@forEach
if (idx.containsKey(it.serial) == false) {
idx[it.serial] = ArrayList<CheatSet>()
}
idx[it.serial]!!.addAll(it.grp)
}
var nlist = ArrayList<CheatGrp>()
idx.forEach {
nlist.add( CheatGrp(it.key, it.value) )
}
val content = format( nlist )
Files.write(Paths.get("gba.acl"), content)
}