#include "arm11/acl.h" #include "fs.h" #include #include extern Result fReadSize( FHandle file, void *buff, unsigned size, uint32_t *readout ); typedef struct { uint32_t typeloc; uint32_t cfgid; } CheatCfg; typedef struct { char *aclfile; // acl文件 uint16_t serc; // serial前3字节总数量 CheatCfg *list; // 调用select_game后保存serial对应的一组cheat配置 uint16_t listc; // list的个数 uint32_t setid; // 选中的cheat_set的id } CheatLib; typedef struct { uint32_t entid; // entry的id uint32_t offset; // entry对应数据在cheat_set的偏移 uint32_t datasz; // 此entry对应的数据长度(hole对应key数量,key对应gamecode数量) } CheatEntry; typedef struct { uint32_t seek; // 在acl文件中的基础偏移 uint32_t entc; // 此cheat_set所有的entry总数 CheatEntry entry; // 当选选中的entry的数据 char *strings; // 字符串表 void *entdata; // 当前选中的entry对应的列表数据 } CheatSet; static CheatLib gblcht = {0, 0, NULL, 0, 0}; static CheatSet gblset = {0, 0, {0,0,0}, NULL, NULL}; #define ACL_MAGIC_CODE 0x4c4341 #define ACL_GBA_CODELEN 4 #define ACL_HEADER_LEN 8 #define ACL_SERIAL_LEN(n) ((n)*3) #define ACL_INDEX_OFFSET(i) ((i)*sizeof(uint16_t)) #define ACL_CONFIG_OFFSET(i) ((i)*sizeof(CheatCfg)) #define serial_count(n) ((n) & 0xffff) #define max_cheat_count(n) ((n) >> 16) acl_error_t acl_open_lib( acl_text_t filename ) { FHandle cheatfile; uint32_t readed; if( RES_OK != fOpen(&cheatfile, filename, FA_OPEN_EXISTING | FA_READ) ) return ACHTLIB_NOT_FOUND; // 验证magic number uint32_t num; if( RES_OK != fRead(cheatfile, &num, sizeof(num), &readed) || readed != sizeof(num) ) { fClose( cheatfile ); return ACHTLIB_INVALID; } if ( (num & 0xffffff) != ACL_MAGIC_CODE ) { fClose( cheatfile ); return ACHTLIB_INVALID; } if( RES_OK != fRead( cheatfile, &num, sizeof(num), &readed) || readed != sizeof( num ) ) { fClose( cheatfile ); return ACHTLIB_INVALID; } CheatCfg *cfglist = (CheatCfg*)malloc( max_cheat_count(num) * sizeof(CheatCfg) ); if( cfglist == NULL ) { fClose( cheatfile ); return ACHTLIB_NOMEM; } cfglist[0].cfgid = 0; int namelen = strlen(filename); char *aclfile = (char*)malloc( 1+namelen ); if( aclfile == NULL ) { free( cfglist ); fClose( cheatfile ); return ACHTLIB_NOMEM; } memcpy( aclfile, filename, namelen ); aclfile[namelen] = '\0'; gblcht.aclfile = aclfile; gblcht.serc = serial_count(num); gblcht.list = cfglist; fClose(cheatfile); return ACHTLIB_SUCCESS; } acl_error_t acl_close_lib( void ) { if( gblcht.serc != 0 ) { free( gblcht.aclfile ); gblcht.aclfile = NULL; free( gblcht.list ); gblcht.list = NULL; gblcht.serc = gblcht.listc = 0; if( gblset.strings ){ free( gblset.strings ); gblset.strings = NULL; } if( gblset.entdata ){ free( gblset.entdata ); gblset.entdata = NULL; } return ACHTLIB_SUCCESS; } else return ACHTLIB_INVALID; } static int32_t bin_search( FHandle fd, acl_text_t game ) { if( RES_OK != fLseek(fd, ACL_HEADER_LEN) ){ return -ACHTLIB_INVALID; } uint32_t size = ACL_SERIAL_LEN(gblcht.serc); char *serials = (char*)malloc( size ); if( serials == NULL ){ return -ACHTLIB_NOMEM; } uint32_t readed; if( RES_OK != fReadSize(fd, serials, size, &readed) || readed != size ) { free( serials ); return -ACHTLIB_INVALID; } int start = 0, end = gblcht.serc - 1; size = ACL_SERIAL_LEN(1); int found = -1; while( start <= end ) { int mid = (start+end) >> 1; int res = strncmp( game, serials+ACL_SERIAL_LEN(mid), size ); if( res == 0 ) { found = mid; break; } else if( res > 0 )// serial < game { start = mid + 1; } else end = mid - 1; } free( serials ); return found < 0 ? -ACHTLIB_NOT_FOUND : found; } #define config_type(n) ( (n) & 0xff ) acl_error_t acl_select_game( acl_text_t game, acl_boolean_t filter, acl_count_t *n ) { CheatLib *inst = (CheatLib*)&gblcht; FHandle fd; if( inst->serc == 0 ) return ACHTLIB_NOT_OPEN; if( strlen(game) != ACL_GBA_CODELEN ) return ACHTLIB_NOT_FOUND; if( RES_OK != fOpen(&fd, inst->aclfile, FA_OPEN_EXISTING | FA_READ) ) return ACHTLIB_INVALID; int32_t index = bin_search( fd, game ); if( index < 0 ) { fClose( fd ); return index * -1; } uint32_t offset = ACL_HEADER_LEN + ACL_SERIAL_LEN(inst->serc); offset += ACL_INDEX_OFFSET(index); uint32_t readed; uint16_t groups[2]; if( RES_OK != fLseek(fd, offset) ) { fClose( fd ); return ACHTLIB_INVALID; } if( RES_OK != fRead(fd, groups, sizeof(groups), &readed) || readed != sizeof(groups) ) { fClose( fd ); return ACHTLIB_INVALID; } offset = ACL_HEADER_LEN + ACL_SERIAL_LEN(inst->serc) + ACL_INDEX_OFFSET(inst->serc+1); offset += ACL_CONFIG_OFFSET(groups[0]); int32_t gcount = groups[1] - groups[0]; if( RES_OK != fLseek(fd, offset) ) { fClose( fd ); return ACHTLIB_INVALID; } CheatCfg *cfg = inst->list; if( RES_OK != fRead(fd, cfg, gcount * sizeof(CheatCfg), &readed) // 可能需要多次fread || readed != gcount * sizeof(CheatCfg) ) { fClose( fd ); return ACHTLIB_INVALID; } if( filter ) { char reg = game[3]; index = -1; for( int32_t i=0; i < gcount; ++i ) { if( config_type(cfg[i].typeloc) == reg ) { if( index < 0 ) index = i; } else if( index >= 0 ) { gcount = i-index; memcpy( cfg, cfg+index, gcount * sizeof(CheatCfg) ); break; } } } inst->listc = gcount; inst->setid = 0; if( n != NULL ) *n = gcount; fClose( fd ); return ACHTLIB_SUCCESS; } acl_error_t acl_query_cheat_set( acl_index_t index, acl_chtid_t *chtid, acl_region_t *region ) { CheatLib *inst = &gblcht; if( chtid == NULL && region == NULL ) return ACHTLIB_SUCCESS; if( inst->serc == 0 ) return ACHTLIB_NOT_OPEN; if( inst->listc == 0 ) return ACHTLIB_INVALID; if( index >= inst->listc ) return ACHTLIB_NOT_FOUND; else { CheatCfg *cfg = inst->list; if( chtid!=NULL ) *chtid = cfg[index].cfgid; if( region!=NULL ) *region = config_type( cfg[index].typeloc ); return ACHTLIB_SUCCESS; } } static acl_error_t init_cheatset( FHandle fd, uint32_t offset ) { gblset.seek = offset; if( gblset.strings ) { free( gblset.strings ); gblset.strings = NULL; } if( gblset.entdata ) { free( gblset.entdata ); gblset.entdata = NULL; } uint32_t readed; if( RES_OK != fLseek(fd, offset) ) return ACHTLIB_INVALID; uint16_t entc; if( RES_OK != fRead(fd, &entc, sizeof(entc), &readed) || readed != sizeof( entc ) ) return ACHTLIB_INVALID; gblset.entc = entc; if( RES_OK != fRead(fd, &gblset.entry, sizeof(CheatEntry), &readed) || readed != sizeof(CheatEntry) ) return ACHTLIB_INVALID; if( gblset.entry.entid != 0 ) return ACHTLIB_INVALID; offset = gblset.seek + sizeof(entc) + entc*sizeof(CheatEntry); if( RES_OK != fLseek(fd, offset) ) return ACHTLIB_INVALID; uint32_t size = gblset.entry.offset - sizeof(entc) - entc*sizeof(CheatEntry); char *table = (char*)malloc( size ); if( !table ) return ACHTLIB_NOMEM; if( RES_OK != fReadSize(fd, table, size, &readed) || readed != size ) { free( table ); return ACHTLIB_INVALID; } gblset.strings = table; return ACHTLIB_SUCCESS; } static acl_error_t load_data( FHandle fd ) { if( gblset.entdata != NULL ) { free( gblset.entdata ); gblset.entdata = NULL; } uint32_t elemlen = gblset.entry.entid > 0xffff ? sizeof(uint32_t) : sizeof(uint16_t); uint32_t total = gblset.entry.datasz * elemlen; if( total == 0 ) return ACHTLIB_SUCCESS; uint8_t *d = (uint8_t*)malloc( total ); if( d == NULL ) return ACHTLIB_NOMEM; if( RES_OK != fLseek(fd, gblset.seek + gblset.entry.offset)) return ACHTLIB_INVALID; uint32_t readed; if( RES_OK != fReadSize(fd, d, total, &readed) || readed != total ) { free( d ); return ACHTLIB_INVALID; } gblset.entdata = d; return ACHTLIB_SUCCESS; } #define config_offset(n) ( (n & ~0xff) >> 3 ) acl_error_t acl_select_cheat_set( acl_chtid_t id ) { CheatLib *inst = &gblcht; if( inst->serc == 0 ) return ACHTLIB_NOT_OPEN; if( inst->listc == 0 ) return ACHTLIB_INVALID; FHandle fd; if( RES_OK != fOpen(&fd, inst->aclfile, FA_OPEN_EXISTING | FA_READ) ) return ACHTLIB_INVALID; acl_boolean_t found = 0; uint32_t offset = 0; CheatCfg *cfg = inst->list; for( int i=0; i < inst->listc; ++i ) { if( cfg[i].cfgid == id ) { offset = config_offset( cfg[i].typeloc ); acl_error_t r = init_cheatset( fd, offset ); if( r == ACHTLIB_SUCCESS ) { found = 1; inst->setid = id; break; } else { fClose( fd ); return r; } } } acl_error_t r = found ? load_data(fd) : ACHTLIB_NOT_FOUND; fClose(fd); return r; } #define READ_CACHE_SIZE 16 acl_error_t acl_select_entry( acl_entryid_t id, acl_elemlen_t *count) { if( gblcht.serc == 0 ) return ACHTLIB_NOT_OPEN; if( gblcht.listc == 0 ) return ACHTLIB_INVALID; if( gblcht.setid == 0 ) return ACHTLIB_INVALID; acl_error_t r = ACHTLIB_SUCCESS; if( gblset.entry.entid != id ) { FHandle fd; if( RES_OK != fOpen(&fd, gblcht.aclfile, FA_OPEN_EXISTING | FA_READ) ) return ACHTLIB_INVALID; // update gblset.entry if( RES_OK != fLseek(fd, gblset.seek + sizeof(uint16_t)) ) { fClose( fd ); return ACHTLIB_INVALID; } CheatEntry cache[READ_CACHE_SIZE]; uint32_t readed; for( uint32_t i = 0; i < gblset.entc; i += READ_CACHE_SIZE ) { if( RES_OK != fRead(fd, cache, sizeof(cache), &readed) || readed != sizeof(cache) ) { fClose( fd ); return ACHTLIB_INVALID; } acl_boolean_t found = 0; for( int j=0; j < READ_CACHE_SIZE; ++j ) { if( cache[j].entid == id ) { memcpy( &gblset.entry, &cache[j], sizeof(CheatEntry) ); found = 1; break; } } if( found ) break; } r = load_data(fd); fClose( fd ); } if( count!=NULL ) *count = gblset.entry.datasz; return r; } acl_error_t acl_entry_get_label( acl_index_t index, acl_text_t *label ) { if( gblcht.serc == 0 ) return ACHTLIB_NOT_OPEN; if( gblcht.listc == 0 ) return ACHTLIB_INVALID; if( gblcht.setid == 0 ) return ACHTLIB_INVALID; if( index < gblset.entry.datasz ) { uint16_t *d = (uint16_t*)gblset.entdata; if( label != NULL ) *label = gblset.strings + d[index]; return ACHTLIB_SUCCESS; } else return ACHTLIB_NOT_FOUND; } acl_error_t acl_entry_get_armcode( acl_index_t index, acl_armcode_t *code ) { if( gblcht.serc == 0 ) return ACHTLIB_NOT_OPEN; if( gblcht.listc == 0 ) return ACHTLIB_INVALID; if( gblcht.setid == 0 ) return ACHTLIB_INVALID; if( index < gblset.entry.datasz ) { uint32_t *d = (uint32_t*)gblset.entdata; if( code != NULL ) *code = d[index]; return ACHTLIB_SUCCESS; } else return ACHTLIB_NOT_FOUND; }