Compare commits

...

2 Commits

2 changed files with 654 additions and 1 deletions

View File

@ -0,0 +1,648 @@
// 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)
}

View File

@ -120,9 +120,14 @@ pub fn main() !void {
}
fn readAll(allocator: Allocator, file: File) ![]u8 {
const file_size: usize = @intCast(try file.getEndPos());
//const file_size: usize = @intCast(try file.getEndPos());
//const file_contents = try allocator.alloc(u8, file_size);
try file.seekFromEnd(0);
const file_size = try file.getPos();
const file_contents = try allocator.alloc(u8, file_size);
try file.seekTo(0);
const readed = try file.readAll(file_contents);
if (readed != file_size) {
return error.EndOfStream;