use std::fs; use serde_json::Value; use byteorder::{LittleEndian, ByteOrder}; use std::collections::HashMap; use std::collections::BTreeMap; type Module = Vec; type LockKey = Vec; type LockHole = (String, Vec< LockKey >); #[derive(Clone)] #[derive(Default)] struct Lock { name: String, holes: Vec< LockHole >, } #[derive(Debug)] enum ParserState { WaitLock, ReadLock, WaitHole, ReadHole, ReadKey, WaitPart, NeedPart, } const PASSHOLE: [&str; 2] = ["text", "off"]; const HEX: &str = "0123456789abcdefABCDEF"; struct ParserCtx<'a>{ state: ParserState, token: Vec, line: u32, file: &'a String, code: u32, locks: Vec< Lock >, currlock: Option, currhole: Option, output: &'a mut Vec< (u32, Module) >, } fn token_str( ctx: &mut ParserCtx ) -> String { if ctx.token.len() == 0 { "".to_string() } else { let retval = String::from_iter( &ctx.token ); ctx.token.clear(); retval } } fn error( ctx: &ParserCtx, str: String ) { if ctx.currlock.is_some() { panic!("{} at line: {} in lock {:?} [{}]", str, ctx.line, ctx.currlock.iter().next().unwrap().name, ctx.file); } else { panic!("{} at line: {} in lock {:?} [{}]", str, ctx.line, "".to_string(), ctx.file); } } enum EntData<'a> { Names(Vec<&'a String>), Cmds(&'a Vec), } struct LadeCtx { strtable: Vec, idxtable: Vec, cmdtable: Vec, strcache: HashMap, } #[inline(always)] fn make_id( a: usize, b: usize ) -> u32 { (a | b<<16).try_into().unwrap() } fn find_index( t: &Vec, tr: &[u8] ) -> i16 { let rlen = tr.len(); if rlen > t.len() { return -1; } let limit = t.len() - rlen; for i in 0..limit { if t[i + rlen] != 0 { continue } let mut eq = true; for j in 0..rlen { if t[i+j] != tr[j] { eq = false; break; } } if eq { return i.try_into().unwrap(); } } -1 } fn ut_push( t: &mut Vec, tr: &[u8] ) -> u16 { let off = t.len(); t.reserve( tr.len() + 1 ); t.extend_from_slice(tr); t.push(0); off.try_into().unwrap() } fn add_string( list: &Vec<&String>, ctx: &mut LadeCtx ) -> Vec { let mut ret: Vec = Vec::with_capacity(list.len()); for tr in list.iter() { let res = ctx.strcache.get(&tr.to_string()); if let Some(pos) = res { ret.push( *pos ); } else { let code: &[u8] = tr.as_bytes(); let off: i16 = find_index(&ctx.strtable, code); let res: u16 = if off < 0 { ut_push(&mut ctx.strtable, code) } else { off.try_into().unwrap() }; ctx.strcache.insert(tr.to_string(), res); ret.push( res ); } } return ret; } fn add_str_index( list: &Vec, ctx: &mut LadeCtx ) -> u32 { if list.len() == 0 { return 0; } if ctx.strtable.len() > 0xffff { panic!("string table is too big.") } let ret: u32 = ctx.idxtable.len().try_into().unwrap(); ctx.idxtable.reserve( list.len() ); ctx.idxtable.extend( list.iter() ); return ret*2; } fn assemble_cheat( list: &Vec, dup: &mut BTreeMap, hole: u16, order: &String ) -> Vec { let from_t_addr = |taddr| { match u32::from_str_radix(taddr, 16) { Ok(n) => { if n == 0 || n > 0x41fffff { panic!("get an invalid address: {}", taddr); } if n < 0x3ffff { return n | 0x200_0000; } else if 0 == (n & 0xf000000) { return (n&0xffff) | 0x3000000; } else { return n; } }, Err(_e) => panic!("get an invalid address: {} in {}", taddr, order), } }; let mut addrval: BTreeMap = BTreeMap::new(); for command in list.iter() { if command.len() == 0 { continue; } let mut iter = command.iter(); let addr = from_t_addr( iter.next().unwrap() ); let cnt = (command.len() - 1).try_into().unwrap(); for pos in 0..cnt { let r = iter.next().unwrap(); match u32::from_str_radix(r, 16) { Ok(val) => { addrval.insert(addr + pos, val); }, Err(_e) => panic!("cannot get valid cheat val {} in {}", r, order), } } } let mut ordered: Vec<(u32, u32)> = addrval.iter().map(|(&a,&b)| (a,b)).collect(); ordered.sort_by( |a, b| a.0.cmp(&b.0) ); let mut blocks: Vec = Vec::new(); let mut curr: (u32, u32, u32) = (0, 0, 0); for (addr, value) in ordered.iter() { dup.insert(*addr, hole); if curr.2 == 0 { curr = (*addr, *value, 1); } else { if curr.1 == *value && curr.0 + curr.2 == *addr { curr.2 = curr.2 + 1; } else { blocks.push( curr.0 ); blocks.push( (curr.2<<8) | curr.1 ); curr = (*addr, *value, 1); } } } if curr.2 != 0 { blocks.push( curr.0 ); blocks.push( (curr.2<<8) | curr.1 ); } return blocks; } fn add_command( list: &Vec, ctx: &mut LadeCtx ) -> u32 { let ret:u32 = ctx.cmdtable.len().try_into().unwrap(); ctx.cmdtable.reserve( list.len() ); ctx.cmdtable.extend( list.iter() ); return ret * 4; } fn lade( list: &Vec, code: u32, order: &String ) -> (u32, Module) { let mut rootstr: Vec<&String> = Vec::new(); let mut names: Vec<&String> = Vec::new(); let mut enttable: Vec<(u32, EntData)> = Vec::new(); for (i, lock) in list.iter().enumerate() { rootstr.push( &lock.name ); names.push( &lock.name ); let mut holestr: Vec<&String> = Vec::with_capacity( lock.holes.len() ); let colname = lock.holes.len() > 1; for (index, (name, cmd)) in lock.holes.iter().enumerate() { holestr.push( name ); if colname { names.push( name ); } enttable.push( (make_id(i+1, index+1), EntData::Cmds(&cmd)) ); } enttable.push( (make_id(i+1, 0), EntData::Names(holestr)) ); } enttable.push( (make_id(0, 0), EntData::Names(rootstr) ) ); if enttable.len() > 0xffff { panic!("entry count is overflow in file {}", order); } enttable.sort_by( |a, b| a.0.cmp( &b.0 ) ); names.sort_by( |a, b| if a.len() == b.len() { a.cmp(&b) } else { b.len().cmp( &a.len() ) } ); let mut ctx: LadeCtx = LadeCtx{ strtable: Vec::with_capacity( 1024 ), idxtable: Vec::with_capacity( 128 ), cmdtable: Vec::with_capacity( 2048 ), strcache: HashMap::new(), }; add_string(&names, &mut ctx); let mut entbytelist: Vec<(u32, u32, u32)> = Vec::with_capacity( enttable.len() ); let emptylist: Vec<&String> = Vec::new(); let mut holedup: BTreeMap = BTreeMap::new(); for (id, data) in enttable.iter() { match data { EntData::Names( names ) => { let idxlist = add_string( if *id == 0 { names } else { if names.len() > 1 { names } else { &emptylist } }, &mut ctx ); let location = add_str_index( &idxlist, &mut ctx ); entbytelist.push( (*id, location, idxlist.len().try_into().unwrap()) ); }, EntData::Cmds( cmds ) => { let armbytecode = assemble_cheat( cmds, &mut holedup, (id & 0xffff).try_into().unwrap(), &order ); let location = add_command( &armbytecode, &mut ctx ); entbytelist.push( (*id, location, armbytecode.len().try_into().unwrap()) ); }, } } let mut xv = code; for cmd in ctx.cmdtable.iter() { xv = xv ^ cmd; } let bin = pack( entbytelist, &ctx.strtable, &ctx.idxtable, &ctx.cmdtable ); return (xv, bin); } fn align( n: usize, base: usize ) -> usize { let ext = n % base; if ext == 0 { n } else { n + base - ext } } fn pack( entlist: Vec<(u32,u32,u32)>, strlist: &Vec, idxlist: &Vec, cmdlist: &Vec ) -> Module { let entrysize: usize = 12; let strbase = 2 + entlist.len() * entrysize; let idxbase = strbase + strlist.len(); let cmdbase = idxbase + idxlist.len() * 2;// u16=2B let nonalign_size = cmdbase + cmdlist.len() * 4;// u32=4B let size = align(nonalign_size, 32); let mut result: Module = vec![0u8; size]; LittleEndian::write_u16(&mut result[0..2], entlist.len().try_into().unwrap()); for (i, entry) in entlist.iter().enumerate() { let offset = 2 + i * entrysize; let locbase:u32; if entry.0 > 0xffff { locbase = cmdbase.try_into().unwrap(); } else { locbase = idxbase.try_into().unwrap(); } LittleEndian::write_u32(&mut result[offset..offset+4], entry.0); LittleEndian::write_u32(&mut result[offset+4..offset+8], locbase + entry.1); LittleEndian::write_u32(&mut result[offset+8..offset+12], entry.2); } // string table result[strbase..idxbase].copy_from_slice( &strlist ); // idxes table LittleEndian::write_u16_into(idxlist, &mut result[idxbase..cmdbase]); // instruction table LittleEndian::write_u32_into(cmdlist, &mut result[cmdbase..nonalign_size]); return result; } fn incr( ch: char, ctx: &mut ParserCtx ) { match ch { '[' => { match ctx.state { ParserState::WaitLock | ParserState::ReadHole | ParserState::NeedPart => { ctx.state = ParserState::ReadLock; if ctx.currlock.is_some() { let currlock = ctx.currlock.clone().unwrap(); ctx.locks.push( currlock ); } let tmp = Lock { name: "".to_string(), holes: Vec::new(), }; ctx.currlock = Some( tmp ); }, ParserState::WaitPart => {}, _ => error( ctx, format!("error occur [ on {:?}", ctx.state) ), } }, ']' => { match ctx.state { ParserState::ReadLock => { ctx.state = ParserState::WaitHole; let name = token_str(ctx); if name.to_lowercase() == "gameinfo" { ctx.output.push( lade(&ctx.locks, ctx.code, &ctx.file) ); ctx.state = ParserState::WaitPart; ctx.locks = Vec::new(); ctx.currhole = None; ctx.currlock = None; } else if !name.is_empty() { ctx.currlock = Some( Lock { name: name.to_string(), holes: Vec::new(), }); ctx.currhole = None; } else { panic!("empty lock name {}", ctx.file); } }, ParserState::WaitPart => {}, ParserState::NeedPart => { ctx.state = ParserState::WaitPart; }, _ => error( ctx, format!("error occur ] on {:?}", ctx.state) ), } }, '\r' | '\n' => { match ctx.state { ParserState::WaitHole => { ctx.state = ParserState::ReadHole; }, ParserState::ReadKey => { let token = token_str(ctx); let mut pass = false; if let Some(hole) = &mut ctx.currhole { let (name, ref mut keys) = hole; if !token.is_empty() { if let Some(key) = keys.last_mut() { key.push( token ); } } if PASSHOLE.iter().any( |&s| s == name.to_lowercase() ) { pass = true; } } if pass == false { if let Some(lock) = &mut ctx.currlock { lock.holes.push( ctx.currhole.take().unwrap() ); } } ctx.state = ParserState::ReadHole; }, ParserState::ReadLock => { error(ctx, format!("error occur newline on {:?}", ctx.state)); }, ParserState::ReadHole => { if ctx.token.len() > 0 && ctx.token.iter().any( |c| *c != ' ' && *c != '\t' ) { error(ctx, format!("error occur newline on {:?}", ctx.state)); } else { ctx.token.clear(); } }, ParserState::WaitPart => { ctx.state = ParserState::NeedPart; }, _ => {}, } if ch == '\n' { ctx.line += 1; } }, '=' => { match ctx.state { ParserState::ReadHole => { ctx.state = ParserState::ReadKey; ctx.currhole = Some( (token_str(ctx), vec![Vec::new()]) ); }, ParserState::ReadLock => { ctx.token.push( ch ); }, ParserState::WaitPart => {}, ParserState::NeedPart => { ctx.state = ParserState::WaitPart; }, _ => error( ctx, format!("error occur = on {:?}", ctx.state) ), } }, ',' => { match ctx.state { ParserState::ReadKey => { if ctx.token.len() > 0 { let token = token_str(ctx); ctx.currhole.as_mut().map( |hole| { let (_name, ref mut keys) = hole; if !token.is_empty() { if let Some(key) = keys.last_mut() { key.push( token ); } } true } ); } }, ParserState::ReadHole => { // 秘籍数据需要换行才能写完的情况 let token = token_str(ctx); ctx.currlock.as_mut().map( |lock| { ctx.currhole = lock.holes.pop().map( |hole| { let (name, mut keys) = hole; if !token.is_empty() { if let Some(key) = keys.last_mut() { key.push( token ); } } (name, keys) } ); } ); ctx.state = ParserState::ReadKey; } ParserState::ReadLock => { ctx.token.push( ch ); }, ParserState::WaitPart => {}, ParserState::NeedPart => { ctx.state = ParserState::WaitPart; }, _ => error( ctx, format!("error occur , on {:?}", ctx.state) ), } }, ';' => { match ctx.state { ParserState::ReadKey => { let token = token_str(ctx); ctx.currhole.as_mut().map( |hole| { let (_name, ref mut keys) = hole; if !token.is_empty() { if let Some(key) = keys.last_mut() { key.push( token ); } } keys.push( Vec::new() ); true } ); }, ParserState::ReadLock => { ctx.token.push( ch ); }, ParserState::ReadHole => { let token = token_str(ctx); ctx.currlock.as_mut().map( |lock| { ctx.currhole = lock.holes.pop().map( |hole| { let (name, mut keys) = hole; if !token.is_empty() { if let Some(key) = keys.last_mut() { key.push( token ); } } keys.push( Vec::new() ); (name, keys) } ); } ); ctx.state = ParserState::ReadKey; }, ParserState::WaitPart => {}, ParserState::NeedPart => { ctx.state = ParserState::WaitPart; }, _ => error(ctx, format!("error occur ; on {:?}", ctx.state)), } }, ' ' => { match ctx.state { ParserState::ReadLock | ParserState::ReadHole => { ctx.token.push( ch ); }, ParserState::NeedPart => { ctx.state = ParserState::WaitPart; }, _ => {}, } }, _ => { match ctx.state { ParserState::ReadLock | ParserState::ReadHole => { ctx.token.push( ch ); }, ParserState::ReadKey => { if String::from(HEX).contains(ch) == true { ctx.token.push( ch ); } else if let Some(hole) = &ctx.currhole { let (name, _key) = hole; if name == "text" { ctx.token.push( ch ); } } else { error(ctx, format!("error occur {} on {:?}", ch, ctx.state)); } }, ParserState::WaitPart => {}, ParserState::NeedPart => { ctx.state = ParserState::WaitPart; }, _ => error(ctx, format!("error occur {} on {:?}", ch, ctx.state)), } }, } } fn done(ctx: &mut ParserCtx) { match ctx.state { ParserState::WaitPart | ParserState::NeedPart => {}, _ => error( ctx, format!("error occur eof on {:?}", ctx.state) ), } } fn parse( data: String, serial: &String, order: &String ) -> Vec< (u32, Module) > { let mut ret: Vec<(u32, Module)> = Vec::new(); let mut context = ParserCtx { state: ParserState::WaitLock, token: Vec::new(), line: 1, file: order, code: { let mut r:u32 = 0; for v in serial.as_bytes().iter() { let u:u32 = *v as u32; r = u | r << 8; } r }, locks: Vec::new(), currlock: None, currhole: None, output: &mut ret, }; for ch in data.chars() { incr( ch, &mut context ); } done( &mut context ); return ret; } fn loadlist() -> Vec<(String,String)> { let mut retval: Vec<(String, String)> = Vec::new(); let file_content = fs::read_to_string("./serial.json") .expect("Unable to read file"); let json_data: Value = serde_json::from_str(&file_content) .expect("JSON was not well-formatted"); if let Value::Object(map) = json_data { for (key, value) in map.iter() { if let Value::String(serial) = value { if serial != "????" { retval.push( (key.to_string(), serial.to_string()) ); } } } } else { panic!("serial.json with invalid format."); } return retval; } fn transform<'a>( list: &'a Vec<(String, String)> ) -> Vec<(&'a String, Vec<(u32, Module)>)> { let mut retval: Vec<(&String, Vec<(u32, Module)>)> = Vec::new(); for (order, serial) in list.iter() { let file = format!("./gba/{}.u8", order); let chtdata = fs::read_to_string(file).unwrap_or("".to_string()); if chtdata.len() > 0 { let cheats = parse( chtdata.to_string(), serial, order ); retval.push( (serial, cheats) ); } else { println!("no data {}", order); } } return retval; } fn format<'a>( mut cheats: Vec<(&'a String, Vec<(u32, Module)>)> ) -> Vec { cheats.sort_by( |a, b| a.0.cmp(&b.0) ); println!("valid rom has {}", cheats.len()); let (sers, offs, chtc, maxl, _) = { let mut ret: (Vec, Vec, usize, usize, String) = (vec![], vec![], 0, 0, "".to_string()); let mut last: usize = 0; for game in cheats.iter() { let (serial, cheat) = game; let val = serial.get(0..3).expect("not valid serial"); if ret.4.ne(val) { if ret.1.len() > 0 && ret.2 - last > ret.3 { ret.3 = ret.2 - last; } ret.0.extend( val.as_bytes() ); ret.1.push( ret.2.try_into().unwrap() ); last = ret.2; ret.4 = val.to_string(); } ret.2 = ret.2 + cheat.len(); } if ret.1.len() > 0 { if ret.2 - last > ret.3 { ret.3 = ret.2 - last; } ret.1.push( ret.2.try_into().unwrap() ); } ret }; let (cheats, expanded, _) = { let mut ret: (Vec, Vec, usize) = (vec![], vec![], align(8 + sers.len() + offs.len()*2 + chtc * 8, 32)); for game in cheats.iter() { let (serial, cheat) = game; let val = serial.chars().nth(3).expect("invalid serial"); for (id, bin) in cheat.iter() { let off: u32 = (ret.2 + ret.1.len()).try_into().unwrap(); ret.0.push( (val as u32) | (off << 3) ); ret.0.push( *id ); ret.1.extend( bin ); } } ret }; println!("name: {} cheats: {} maxl: {}", sers.len(), chtc, maxl); let serialbase = 8; let offsetbase = serialbase + sers.len(); let cheatbase = offsetbase + offs.len() * 2; let expandbase = align( cheatbase + cheats.len() * 4, 32 ); let total = expandbase + expanded.len(); let mut output: Vec = vec![0u8; total]; output[0..4].copy_from_slice(&['A' as u8, 'C' as u8, 'L' as u8, 1]); LittleEndian::write_u16(&mut output[4..6], (sers.len() / 3).try_into().unwrap()); LittleEndian::write_u16(&mut output[6..8], maxl.try_into().unwrap()); output[serialbase..offsetbase].copy_from_slice( &sers ); LittleEndian::write_u16_into(&offs, &mut output[offsetbase..cheatbase]); LittleEndian::write_u32_into(&cheats, &mut output[cheatbase..cheatbase+cheats.len()*4]); output[expandbase..expandbase+expanded.len()].copy_from_slice( &expanded ); return output; } fn main() { let list = loadlist(); let roms = { let ret = transform( &list ); let mut idx: BTreeMap<&String, Vec<(u32, Module)>> = BTreeMap::new(); for (serial, cheat) in ret.into_iter() { if cheat.len() == 0 { continue; } idx.entry(serial).or_insert_with(Vec::new).extend( cheat ); } idx.into_iter().collect() }; let content = format( roms ); let _ = fs::write("gba.acl", content); }