634 lines
24 KiB
C#

using System;
using System.IO;
using System.Text.Json;
using System.Linq;
using System.Collections;
using System.Text;
using System.Runtime.InteropServices;
namespace CheatTest;
using Module = Memory<byte>;
using LockKey = List<string>;
using LockHole = (string, List< List<string> >);
struct Lock {
public string name;
public List<LockHole> holes;
}
enum ParserState {
WaitLock,
ReadLock,
WaitHole,
ReadHole,
ReadKey,
WaitPart,
NeedPart,
}
enum EntType {
Names,
Cmds,
}
class Program {
static string[] PASSHOLE = { "off", "text" };
static uint Align(uint n, uint basic) {
var ext = n % basic;
return ext == 0 ? n : n + basic - ext;
}
static int FindIndex( List<byte> t, byte[] tr ) {
var rlen = tr.Length;
var tlen = t.Count;
var limit = tlen - rlen;
for( var i=0; i < limit; ++i ){
if( t[i+rlen] != 0 ) continue;
var eq = true;
for( var j=0; j < rlen; ++j ){
if( t[i+j] != tr[j] ){
eq = false;
break;
}
}
if( eq ) return i;
}
return -1;
}
static List<uint> AssembleCheat( List<LockKey> list, Dictionary<uint, ushort> dup, ushort hole ){
Func<string, uint> fromTaddr = (string taddr) => {
var n = Convert.ToUInt32(taddr, 16);
if( n == 0 || n > 0x41fffff) {
Console.WriteLine($"get an invalid address {taddr}");
Environment.Exit(-1);
}
if( n <= 0x3ffff ){
return n | 0x2000000;
}
else if( 0 == (n&0xf000000) ){
return (n & 0xffff) | 0x3000000;
}
else return n;
};
var addrval = new Dictionary<uint, uint>();
foreach( var command in list ){
if( command.Count == 0 ){
continue;
}
var addr = fromTaddr( command[0] );
var len = command.Count - 1;
for( uint pos = 0; pos < len; ++pos ){
addrval[addr+pos] = Convert.ToUInt32(command[(int)pos+1], 16);
}
}
var ordered = addrval.Select(kvp => (kvp.Key, kvp.Value)).ToList();
ordered.Sort(
( (uint,uint) a, (uint,uint) b ) => {
return (int)a.Item1 - (int)b.Item1;
}
);
var blocks = new List<uint>();
(uint, uint, uint) curr = (0,0,0);
foreach( var (addr, value) in ordered){
dup.TryAdd(addr, hole);
if( curr.Item3 == 0 ){
curr = (addr, value, 1);
}
else {
if( curr.Item2 == value && curr.Item1 + curr.Item3 == addr ){
++curr.Item3;
}
else {
blocks.Add( curr.Item1 );
blocks.Add( (curr.Item3<<8) | curr.Item2 );
curr = (addr, value, 1);
}
}
}
if( curr.Item3 != 0 ){
blocks.Add( curr.Item1 );
blocks.Add( (curr.Item3<<8) | curr.Item2 );
}
return blocks;
}
static (uint, Memory<byte>) Lade( List<Lock> locks, string serial, string order ) {
var rootstr = new List<string>();
var names = new List<string>();
var enttable = new List<(uint, EntType, List<string>?, List<LockKey>?)>();
Func<uint, uint, uint> makeID = (uint a, uint b) => a | (b << 16);
for (var i = 0; i < locks.Count; ++i){
Lock l = locks[i];
rootstr.Add(l.name);
names.Add(l.name);
var holestr = new List<string>();
bool colname = l.holes.Count > 1;
for (var index=0; index < l.holes.Count; ++index){
var (name, cmd) = l.holes[index];
holestr.Add(name);
if (colname) names.Add(name);
var id = makeID((uint)(i + 1), (uint)(index + 1));
enttable.Add( (id, EntType.Cmds, null, cmd) );
}
enttable.Add( (makeID((uint)(i + 1), 0), EntType.Names, holestr, null) );
}
enttable.Add( (makeID(0, 0), EntType.Names, rootstr, null) );
if(enttable.Count > 0xffff) {
throw new Exception($"Too many entries in file {order}");
}
enttable = enttable.OrderBy(n => n.Item1).ToList();
names.Sort( (string a, string b) => {
if( a.Length == b.Length ){
return a.CompareTo(b);
}
else return b.Length - a.Length;
} );
List<byte> strtable = new List<byte>(1024);
List<ushort> idxtable = new List<ushort>(128);
List<uint> cmdtable = new List<uint>(2048);
var strcache = new Dictionary<string, ushort>();
Func<byte[], ushort> ut_push = (byte[] tr) => {
var off = strtable.Count;
strtable.EnsureCapacity( off + tr.Length + 1 );
strtable.AddRange( tr );
strtable.Add(0);
return (ushort)off;
};
Func<string, ushort> ut_addr = (string tr) => {
if( strcache.ContainsKey(tr) ){
return (ushort)strcache[tr];
}
else {
var code = Encoding.UTF8.GetBytes(tr);
var idx = FindIndex(strtable, code);
ushort off = (ushort)(idx < 0 ? ut_push(code) : idx);
strcache[tr] = off;
return off;
}
};
Func<List<string>, ushort[]> addString = (List<string> list) => {
var retval = new ushort[list.Count];
for( var i=0; i < list.Count; i++ ){
retval[i] = ut_addr(list[i]);
}
return retval;
};
Func<ushort[], ushort> addStrIndex = (ushort[] list) => {
if( list.Length == 0 ) return 0;
if( strtable.Count > 0xffff ){
Console.WriteLine($"string table is too big {order}");
}
var ret = idxtable.Count;
idxtable.EnsureCapacity( ret + list.Length );
idxtable.AddRange( list );
return (ushort)(ret * 2);
};
Func<List<uint>, uint> addCommand = (List<uint> list) => {
var ret = cmdtable.Count;
cmdtable.EnsureCapacity( ret + list.Count );
cmdtable.AddRange( list );
return (uint)(ret * 4);
};
addString(names);
var entbytelist = new List<(uint, uint, uint)>();
var emptylist = new List<string>();
var holedup = new Dictionary<uint, ushort>();
foreach( var (id, type, labels, cmds) in enttable ){
if( type == EntType.Names ){
var idxlist = addString( id == 0 ? labels : labels.Count > 1 ? labels : emptylist);
var location = addStrIndex( idxlist );
entbytelist.Add(((uint, uint, uint))(id, location, idxlist.Length));
}
else {
var armbytecode = AssembleCheat( cmds, holedup, (ushort)(id & 0xffff));
var location = addCommand( armbytecode );
entbytelist.Add(((uint, uint, uint))(id, location, armbytecode.Count));
}
}
uint code = 0;
foreach( var ch in serial ){
code = (code<<8) | ((uint)ch & 0xff);
}
foreach( var cmd in cmdtable ){
code = code ^ cmd;
}
return (code, Pack( entbytelist, strtable, idxtable, cmdtable ));
}
static Memory<byte> Pack( List<(uint,uint,uint)> entlist, List<byte> strlist, List<ushort> idxlist, List<uint> cmdlist ){
uint entrysize = 12;
uint strbase = (uint)(2 + entlist.Count * entrysize);
uint idxbase = (uint)(strbase + strlist.Count);
uint cmdbase = (uint)(idxbase + idxlist.Count*2);
uint nonalign_size = (uint)(cmdbase + cmdlist.Count * 4);
uint size = Align(nonalign_size, 32);
var result = new Memory<byte>(new byte[size]);
var span = result.Span;
MemoryMarshal.Write<ushort>( span.Slice(0, 2), (ushort)entlist.Count );
for(var i=0; i < entlist.Count; ++i){
var entry = entlist[i];
var offset = 2 + i * entrysize;
var locbase = entry.Item1 > 0xffff ? cmdbase : idxbase;
MemoryMarshal.Write<uint>( span.Slice((int)offset, 4), entry.Item1 );
MemoryMarshal.Write<uint>( span.Slice((int)(offset + 4), 4), (uint)(locbase + entry.Item2) );
MemoryMarshal.Write<uint>( span.Slice((int)(offset + 8), 4), entry.Item3 );
}
// string table
CollectionsMarshal.AsSpan( strlist ).CopyTo( span.Slice((int)strbase, strlist.Count ) );
// idxes table
MemoryMarshal.AsBytes( CollectionsMarshal.AsSpan(idxlist) )
.CopyTo( span.Slice((int)idxbase, idxlist.Count * 2) );
// inst table
MemoryMarshal.AsBytes( CollectionsMarshal.AsSpan(cmdlist) )
.CopyTo( span.Slice((int)cmdbase, cmdlist.Count * 4) );
return result;
}
static List<(uint, Memory<byte>)> Parse( string data, string serial, string order ) {
var retval = new List<(uint, Memory<byte>)>();
var state = ParserState.WaitLock;
var token = new List<char>();
var line = 1;
var locks = new List<Lock>();
Lock? currlock = null;
LockHole? currhole = null;
Func<string> token_str = () => {
var r = new string( CollectionsMarshal.AsSpan(token) );
token.Clear();
return r;
};
Action<string> error = (string msg) => {
Console.WriteLine($"Error<{msg}> at line {line}: {msg}");
Environment.Exit(1);
};
Action<char> incr = (char ch) => {
switch(ch) {
case '[': {
if(state == ParserState.WaitLock || state == ParserState.ReadHole || state == ParserState.NeedPart ){
state = ParserState.ReadLock;
if( currlock != null ) {
locks.Add( currlock.Value );
}
currlock = new Lock();
}
else if(state == ParserState.WaitPart) { }
else error($"error occur [ on {state}");
}
break;
case ']': {
if(state == ParserState.ReadLock) {
state = ParserState.WaitHole;
var name = token_str();
if(name.ToLower() == "gameinfo") {
retval.Add( Lade(locks, serial, order) );
state = ParserState.WaitPart;
locks = new List<Lock>();
currhole = null;
currlock = null;
}
else if( name.Length > 0 ) {
currlock = new Lock {
name = name,
holes = new List<LockHole>()
};
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}");
}
break;
case '\r': case '\n': {
if(state == ParserState.WaitHole) {
state = ParserState.ReadHole;
}
else if( state == ParserState.ReadKey){
var token = token_str();
var pass = false;
if( currhole != null ){
var (name, keys) = currhole.Value;
if( token.Length > 0 ){
keys.Last().Add(token);
}
if( PASSHOLE.Contains(name.ToLower()) ){
pass = true;
}
}
if( !pass ){
if( currlock != null ){
if( currhole == null ) error("不应该来到这里");
currlock.Value.holes.Add( currhole.Value );
}
currhole = null;
}
state = ParserState.ReadHole;
}
else if(state == ParserState.ReadLock){
error($"error occur newline on {state}");
}
else if(state == ParserState.ReadHole){
if(token.Count > 0 && token.Find(c => c != ' ' && c != '\t') != 0 ){
error($"error occur newline on {state}");
}
else token.Clear();
}
else if(state == ParserState.WaitPart){
state = ParserState.NeedPart;
}
else {}
if( ch == '\n' ) ++line;
}
break;
case '=': {
if(state == ParserState.ReadHole){
state = ParserState.ReadKey;
currhole = (token_str(), new List<List<string>>{ new List<string>() });
}
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}");
}
break;
case ',': {
if( state == ParserState.ReadKey ){
if( token.Count > 0 ){
if( currhole == null ) error("不应该来到这里");
var token = token_str();
var (name, keys) = currhole.Value;
keys.Last().Add(token);
}
}
else if( state == ParserState.ReadHole ){
var (name, cmd) = currlock.Value.holes.Last();
currlock.Value.holes.RemoveAt(currlock.Value.holes.Count-1);
if( token.Count > 0 ) cmd.Last().Add(token_str());
currhole = (name, cmd);
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}");
}
break;
case ';': {
if( state == ParserState.ReadKey){
if(token.Count > 0) currhole.Value.Item2.Last().Add(token_str());
currhole.Value.Item2.Add(new List<string>());
}
else if( state == ParserState.ReadLock){
token.Add( ch );
}
else if( state == ParserState.ReadHole){
var (name, cmd) = currlock.Value.holes.Last();
currlock.Value.holes.RemoveAt(currlock.Value.holes.Count-1);
if( token.Count > 0 ) cmd.Last().Add(token_str());
cmd.Add(new List<string>());
currhole = (name, cmd);
state = ParserState.ReadKey;
}
else if( state == ParserState.WaitPart) {}
else if( state == ParserState.NeedPart ) {
state = ParserState.WaitPart;
}
else error($"error occur ; on {state}");
}
break;
case ' ': {
if( state == ParserState.ReadLock || state == ParserState.ReadHole ){
token.Add( ch );
}
else if( state == ParserState.NeedPart ) {
state = ParserState.WaitPart;
}
else {}
}
break;
default: {
if( state == ParserState.ReadLock || state == ParserState.ReadHole ){
token.Add( ch );
}
else if( state == ParserState.ReadKey ){
if( "0123456789abcdefABCDEF".Contains(ch) ){
token.Add( ch );
}
else if(currhole != null) {
if(currhole.Value.Item1 == "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}");
}
break;
}
};
Action done = () => {
if (state != ParserState.WaitPart && state != ParserState.NeedPart) {
error($"Unexpected end of file {state} in {order}");
}
};
foreach (char ch in data){
incr(ch);
}
done();
return retval;
}
static List<(string, List<(uint, Memory<byte>)>)> Transform( List<(string, string)> list ) {
var retval = new List<(string, List<(uint, Memory<byte>)>)>();
foreach (var (order, serial) in list) {
var file = $"./gba/{order}.u8";
try{
var chtdata = File.ReadAllText(file);
if (chtdata.Length > 0) {
var cheats = Parse( chtdata, serial, order );
retval.Add((serial, cheats));
}
else {
//Console.WriteLine($"{order} is empty");
retval.Add((serial, new List<(uint, Memory<byte>)>()));
}
}
catch( Exception e ){
Console.WriteLine($"{e} not found");
retval.Add((serial, new List<(uint, Memory<byte>)>()));
}
}
return retval;
}
static List<(string, string)> LoadList() {
var retval = new List<(string, string)>();
var file_content = File.ReadAllText("serial.json");
using (JsonDocument doc = JsonDocument.Parse(file_content))
{
JsonElement root = doc.RootElement;
HashSet<string> set = new HashSet<string>();
foreach (var item in root.EnumerateObject())
{
string? serial = item.Value.GetString();
if( set.Contains(item.Name) ) Console.WriteLine($"重复 {item.Name}");
else if (serial != null && serial != "????") {
set.Add( item.Name );
retval.Add(( item.Name, serial ));
}
}
}
return retval;
}
static byte[] Format( List<(string, List<(uint, Module)>)> cheats ) {
cheats.Sort( ((string, List<(uint, Module)>) a, (string, List<(uint, Module)>) b) => {
return a.Item1.CompareTo(b.Item1);
} );
Console.WriteLine($"valid rom has {cheats.Count}");
List<byte> sers;
List<ushort> offs;
uint chtc;
uint maxl;
{
(List<byte>, List<ushort>, uint, uint, string) ret = (new List<byte>(), new List<ushort>(), 0, 0, "");
uint last = 0;
foreach(var (serial, cheat) in cheats){
var val = serial.Substring(0, 3);
if( ret.Item5 != val ){
if( ret.Item2.Count > 0 && ret.Item3 - last > ret.Item4 ){
ret.Item4 = ret.Item3 - last;
}
ret.Item1.AddRange( Encoding.ASCII.GetBytes(val) );
ret.Item2.Add((ushort)ret.Item3);
last = ret.Item3;
ret.Item5 = val;
}
ret.Item3 += (uint)cheat.Count;
}
if( ret.Item2.Count > 0 ){
if( ret.Item3 - last > ret.Item4 ){
ret.Item4 = ret.Item3 - last;
}
ret.Item2.Add((ushort)ret.Item3);
}
sers = ret.Item1;
offs = ret.Item2;
chtc = ret.Item3;
maxl = ret.Item4;
}
List<uint> chts;
List<byte> expanded;
{
(List<uint>, List<byte>, uint) ret = (new List<uint>(), new List<byte>(), Align((uint)(8 + sers.Count + offs.Count*2 + chtc * 8), 32));
foreach(var (serial, cheat) in cheats){
var val = serial[3];
foreach(var (id, bin) in cheat) {
var off = ret.Item3 + ret.Item2.Count;
ret.Item1.Add((uint)((val) | (off<<3)));
ret.Item1.Add( id );
ret.Item2.EnsureCapacity( ret.Item2.Count + bin.Length );
ret.Item2.AddRange( MemoryMarshal.ToEnumerable<byte>(bin) );
}
}
chts = ret.Item1;
expanded = ret.Item2;
}
Console.WriteLine($"name: {sers.Count} cheats: {chtc} maxl: {maxl}");
var serialbase = 8;
var offsetbase = serialbase + sers.Count;
var cheatbase = offsetbase + offs.Count * 2;
var expandbase = Align((uint)(cheatbase + chts.Count*4), 32 );
var total = expandbase + expanded.Count;
var retval = new byte[total];
var output = new Memory<byte>(retval);
var span = output.Span;
span[0] = (byte)'A';
span[1] = (byte)'C';
span[2] = (byte)'L';
span[3] = 1;
MemoryMarshal.Write<ushort>(span.Slice(4, 2), (ushort)(sers.Count / 3));
MemoryMarshal.Write<ushort>(span.Slice(6, 2), (ushort)maxl);
CollectionsMarshal.AsSpan( sers ).CopyTo( span.Slice(serialbase, sers.Count) );
MemoryMarshal.AsBytes( CollectionsMarshal.AsSpan(offs) )
.CopyTo( span.Slice(offsetbase, cheatbase - offsetbase) );
MemoryMarshal.AsBytes( CollectionsMarshal.AsSpan(chts) )
.CopyTo( span.Slice(cheatbase, chts.Count * 4) );
CollectionsMarshal.AsSpan( expanded ).CopyTo( span.Slice((int)expandbase, expanded.Count) );
return retval;
}
static void Main(string[] args) {
var list = LoadList();
var roms = Transform(list);
Console.WriteLine($"all rom has {roms.Count}");
var idx = new Dictionary<string, List<(uint, Module)>>();
foreach( var (serial, cheat) in roms ){
if(cheat.Count == 0 ){
continue;
}
if( !idx.ContainsKey(serial) ){
idx[serial] = new List<(uint, Module)>();
}
idx[serial].AddRange( cheat );
}
var content = Format( idx.Select(g => (g.Key, g.Value)).ToList() );
File.WriteAllBytes("gba.acl", content);
}
}