mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-06 22:04:10 +08:00
480 lines
13 KiB
C
480 lines
13 KiB
C
#include "arm11/acl.h"
|
||
#include "fs.h"
|
||
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
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};
|
||
static char sermem[1024*6];
|
||
|
||
#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 )
|
||
{
|
||
extern void log( const char *, ... );
|
||
|
||
if( RES_OK != fLseek(fd, ACL_HEADER_LEN) ){
|
||
return -ACHTLIB_INVALID;
|
||
}
|
||
|
||
uint32_t size = ACL_SERIAL_LEN(gblcht.serc);
|
||
char *serials = &sermem[0];//(char*)malloc( size );
|
||
if( serials == NULL ){
|
||
return -ACHTLIB_NOMEM;
|
||
}//log("alloc serial: %08x with size: %d", serials, size);
|
||
|
||
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;
|
||
}//log("free serial: %08x", serials);
|
||
//free( serials );
|
||
//log("after free");
|
||
|
||
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;
|
||
}
|