2023-03-11 22:48:33 +08:00

478 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "arm11/acl.h"
#include "arm11/fmt.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};
#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 )
{
extern void log( const char * );
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;
ee_printf("\x1b[2Jsearch\n");
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;
}