mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-06 05:44:11 +08:00
672 lines
16 KiB
Go
672 lines
16 KiB
Go
package main;
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"encoding/json"
|
|
"encoding/binary"
|
|
"strings"
|
|
"bytes"
|
|
"sort"
|
|
)
|
|
|
|
type Serial struct {
|
|
order string
|
|
serial string
|
|
}
|
|
|
|
type Packed struct {
|
|
code uint32
|
|
data []byte
|
|
}
|
|
|
|
type Cheat struct {
|
|
serial string
|
|
cheats []Packed
|
|
}
|
|
|
|
type LockKey = []string
|
|
|
|
type LockHole struct {
|
|
name string
|
|
keys []LockKey
|
|
}
|
|
|
|
type Lock struct {
|
|
name string
|
|
holes []LockHole
|
|
}
|
|
|
|
type EntData struct {
|
|
id uint32
|
|
t uint
|
|
strs [](*string)
|
|
cmds *[]LockKey
|
|
}
|
|
|
|
const (
|
|
WaitLock = iota
|
|
ReadLock
|
|
WaitHole
|
|
ReadHole
|
|
ReadKey
|
|
WaitPart
|
|
NeedPart
|
|
)
|
|
|
|
const (
|
|
EntNames = iota
|
|
EntCmds
|
|
)
|
|
|
|
func align(n uint32, basic uint32) uint32 {
|
|
ext := n % basic
|
|
if ext == 0 {
|
|
return n
|
|
} else {
|
|
return n + basic - ext
|
|
}
|
|
}
|
|
|
|
func find_index(t []byte, tr []byte) int {
|
|
rlen, tlen := len(tr), len(t)
|
|
limit := tlen - rlen
|
|
for i := 0; i < limit; i++ {
|
|
if t[i+rlen] != 0 {
|
|
continue
|
|
}
|
|
|
|
eq := true
|
|
for j := 0; j < rlen; j++ {
|
|
if tr[j] != t[i+j] {
|
|
eq = false
|
|
break
|
|
}
|
|
}
|
|
if eq {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func assemble_cheat(list *[]LockKey, dup map[uint32]uint16, hole uint16) []uint32 {
|
|
var atoi = func(t string) uint32 {
|
|
var n uint32
|
|
fmt.Sscanf(t, "%x", &n)
|
|
return n
|
|
}
|
|
var from_taddr = func(taddr string) uint32 {
|
|
n := atoi(taddr)
|
|
if n == 0 || n > 0x41fffff {
|
|
panic(fmt.Sprintf("invalid taddr %s", taddr))
|
|
}
|
|
|
|
if n <= 0x3ffff {
|
|
return n | 0x2000000
|
|
}else if 0 == (n&0xf000000) {
|
|
return (n & 0xffff) | 0x3000000
|
|
}else { return n }
|
|
}
|
|
|
|
addrval := make(map[uint32]uint32, 0)
|
|
for _, command := range *list {
|
|
if len(command) == 0 {
|
|
continue
|
|
}
|
|
|
|
addr := from_taddr(command[0])
|
|
len := len(command) - 1
|
|
for i := 0; i < len; i++ {
|
|
addrval[addr + uint32(i)] = atoi(command[i+1])
|
|
}
|
|
}
|
|
|
|
ordered := make([][2]uint32, 0)
|
|
for addr, val := range addrval {
|
|
ordered = append(ordered, [2]uint32{addr, val})
|
|
}
|
|
sort.Slice(ordered, func(i, j int) bool {
|
|
return ordered[i][0] < ordered[j][0]
|
|
})
|
|
blocks, curr := make([]uint32, 0), [3]uint32{0, 0, 0}
|
|
for _, addrval := range ordered {
|
|
addr, value := addrval[0], addrval[1]
|
|
dup[addr] = hole
|
|
|
|
if 0 == curr[2] {
|
|
curr = [3]uint32{addr, value, 1}
|
|
}else if curr[1] == value && curr[0] + curr[2] == addr {
|
|
curr[2]++
|
|
}else {
|
|
blocks = append(blocks, curr[0], curr[2]<<8 | curr[1])
|
|
curr = [3]uint32{addr, value, 1}
|
|
}
|
|
}
|
|
if curr[2] > 0 {
|
|
blocks = append(blocks, curr[0], curr[2]<<8 | curr[1])
|
|
}
|
|
return blocks
|
|
}
|
|
|
|
func loadlist() ([]Serial, error) {
|
|
bytes, err := os.ReadFile("./serial.json")
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return nil, err
|
|
}
|
|
|
|
var jsonData map[string]string
|
|
err = json.Unmarshal(bytes, &jsonData)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return nil, err
|
|
}
|
|
|
|
var serials []Serial
|
|
for k, v := range jsonData {
|
|
if v != "????" {
|
|
serials = append(serials, Serial{order: k, serial: v})
|
|
}
|
|
}
|
|
return serials, nil
|
|
}
|
|
|
|
func lade(locks []Lock, serial string, order string) Packed {
|
|
rootstr, names := make([]*string, 0), make([]*string, 0)
|
|
enttable := make([]EntData, 0)
|
|
|
|
var make_id = func(a uint32, b uint32) uint32 {
|
|
return a | b<<16
|
|
};
|
|
|
|
for i, lock := range locks {
|
|
rootstr = append(rootstr, &lock.name)
|
|
names = append(names, &lock.name)
|
|
|
|
holestr := make([]*string, 0)
|
|
colname := len(lock.holes) > 1
|
|
for idx, hole := range lock.holes {
|
|
holestr = append(holestr, &hole.name)
|
|
if colname {
|
|
names = append(names, &hole.name)
|
|
}
|
|
id := make_id(uint32(i+1), uint32(idx+1))
|
|
enttable = append( enttable, EntData{
|
|
id, EntCmds, make([]*string, 0), &hole.keys,
|
|
})
|
|
}
|
|
enttable = append(enttable, EntData{
|
|
make_id(uint32(i+1), 0),
|
|
EntNames, holestr, nil,
|
|
})
|
|
}
|
|
enttable = append(enttable, EntData{
|
|
make_id(0, 0), EntNames, rootstr, nil,
|
|
})
|
|
|
|
if len(enttable) > 0xffff {
|
|
panic("too many entries")
|
|
}
|
|
|
|
sort.Slice(enttable, func(i, j int) bool {
|
|
return enttable[i].id < enttable[j].id
|
|
})
|
|
sort.Slice(names, func(i, j int) bool {
|
|
if len(*names[i]) == len(*names[j]) {
|
|
return *names[i] < *names[j]
|
|
}else {
|
|
return len(*names[j]) < len(*names[i])
|
|
}
|
|
})
|
|
|
|
strtable, idxtable, cmdtable := make([]byte, 0), make([]uint16, 0), make([]uint32, 0)
|
|
strcache := make(map[string]uint16, 0)
|
|
|
|
var ut_push = func(tr []byte) uint16 {
|
|
off := len(strtable)
|
|
for _, v := range tr {
|
|
strtable = append(strtable, v)
|
|
}
|
|
strtable = append(strtable, 0)
|
|
return uint16(off)
|
|
}
|
|
var ut_addr = func(tr string) uint16 {
|
|
if v, ok := strcache[tr]; ok {
|
|
return v
|
|
}
|
|
idx := find_index(strtable, []byte(tr))
|
|
var off uint16
|
|
if idx < 0 { off = ut_push([]byte(tr)) } else { off = uint16(idx) }
|
|
//uint16(idx < 0 ? ut_push([]byte(tr)) : idx)
|
|
strcache[tr] = off
|
|
return off
|
|
}
|
|
var add_string = func(list []*string) []uint16 {
|
|
retval := make([]uint16, len(list))
|
|
for i, v := range list {
|
|
retval[i] = ut_addr(*v)
|
|
}
|
|
return retval
|
|
}
|
|
var add_str_index = func(list []uint16) uint16 {
|
|
if len(list) == 0 {
|
|
return 0
|
|
}
|
|
if len(strtable) > 0xffff {
|
|
panic("too many strings")
|
|
}
|
|
|
|
retval := uint16(len(idxtable))
|
|
for _, v := range list {
|
|
idxtable = append(idxtable, v)
|
|
}
|
|
return retval*2
|
|
}
|
|
var add_command = func(list []uint32) uint32 {
|
|
retval := len(cmdtable)
|
|
for _, v := range list {
|
|
cmdtable = append(cmdtable, v)
|
|
}
|
|
return uint32(retval*4)
|
|
}
|
|
|
|
add_string(names)
|
|
|
|
entbytelist, emptylist, holedup := make([][3]uint32, 0), make([]*string, 0), make(map[uint32]uint16, 0)
|
|
for _, entry := range enttable {
|
|
if entry.t == EntNames {
|
|
var idxlist []uint16
|
|
if entry.id == 0 {
|
|
idxlist = add_string( entry.strs )
|
|
}else if len(entry.strs) > 1 {
|
|
idxlist = add_string( entry.strs )
|
|
}else {
|
|
idxlist = add_string( emptylist )
|
|
}
|
|
location := add_str_index(idxlist)
|
|
entbytelist = append(entbytelist, [3]uint32{
|
|
entry.id, uint32(location), uint32(len(idxlist)),
|
|
})
|
|
} else {
|
|
armbytecode := assemble_cheat( entry.cmds, holedup, uint16(entry.id & 0xffff) )
|
|
location := add_command(armbytecode)
|
|
entbytelist = append(entbytelist, [3]uint32{
|
|
entry.id, uint32(location), uint32(len(armbytecode)),
|
|
})
|
|
}
|
|
}
|
|
|
|
var code uint32 = 0
|
|
for _, c := range []byte(serial) {
|
|
code = (code << 8) | uint32(c & 0xff)
|
|
}
|
|
for _, cmd := range cmdtable {
|
|
code = code ^ cmd
|
|
}
|
|
return Packed{code, pack(entbytelist, strtable, idxtable, cmdtable) }
|
|
}
|
|
|
|
func pack(entlist [][3]uint32, strlist []byte, idxlist []uint16, cmdlist []uint32) []byte {
|
|
entrysize := 12
|
|
strbase := 2 + len(entlist) * entrysize
|
|
idxbase := strbase + len(strlist)
|
|
cmdbase := idxbase + len(idxlist) * 2
|
|
nonalign_size := cmdbase + len(cmdlist) * 4
|
|
size := align(uint32(nonalign_size), 32)
|
|
|
|
result := make([]byte, size)
|
|
binary.Encode(result, binary.LittleEndian, uint32(len(entlist)))
|
|
|
|
for i, entry := range entlist {
|
|
offset := i * entrysize + 2
|
|
locbase := idxbase
|
|
if entry[0] > 0xffff {
|
|
locbase = cmdbase
|
|
}
|
|
|
|
binary.Encode(result[offset:], binary.LittleEndian, entry[0])
|
|
binary.Encode(result[offset+4:], binary.LittleEndian, entry[1] + uint32(locbase))
|
|
binary.Encode(result[offset+8:], binary.LittleEndian, entry[2])
|
|
}
|
|
|
|
copy(result[strbase:], strlist)
|
|
|
|
for i, v := range idxlist {
|
|
binary.Encode(result[idxbase + i*2:], binary.LittleEndian, v)
|
|
}
|
|
|
|
for i, v := range cmdlist {
|
|
binary.Encode(result[cmdbase + i*4:], binary.LittleEndian, v)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func parse(datas []byte, serial string, order string) ([]Packed, error) {
|
|
var retval []Packed
|
|
var token []byte
|
|
state, line, locks := WaitLock, 1, make([]Lock, 0)
|
|
var currlock *Lock
|
|
var currhole *LockHole
|
|
|
|
var token_str = func() string {
|
|
var r = string( token );
|
|
token = make([]byte, 0)
|
|
return r;
|
|
};
|
|
|
|
var error = func(msg string) {
|
|
panicmsg := fmt.Sprintf("error occur %s on line %d", msg, line)
|
|
panic(panicmsg)
|
|
};
|
|
|
|
// incr
|
|
var incr = func(ch byte) {
|
|
switch ch {
|
|
case '[':
|
|
if state == WaitLock || state == ReadHole || state == NeedPart {
|
|
state = ReadLock;
|
|
if currlock != nil {
|
|
locks = append(locks, *currlock );
|
|
}
|
|
currlock = &Lock{"", make([]LockHole, 0)};
|
|
}else if state == WaitPart {
|
|
}else { error(fmt.Sprintf("error occur [ on %d", state) ) }
|
|
case ']':
|
|
if state == ReadLock {
|
|
state = WaitHole
|
|
name := token_str()
|
|
if strings.ToLower(name) == "gameinfo" {
|
|
retval = append(retval, lade(locks, serial, order))
|
|
state = WaitPart
|
|
locks = make([]Lock, 0)
|
|
currlock = nil
|
|
currhole = nil
|
|
}else if len(name) > 0 {
|
|
currlock = &Lock{name, make([]LockHole, 0)}
|
|
currhole = nil
|
|
}else { error(fmt.Sprintf("empty lock name in %s", order) ) }
|
|
}else if state == WaitPart {
|
|
}else if state == NeedPart{
|
|
state = WaitPart
|
|
}else { error(fmt.Sprintf("error occur ] on %d", state) ) }
|
|
case '\r', '\n':
|
|
if state == WaitHole {
|
|
state = ReadHole
|
|
}else if state == ReadKey {
|
|
t, pass := token_str(), false
|
|
if currhole != nil {
|
|
if len(t) > 0 {
|
|
idx := len(currhole.keys) - 1
|
|
currhole.keys[idx] = append(currhole.keys[idx], t)
|
|
}
|
|
|
|
t = strings.ToLower(currhole.name)
|
|
if t == "text" || t == "off" {
|
|
pass = true
|
|
}
|
|
}
|
|
if !pass {
|
|
if currlock != nil {
|
|
if currhole == nil { error("unreachable for pass") }
|
|
currlock.holes = append(currlock.holes, *currhole)
|
|
//LockHole{name: currhole.name, keys: currhole.keys})
|
|
}
|
|
currhole = nil
|
|
}
|
|
state = ReadHole
|
|
}else if state == ReadLock {
|
|
error(fmt.Sprintf("error occur newline on %d", state))
|
|
}else if state == ReadHole {
|
|
if len(token) > 0 && bytes.ContainsFunc(token, func(r rune) bool { return r != ' ' && r != '\t' }) {
|
|
error(fmt.Sprintf("error occur space on %d", state))
|
|
} else {
|
|
token = make([]byte, 0)
|
|
}
|
|
}else if state == WaitPart {
|
|
state = NeedPart
|
|
} else {}
|
|
if ch == '\n' { line++ }
|
|
case '=':
|
|
if state == ReadHole {
|
|
state = ReadKey
|
|
currhole = &LockHole{name: token_str(), keys: make([]LockKey, 1)}
|
|
}else if state == ReadLock {
|
|
token = append(token, ch)
|
|
}else if state == WaitPart {
|
|
}else if state == NeedPart {
|
|
state = WaitPart
|
|
}else { error(fmt.Sprintf("error occur = on %d", state)) }
|
|
case ',':
|
|
if state == ReadKey {
|
|
if currhole == nil { error("unreachable for currhole") }
|
|
|
|
if len(token) > 0 {
|
|
idx := len(currhole.keys) - 1
|
|
currhole.keys[idx] = append(currhole.keys[idx], token_str())
|
|
}
|
|
}else if state == ReadHole {
|
|
if currlock == nil { error("unreachable for currlock") }
|
|
|
|
idx := len(currlock.holes) - 1
|
|
tmp := currlock.holes[idx]
|
|
currlock.holes = currlock.holes[:idx]
|
|
if len(token) > 0 {
|
|
pos := len(tmp.keys) - 1
|
|
tmp.keys[pos] = append(tmp.keys[pos], token_str())
|
|
}
|
|
currhole = &LockHole{name: tmp.name, keys: tmp.keys}
|
|
state = ReadKey
|
|
}else if state == ReadLock {
|
|
token = append(token, ch)
|
|
}else if state == WaitPart {
|
|
}else if state == NeedPart {
|
|
state = WaitPart
|
|
}else { error(fmt.Sprintf("error occur , on %d", state)) }
|
|
case ';':
|
|
if state == ReadKey {
|
|
if currhole == nil { error("unreachable for currhole") }
|
|
|
|
if len(token) > 0 {
|
|
idx := len(currhole.keys) - 1
|
|
currhole.keys[idx] = append(currhole.keys[idx], token_str())
|
|
}
|
|
if currhole != nil {
|
|
currhole.keys = append(currhole.keys, make([]string, 0))
|
|
}
|
|
}else if state == ReadLock {
|
|
token = append(token, ch)
|
|
}else if state == ReadHole {
|
|
if currlock == nil { error("unreachable for currlock") }
|
|
|
|
idx := len(currlock.holes) - 1
|
|
tmp := currlock.holes[idx]
|
|
currlock.holes = currlock.holes[:idx]
|
|
if len(token) > 0 {
|
|
pos := len(tmp.keys) - 1
|
|
tmp.keys[pos] = append(tmp.keys[pos], token_str())
|
|
}
|
|
tmp.keys = append(tmp.keys, make([]string, 0))
|
|
currhole = &LockHole{name: tmp.name, keys: tmp.keys}
|
|
state = ReadKey
|
|
}else if state == WaitPart {
|
|
}else if state == NeedPart {
|
|
state = WaitPart
|
|
}else { error(fmt.Sprintf("error occur ; on %d", state)) }
|
|
case ' ':
|
|
if state == ReadLock || state == ReadHole {
|
|
token = append(token, ch)
|
|
}else if state == NeedPart {
|
|
state = WaitPart
|
|
}else {}
|
|
default:
|
|
if state == ReadLock || state == ReadHole {
|
|
token = append(token, ch)
|
|
}else if state == ReadKey {
|
|
if strings.Contains("0123456789abcdefABCDEF", string(ch)) {
|
|
token = append(token, ch)
|
|
} else if currhole != nil {
|
|
if strings.ToLower(currhole.name) == "text" {
|
|
token = append(token, ch)
|
|
}
|
|
} else { error(fmt.Sprintf("error occur %c on %d", ch, state)) }
|
|
}else if state == WaitPart {
|
|
}else if state == NeedPart {
|
|
state = WaitPart
|
|
} else { error(fmt.Sprintf("error occur %c on %d", ch, state))}
|
|
}
|
|
}
|
|
|
|
// done
|
|
var done = func() {
|
|
if state != WaitPart && state != NeedPart {
|
|
error(fmt.Sprintf("unexpected end of file %d in %s", state, order))
|
|
}
|
|
}
|
|
|
|
for _, ch := range datas {
|
|
incr(ch)
|
|
}
|
|
done()
|
|
return retval, nil
|
|
}
|
|
|
|
func transform(list []Serial) []Cheat {
|
|
var retval []Cheat
|
|
for _, game := range list {
|
|
file := fmt.Sprintf("./gba/%s.u8", game.order)
|
|
if _, err := os.Stat(file); os.IsNotExist(err) {
|
|
// fmt.Println(file, " not found")
|
|
retval = append( retval, Cheat{ game.serial, make([]Packed, 0) } )
|
|
continue
|
|
}
|
|
|
|
bytes, err := os.ReadFile(file)
|
|
if err != nil || len(bytes) == 0 {
|
|
retval = append( retval, Cheat{ game.serial, make([]Packed, 0) } )
|
|
} else {
|
|
cheats, err := parse(bytes, game.serial, game.order)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
retval = append( retval, Cheat{ game.serial, make([]Packed, 0) } )
|
|
} else {
|
|
retval = append( retval, Cheat{ game.serial, cheats } )
|
|
}
|
|
}
|
|
}
|
|
return retval
|
|
}
|
|
|
|
func format( cheats []Cheat ) []byte {
|
|
sort.Slice( cheats, func(i, j int) bool {
|
|
return cheats[i].serial < cheats[j].serial
|
|
})
|
|
fmt.Println("valid rom has", len(cheats))
|
|
|
|
var step1 = func() ([]byte, []uint16, uint32, uint32) {
|
|
item1, item2 := make([]byte, 0), make([]uint16, 0)
|
|
item3, item4, item5, last := uint32(0), uint32(0), "", uint32(0)
|
|
for _, ch := range cheats {
|
|
serial, cheat := ch.serial, ch.cheats
|
|
val := serial[0:3]
|
|
if item5 != val {
|
|
if len(item2) > 0 && item3 - last > item4 {
|
|
item4 = item3 - last
|
|
}
|
|
item1 = append(item1, []byte(val)...)
|
|
item2 = append(item2, uint16(item3))
|
|
last = item3
|
|
item5 = val
|
|
}
|
|
item3 += uint32(len(cheat))
|
|
}
|
|
if len(item2) > 0 {
|
|
if item3 - last > item4 {
|
|
item4 = item3 - last
|
|
}
|
|
item2 = append(item2, uint16(item3))
|
|
}
|
|
return item1, item2, item3, item4
|
|
}
|
|
sers, offs, chtc, maxl := step1()
|
|
|
|
var step2 = func() ([]uint32, []byte) {
|
|
item1, item2 := make([]uint32, 0), make([]byte, 0)
|
|
item3 := align(uint32(8 + len(sers) + len(offs)*2 + int(chtc) * 8), 32)
|
|
for _, ch := range cheats {
|
|
serial, cheat := ch.serial, ch.cheats
|
|
val := serial[3]
|
|
|
|
for _, cht := range cheat {
|
|
id, bin := cht.code, cht.data
|
|
off := item3 + uint32(len(item2))
|
|
item1 = append(item1, uint32(val) | (off<<3), id)
|
|
item2 = append(item2, bin...)
|
|
}
|
|
}
|
|
return item1, item2
|
|
}
|
|
chts, expanded := step2()
|
|
fmt.Printf("name: %d cheats: %d maxl: %d", len(sers), chtc, maxl)
|
|
|
|
serialbase := 8
|
|
offsetbase := serialbase + len(sers)
|
|
cheatbase := offsetbase + len(offs) * 2
|
|
cheatend := cheatbase + len(chts) * 4
|
|
expandbase := align(uint32(cheatend), 32)
|
|
total := int(expandbase) + len(expanded)
|
|
|
|
retval := make([]byte, total)
|
|
retval[0] = 'A'
|
|
retval[1] = 'C'
|
|
retval[2] = 'L'
|
|
retval[3] = 1
|
|
|
|
binary.Encode(retval[4:], binary.LittleEndian, uint16(len(sers)/3))
|
|
binary.Encode(retval[6:], binary.LittleEndian, uint16(maxl))
|
|
|
|
copy(retval[serialbase:], sers)
|
|
|
|
for i, off := range offs {
|
|
binary.Encode(retval[offsetbase+i*2:], binary.LittleEndian, off)
|
|
}
|
|
for i, ch := range chts {
|
|
binary.Encode(retval[cheatbase+i*4:], binary.LittleEndian, ch)
|
|
}
|
|
copy(retval[cheatbase:], expanded)
|
|
|
|
return retval
|
|
}
|
|
|
|
func main() {
|
|
list, err := loadlist()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
roms := transform(list)
|
|
fmt.Println("all rom has", len(roms))
|
|
|
|
idx := make(map[string][]Packed, 0)
|
|
for _, rom := range roms {
|
|
if len(rom.cheats) == 0 {
|
|
continue
|
|
}
|
|
|
|
if _, ok := idx[rom.serial]; !ok {
|
|
idx[rom.serial] = make([]Packed, 0)
|
|
}
|
|
|
|
idx[rom.serial] = append(idx[rom.serial], rom.cheats...)
|
|
}
|
|
|
|
cheats := make([]Cheat, 0)
|
|
for serial, chts := range idx {
|
|
cheats = append(cheats, Cheat{ serial, chts })
|
|
}
|
|
|
|
content := format( cheats )
|
|
os.WriteFile("gba.acl", content, 0644)
|
|
} |