#include "arm11/acl.h" #include "fs.h" typedef struct { uint32_t typeloc; uint32_t cfgid; } CheatCfg; typedef struct { FHandle fd; // 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}; #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; gblcht.fd = cheatfile; gblcht.serc = serial_count(num); gblcht.list = cfglist; return ACHTLIB_SUCCESS; } acl_error_t acl_close_lib( void ) { if( gblcht.serc != 0 ) { fClose( gblcht.fd ); gblcht.fd = 0; 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( acl_text_t game ) { if( RES_OK != fLseek(gblcht.fd, ACL_HEADER_LEN) ){ return -ACHTLIB_INVALID; } int size = ACL_SERIAL_LEN(gblcht.serc); char *serials = (char*)malloc( size ); if( serials == NULL ){ return -ACHTLIB_NOMEM; } uint32_t readed; if( RES_OK != fRead(gblcht.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 ); if( found >= 0 ) { return found; } else return -ACHTLIB_NOT_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; if( inst->serc == 0 ) return ACHTLIB_NOT_OPEN; if( strlen(game) != ACL_GBA_CODELEN ) return ACHTLIB_NOT_FOUND; int32_t index = bin_search( game ); if( index < 0 ) 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(inst->fd, offset) ) return ACHTLIB_INVALID; if( RES_OK != fRead(inst->fd, groups, sizeof(groups), &readed) || readed != sizeof(groups) ) return ACHTLIB_INVALID; offset = ACL_HEADER_LEN + ACL_SERIAL_LEN(inst->serc) + ACL_INDEX_OFFSET(inst->serc+1); offset += ACL_CONFIG_OFFSET(groups[0]); uint32_t gcount = groups[1] - groups[0]; if( RES_OK != fLseek(inst->fd, offset) ) return ACHTLIB_INVALID; CheatCfg *cfg = inst->list; if( RES_OK != fRead(inst->fd, cfg, gcount * sizeof(CheatCfg), &readed) // 可能需要多次fread || readed != gcount * sizeof(CheatCfg) ) { return ACHTLIB_INVALID; } if( filter ) { char reg = game[3]; index = -1; for( int 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; *n = gcount; 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( 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; *chtid = cfg[index].cfgid; *region = config_type( cfg[index].typeloc ); return ACHTLIB_SUCCESS; } } static acl_error_t init_cheatset( 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(gblcht.fd, offset) ) return ACHTLIB_INVALID; uint16_t entc; if( RES_OK != fRead(gblcht.fd, &entc, sizeof(entc), &readed) || readed != sizeof( entc ) ) return ACHTLIB_INVALID; gblset.entc = entc; if( RES_OK != fRead(gblcht.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(gblcht.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 != fRead(gblcht.fd, table, size, &readed) // TODO: 多次read || readed != size ) { free( table ); return ACHTLIB_INVALID; } gblset.strings = table; return ACHTLIB_SUCCESS; } static acl_error_t load_data() { if( gblset.entdata != NULL ) { free( gblset.entdata ); gblset.entdata = NULL; } int elemlen = gblset.entry.entid > 0xffff ? sizeof(uint32_t) : sizeof(uint16_t); int 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(gblcht.fd, gblset.seek + gblset.entry.offset)) return ACHTLIB_INVALID; uint32_t readed; if( RES_OK != fRead(gblcht.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; 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( offset ); if( r == ACHTLIB_SUCCESS ) { found = 1; inst->setid = id; break; } else return r; } } if( found ) return load_data(); else return ACHTLIB_NOT_FOUND; } #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; if( gblset.entry.entid != id ) { // update gblset.entry if( RES_OK != fLseek(gblcht.fd, gblset.seek + sizeof(uint16_t)) ) return ACHTLIB_INVALID; CheatEntry cache[READ_CACHE_SIZE]; uint32_t readed; for( int i = 0; i < gblset.entc; i += READ_CACHE_SIZE ) { if( RES_OK != fRead(gblcht.fd, cache, sizeof(cache), &readed) || readed != sizeof(cache) ) 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; } } *count = gblset.entry.datasz; return load_data(); } 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; *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; *code = d[index]; return ACHTLIB_SUCCESS; } else return ACHTLIB_NOT_FOUND; }