mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-06 05:44:11 +08:00
c#测试,大概是Rus的1.5-1.6倍的运行时间
This commit is contained in:
parent
57d91b5499
commit
5e503b55ff
634
tools/cheat-builder/make-acl.cs
Normal file
634
tools/cheat-builder/make-acl.cs
Normal file
@ -0,0 +1,634 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user