/*
* This file is part of open_agb_firm
* Copyright (C) 2021 derrek, profi200
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include "types.h"
#include "error_codes.h"
#include "fs.h"
#include "util.h"
#include "arm11/drivers/hid.h"
#include "arm11/drivers/mcu.h"
#include "arm11/console.h"
#include "arm11/fmt.h"
#include "drivers/gfx.h"
#include "arm11/acf.h"
#include "arm11/atp.h"
#include "arm11/acl.h"
#include "arm11/cheat.h"
#include "arm11/keyremix.h"
#define screenClean() memset(consoleGet()->frameBuffer, 0, CWIDTH*CHEIGHT*sizeof(uint16_t))
// Notes on these settings:
// MAX_ENT_BUF_SIZE should be big enough to hold the average file/dir name length * MAX_DIR_ENTRIES.
#define MAX_ENT_BUF_SIZE (1024u * 196) // 196 KiB.
#define MAX_DIR_ENTRIES (999u)
#define DIR_READ_BLOCKS (10u)
#define CWIDTH 320u
#define CHEIGHT 240u
#define ENT_TYPE_FILE (0)
#define ENT_TYPE_DIR (1)
typedef struct
{
u32 num; // Total number of entries.
char entBuf[MAX_ENT_BUF_SIZE]; // Format: char entryType; char name[X]; // null terminated.
char *ptrs[MAX_DIR_ENTRIES]; // For fast sorting.
} DirList;
int dlistCompare(const void *a, const void *b)
{
const char *entA = *(char**)a;
const char *entB = *(char**)b;
// Compare the entry type. Dirs have priority over files.
if(*entA != *entB) return (int)*entB - *entA;
// Compare the string.
int res;
do
{
res = *++entA - *++entB;
} while(res == 0 && *entA != '\0' && *entB != '\0');
return res;
}
static Result scanDir(const char *const path, DirList *const dList, const char *const filter, const char *match, int *index)
{
FILINFO *const fis = (FILINFO*)malloc(sizeof(FILINFO) * DIR_READ_BLOCKS);
if(fis == NULL) return RES_OUT_OF_MEM;
dList->num = 0;
Result res;
DHandle dh;
u32 received = 0;
if((res = fOpenDir(&dh, path)) == RES_OK)
{
u32 read; // Number of entries read by fReadDir().
u32 numEntries = 0; // Total number of processed entries.
u32 entBufPos = 0; // Entry buffer position/number of bytes used.
const u32 filterLen = strlen(filter);
do
{
if((res = fReadDir(dh, fis, DIR_READ_BLOCKS, &read)) != RES_OK) break;
received = numEntries + read;
read = (read <= MAX_DIR_ENTRIES - numEntries ? read : MAX_DIR_ENTRIES - numEntries);
for(u32 i = 0; i < read; i++)
{
const char entType = (fis[i].fattrib & AM_DIR ? ENT_TYPE_DIR : ENT_TYPE_FILE);
const u32 nameLen = strlen(fis[i].fname);
if(entType == ENT_TYPE_FILE)
{
if(nameLen <= filterLen || strcmp(filter, fis[i].fname + nameLen - filterLen) != 0)
continue;
}
// nameLen does not include the entry type and NULL termination.
if(entBufPos + nameLen + 2 > MAX_ENT_BUF_SIZE)
{
res = RES_PATH_TOO_LONG;
goto scanEnd;
}
char *const entry = &dList->entBuf[entBufPos];
*entry = entType;
safeStrcpy(&entry[1], fis[i].fname, 256);
dList->ptrs[numEntries++] = entry;
entBufPos += nameLen + 2;
}
} while(read == DIR_READ_BLOCKS);
scanEnd:
dList->num = numEntries;
fCloseDir(dh);
}
free(fis);
qsort(dList->ptrs, dList->num, sizeof(char*), dlistCompare);
if( match != NULL )
{
for( u32 i=0; i < dList->num; ++i )
{
if( strcmp(match, &dList->ptrs[i][1]) == 0 )
{
*index = i;
break;
}
}
}
return received > MAX_DIR_ENTRIES ? RES_OUT_OF_RANGE : res;
}
static Result rom_get_serial( const char *file, char serial[5] )
{
FHandle f;
Result res;
u32 readed;
serial[0] = serial[4] = '\0';
if( RES_OK != (res=fOpen(&f, file, FA_OPEN_EXISTING | FA_READ)) )
{
return res;
}
if( RES_OK != (res=fLseek(f, 0xac)) )
{
fClose( f );
return res;
}
if( RES_OK != (res=fRead(f, serial, 4, &readed))
|| readed != 4 )
{
fClose(f);
return res;
}
fClose(f);
return RES_OK;
}
static atp_text_t folder_help[] = {
"操 作 说 明",
"~ ~ ~ ~ ~ ~ ~",
"蓝色 目录",
"白色 游戏",
"~ ~ ~ ~ ~ ~ ~",
"上下方向键 切换选中文件或目录",
"左右方向键 翻页",
"A键 查看目录或启动游戏",
"B键 上层文件夹",
"X键 金手指",
"Y键 配置游戏键位",
"START 查看说明",
"SELECT 系统设置"
};
static atp_text_t cheat_off_help[] = {
"未启用金手指",
"如需开启,请进入系统设置菜单(SELECT键进入)",
"在“激活金手指”选项中启用金手指功能"
};
static atp_error_t display_folder( atp_callerdata_t data, atp_counter_t index, atp_itemcfg_t *config )
{
void* *dat = (void **)data;
DirList *const dList = (DirList*)dat[0];
u8 type = *dList->ptrs[index];
if( type == ENT_TYPE_DIR )
config->text_color = ATP_COLOR_BLUE;
config->text = &dList->ptrs[index][1];
config->value = index;
return ATP_SUCCESS;
}
#define DECLARE_ERROR_PAGE(name, message) static atp_error_t name (atp_callerdata_t, atp_counter_t, atp_linecfg_t *c) \
{ \
c->text = message; \
c->text_align = ATP_PLACEMENT_CENTER; \
c->text_color = ATP_COLOR_RED; \
return ATP_SUCCESS; \
}
DECLARE_ERROR_PAGE( display_openlib, "打开金手指文件出错" )
DECLARE_ERROR_PAGE( display_selcht, "查找金手指配置出错" )
DECLARE_ERROR_PAGE( display_noserial, "提取游戏识别码失败" )
DECLARE_ERROR_PAGE( display_nocheat, "找不到对应的金手指配置" )
DECLARE_ERROR_PAGE( display_empty, "没有合适的文件" )
DECLARE_ERROR_PAGE( display_toolong, "路径过长,改名或移动文件后再试" )
DECLARE_ERROR_PAGE( display_pathfull, "游戏或目录过量,最多显示999个" )
DECLARE_ERROR_PAGE( display_longname, "文件名总计过长,只显示前面的文件" )
DECLARE_ERROR_PAGE( display_conflictkey, "和已有键位产生冲突" )
static atp_error_t display_help( atp_callerdata_t table, atp_counter_t index, atp_linecfg_t *config )
{
atp_text_t *list = (atp_text_t*)table;
config->text_align = ATP_PLACEMENT_CENTER;
config->text_color = index == 0 ? ATP_COLOR_MAGENTA : ATP_COLOR_LIGHT;
config->text = list[index];
return ATP_SUCCESS;
}
atp_error_t help_page( atp_text_t *wording, atp_counter_t length )
{
atp_text_t current;
atp_tips( "返回:按A/B", ¤t );
atp_error_t res = atp_show( length, display_help, (atp_callerdata_t)wording );
atp_tips( current, NULL );
return res;
}
static atp_error_t disp_str( atp_callerdata_t data, atp_counter_t, atp_linecfg_t *cfg )
{
cfg->text = (atp_text_t)data;
return ATP_SUCCESS;
}
/*void log( const char *fmt, ... ) {
char buf[512];
ee_puts("\x1b[2J");
va_list args;
va_start(args, fmt);
u32 res = ee_vsnprintf(buf, 512, fmt, args);
va_end(args);
ee_puts( buf );
uint32_t down;
do{
GFX_waitForVBlank0();
hidScanInput();
down = hidKeysDown();
}while ( down == 0 );
}*/
static atp_error_t select_region( atp_callerdata_t, atp_counter_t index, atp_itemcfg_t *config )
{
static char text[16];
acl_region_t sreg;
acl_chtid_t sid, id;
if( ACHTLIB_SUCCESS != acl_query_cheat_set(index, &sid, &sreg) )
{
config->text = "无效数据";
config->value = index;
return ATP_SUCCESS;
}
char *t;
switch( sreg )
{
case 'C': t = "官方中文"; break;
case 'J': t = "日文版"; break;
case 'E': t = "英文版"; break;
case 'F': t = "法语版"; break;
case 'S': t = "西班牙语"; break;
case 'I': t = "意大利语"; break;
case 'D': t = "德语版"; break;
case 'K': t = "韩文版"; break;
case 'X': case 'P': t = "欧洲语种"; break;
default: t = "其他语种"; break;
}
ee_sprintf(text, "%c-%s", 'A'+(char)index, t);
if( CCHT_NOT_INIT == info_current_cheat( &id, NULL ) )
id = 0;
config->text = text;
config->value = index;
// show saved information
if( sid == id )
{
config->extra_text = "已启用";
config->extra_text_color = ATP_COLOR_GREEN;
}
return ATP_SUCCESS;
}
static atp_error_t select_holes( atp_callerdata_t, atp_counter_t index, atp_itemcfg_t *cfg )
{
acl_text_t text;
acl_error_t res = acl_entry_get_label(index, &text);
if( res == ACHTLIB_SUCCESS )
cfg->text = text;
else cfg->text = "无效数据";
cfg->value = 1+index;
// show saved information
acl_entryid_t id;
if( CCHT_OK == get_current_cheat( index, &id ) && ENT_USING(id) )
{
cfg->extra_text = "开";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
return ATP_SUCCESS;
}
static atp_error_t select_onoff( atp_callerdata_t data, atp_counter_t index, atp_itemcfg_t *cfg )
{
acl_entryid_t eid = (acl_entryid_t)data;
eid &= 0xffff;
if( index == 0 )
{
cfg->text = "不开启";
cfg->value = eid;
}
else if( index == 1 )
{
cfg->text = "开启";
cfg->value = 1<<16 | eid;
}
else
{
cfg->text = "调整数值开启[结果未知]";
cfg->value = 0;
}
// show saved information
if( CCHT_OK == include_current_cheat((acl_entryid_t)cfg->value) )
{
cfg->extra_text = "选中";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
return ATP_SUCCESS;
}
ALWAYS_INLINE u16 calc_step( u16 value )
{
u16 n = value / 50;
if( n == 0 ) return 1;
else
{
u16 base = 1;
while( n > 10 )
{
n = n/10;
base *= 10;
}
if( n < 5 ) return base * 5;
else return base * 10;
}
}
static atp_error_t step_provider( atp_callerdata_t mixid, atp_counter_t index, atp_itemcfg_t *cfg )
{
static char number[8];
u32 mix = (u32)mixid;
u16 max = mix & 0xffff;
u16 step = mix >> 16;
u16 res = step * (index+1);
ee_snprintf( number, 8, "%d", res < max ? res : max );
cfg->text = number;
cfg->value = res < max ? res : max;
return ATP_SUCCESS;
}
static atp_error_t handle_onoff_entry( acl_entryid_t id, acl_elemlen_t codelen, atp_itemval_t *item )
{
#define DEFAULT_ONOFF_HANDLER atp_select("选择此项目对应的设置", 2, select_onoff, NULL, (atp_callerdata_t)id, 0, 0, item);
#define ONOFF_SAFE_CALLACL( statement ) if( ACHTLIB_SUCCESS != statement ) return DEFAULT_ONOFF_HANDLER;
if( codelen > 4 )// more than 2 address
return DEFAULT_ONOFF_HANDLER;
u16 targetval = 0;
acl_armcode_t code;
// one address
if( codelen == 2 )
{
ONOFF_SAFE_CALLACL( acl_entry_get_armcode(1, &code) );
targetval = code & 0xff;
}
// two addresses
else if( codelen == 4 )
{
u32 addr0, addr1;
ONOFF_SAFE_CALLACL( acl_entry_get_armcode(0, &addr0) );
ONOFF_SAFE_CALLACL( acl_entry_get_armcode(2, &addr1) );
if( (addr0 & 1) || (addr0 | 1) != addr1 ) // valid u16 address
return DEFAULT_ONOFF_HANDLER;
ONOFF_SAFE_CALLACL( acl_entry_get_armcode(1, &code) );
targetval = code & 0xff;
ONOFF_SAFE_CALLACL( acl_entry_get_armcode(3, &code) );
targetval |= (code & 0xff) << 8;
}
// more than two address, should not overwrite values
else return DEFAULT_ONOFF_HANDLER;
atp_error_t res = atp_select( "选择此项目对应的设置", 3, select_onoff, NULL, targetval << 16 | id, 0, 0, item );
if( res == ATP_SUCCESS && *item == 0 )
{
char title[24];
ee_snprintf( title, sizeof(title), "默认值:%d", targetval );
u16 step = calc_step( targetval );
atp_counter_t n = (targetval-1+step)/step;
res = atp_select(title, n, step_provider, NULL, (atp_callerdata_t)(step<<16|targetval), n, 0, item );
if( res == ATP_SUCCESS && *item != targetval )
overwrite_current_cheat( 1<<16|id, *item );
*item = 1<<16 | id;
}
return res;
}
static atp_error_t select_keys( atp_callerdata_t data, atp_counter_t index, atp_itemcfg_t *cfg )
{
acl_text_t text;
if( index == 0 )
{
cfg->text = "不开启";
}
else
{
acl_error_t res = acl_entry_get_label(index-1, &text);
if( res == ACHTLIB_SUCCESS )
cfg->text = text;
else cfg->text = "无效数据";
}
acl_entryid_t eid = (acl_entryid_t)data;
cfg->value = index<<16 | eid;
if( CCHT_OK == include_current_cheat((acl_entryid_t)cfg->value) )
{
cfg->extra_text = "选中";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
return ATP_SUCCESS;
}
static inline void key_tips( key_remix_t *p, atp_boolean_t checking, atp_itemcfg_t *cfg )
{
if( p->remix_type == REMIX_TYPE_NONE )
{
cfg->extra_text = "未添加";
cfg->extra_text_color = ATP_COLOR_LIGHT;
}
else
{
if( p->remix_type == REMIX_TYPE_CHEAT )
{
if( checking && p->game_keys == 0)
{
cfg->extra_text = "配置不齐";
cfg->extra_text_color = ATP_COLOR_RED;
}
else
{
cfg->extra_text = "金手指键";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
}
else if( p->remix_type == REMIX_TYPE_REMAP )
{
if( checking && (p->game_keys==0 || p->device_keys==0) )
{
cfg->extra_text = "配置不齐";
cfg->extra_text_color = ATP_COLOR_RED;
}
else
{
cfg->extra_text = "键位映射";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
}
else if( p->remix_type == REMIX_TYPE_HOLD )
{
if( checking && (p->game_keys==0 || p->device_keys==0) )
{
cfg->extra_text = "配置不齐";
cfg->extra_text_color = ATP_COLOR_RED;
}
else
{
cfg->extra_text = "模拟长按";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
}
}
}
#define KCP_OPTION_BASE 1000
static atp_error_t select_krp( atp_callerdata_t, atp_counter_t index, atp_itemcfg_t *cfg )
{
static char name[20];
if( index < KEY_REMIX_LIMIT )
{
ee_snprintf(name, sizeof(name), "键位配置项%ld", index+1);
cfg->text = name;
cfg->value = index;
key_remix_t *p = &g_keyremixConfig[index];
key_tips(p, 1, cfg);
}
else
{
cfg->text = index == KEY_REMIX_LIMIT ? "保存当前配置到SD卡" : "从SD卡加载已保存的配置";
cfg->value = KCP_OPTION_BASE + index-KEY_REMIX_LIMIT;
}
return ATP_SUCCESS;
}
#define KRFIELD_REMIX 0
#define KRFIELD_DEVICE 1
#define KRFIELD_GAME 2
const char *key_name[] = {
"上", "下", "左", "右", "A", "B", "L", "R", "SELECT", "START", "X", "Y", "ZL", "ZR"
};
const u16 key_val[] = {
KEY_DUP, KEY_DDOWN, KEY_DLEFT, KEY_DRIGHT, KEY_A, KEY_B, KEY_L, KEY_R, KEY_SELECT, KEY_START, KEY_X, KEY_Y, KEY_ZL, KEY_ZR
};
#define key_val_len (sizeof(key_val)/sizeof(u16))
static atp_error_t select_kcf( atp_callerdata_t p, atp_counter_t index, atp_itemcfg_t *cfg )
{
key_remix_t *kcfg = (key_remix_t*)p;
if( index == KRFIELD_REMIX )
{
cfg->text = "选择键位功能";
key_tips(kcfg, 0, cfg);
}
else if( index == KRFIELD_DEVICE )
{
cfg->text = "选择实机键位";
if( kcfg->device_keys )
{
for( u8 i=0; i < key_val_len; ++i)
{
if( key_val[i] == kcfg->device_keys )
{
cfg->extra_text = key_name[i];
cfg->extra_text_color = ATP_COLOR_GREEN;
}
}
}
else if( kcfg->remix_type == REMIX_TYPE_CHEAT )
{
cfg->extra_text = "HOME";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
else
{
cfg->extra_text = "未设置";
cfg->extra_text_color = ATP_COLOR_LIGHT;
}
}
else if( index == KRFIELD_GAME )
{
cfg->text = "游戏中对应键位";
if( kcfg->game_keys == 0 )
{
cfg->extra_text = "未设置";
cfg->extra_text_color = ATP_COLOR_LIGHT;
}
else
{
cfg->extra_text = "已设置";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
}
cfg->value = index;
return ATP_SUCCESS;
}
static atp_error_t select_remixtype( atp_callerdata_t, atp_counter_t index, atp_itemcfg_t *cfg )
{
if( index == REMIX_TYPE_NONE ) cfg->text = "停用设置";
else if( index == REMIX_TYPE_REMAP ) cfg->text = "键位映射";
else if( index == REMIX_TYPE_CHEAT ) cfg->text = "金手指键";
else if( index == REMIX_TYPE_HOLD ) cfg->text = "模拟长按";
cfg->value = index;
return ATP_SUCCESS;
}
static atp_error_t select_unikey( atp_callerdata_t str, atp_counter_t, atp_itemcfg_t *cfg )
{
cfg->text = (atp_text_t)str;
cfg->value = 0;
return ATP_SUCCESS;
}
static atp_error_t select_hostkey( atp_callerdata_t dat, atp_counter_t index, atp_itemcfg_t *cfg )
{
phykey_t k = (phykey_t)dat;
cfg->text = key_name[index];
cfg->value = key_val[index];
if( k == key_val[index] )
{
cfg->extra_text = "选中";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
return ATP_SUCCESS;
}
static atp_error_t select_gbakey( atp_callerdata_t p_curkey, atp_counter_t index, atp_itemcfg_t *cfg )
{
conkey_t key = *(conkey_t*)p_curkey;
cfg->text = key_name[index];
cfg->value = key_val[index];
if( key & key_val[index] )
{
cfg->extra_text = "选中";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
return ATP_SUCCESS;
}
static atp_error_t active_gbakey( atp_callerdata_t p_curkey, atp_counter_t index, atp_boolean_t x, atp_boolean_t y, atp_boolean_t l, atp_boolean_t r, atp_boolean_t start, atp_boolean_t select )
{
conkey_t *pkey = (conkey_t*)p_curkey;
if( l || r )
{
conkey_t res = *pkey & key_val[index];
if( res ) *pkey &= ~key_val[index];
else *pkey |= key_val[index];
return ATP_PAGE_UPDATE;
}
else return ATP_PAGE_NOOPTION;
}
extern atp_error_t oaf_config_page();
#define DIRBUFFSIZE 512
#define WAIT_ON_ACT( act ) ( ATP_POWER_OFF == (act) ) ? ATP_POWER_OFF : ATP_PAGE_REFRESH
#define WAIT_ON_ERRPAGE( page ) WAIT_ON_ACT( atp_show(1, (page), NULL) )
static atp_pageopt_t serve_on_key( atp_callerdata_t data, atp_counter_t index, atp_boolean_t x, atp_boolean_t y, atp_boolean_t l, atp_boolean_t r, atp_boolean_t start, atp_boolean_t select )
{
if( start )
{
return WAIT_ON_ACT( help_page( folder_help, sizeof(folder_help)/sizeof(atp_text_t) ) );
}
else if( select )
{
return WAIT_ON_ACT( oaf_config_page() );
}
else if( x )
{
extern int get_cheat_mode();
if( CHEAT_MODE_DISABLED == get_cheat_mode() )
{
return WAIT_ON_ACT( help_page( cheat_off_help, sizeof(cheat_off_help)/sizeof(atp_text_t) ) );
}
void* *dat = (void **)data;
DirList const *dList = (DirList*)dat[0];
char *path = (char*)dat[1];
const char *file = &dList->ptrs[index][1];
int pathlen = strlen(path);
int filelen = strlen(file);
if( pathlen + filelen > DIRBUFFSIZE - 1)
{
return WAIT_ON_ERRPAGE( display_toolong );
}
else if( ENT_TYPE_DIR == dList->ptrs[index][0] )
{
return WAIT_ON_ERRPAGE( display_empty );
}
path[pathlen] = '/';
safeStrcpy( path+pathlen+1, file, DIRBUFFSIZE - filelen );
#define RECOVER_PATH path[pathlen] = '\0'
// 确定game serial
char serial[5];
if( RES_OK != rom_get_serial(path, serial) )
{
RECOVER_PATH;
return WAIT_ON_ERRPAGE( display_noserial );
}
else RECOVER_PATH;
acl_count_t len;
if( ACHTLIB_SUCCESS != acl_open_lib( "gba.acl" ) )
{
return WAIT_ON_ERRPAGE( display_openlib );
}
if( ACHTLIB_SUCCESS != acl_select_game( serial, 0, &len ) )
{
acl_close_lib();
return WAIT_ON_ERRPAGE( display_selcht );
}
if( len == 0 )
{
acl_close_lib();
return WAIT_ON_ERRPAGE( display_nocheat );
}
// 显示配置页面
atp_error_t res;
atp_itemval_t item;
acl_elemlen_t cnt;
#define DISP_DONE 0
#define DISP_REGION 1
#define DISP_HOLES 2
#define DISP_KEYS 3
#define SAFE_CALLACL( r ) if( ACHTLIB_SUCCESS!=r ) \
{\
res = WAIT_ON_ERRPAGE( display_selcht );\
break;\
}
uint8_t status = DISP_REGION;
atp_counter_t defi = 0;
acl_entryid_t eid = 0;
atp_text_t oldtips;
atp_tips("确定A/取消B", &oldtips);
while( status != DISP_DONE )
{
if( status == DISP_REGION )
{
res = atp_select("请谨慎使用金手指!金手指可能会引起卡顿、死机、损坏存档等现象。 "
"选择一个金手指配置:", len, select_region, NULL, NULL, defi, 0, &item );
if( res == ATP_SUCCESS )
{
defi = item;
acl_chtid_t sid;
acl_elemlen_t len;
u32 ccid, cclen;
SAFE_CALLACL( acl_query_cheat_set((acl_index_t)item, &sid, NULL ) );
SAFE_CALLACL( acl_select_cheat_set( sid ) );
SAFE_CALLACL( acl_select_entry(0, &len) );
if( CCHT_OK == info_current_cheat(&ccid, &cclen) )
{
// SKIP the init process when id and len is the same
if( ccid != sid || cclen != len )
init_current_cheat( sid, len );
}
else init_current_cheat( sid, len );
eid = 0;
status = DISP_HOLES;
}
else if( res == ATP_NO_ACTION)
{
status = DISP_DONE;
}
else break;
}
else if( status == DISP_HOLES )
{
SAFE_CALLACL( acl_select_entry(0, &cnt) );
res = atp_select("选择一个金手指项目", cnt, select_holes, NULL, NULL, eid > 0 ? (eid&0xffff)-1 : 0, 0, &item);
if( res == ATP_SUCCESS )
{
eid = (acl_entryid_t)item;
status = DISP_KEYS;
}
else if( res == ATP_NO_ACTION )
{
status = DISP_REGION;
}
else break;
}
else // DISP_KEYS
{
SAFE_CALLACL( acl_select_entry( eid, &cnt ) );
if( cnt == 0 )
{
SAFE_CALLACL( acl_select_entry( 1<<16 | eid, &cnt ) );
res = handle_onoff_entry( eid, cnt, &item );
}
else
{
res = atp_select("选择此项目对应的设置", cnt, select_keys, NULL, (atp_callerdata_t)eid, 0, 0, &item);
}
if( res == ATP_SUCCESS )
{
put_current_cheat( (acl_entryid_t)item );
status = DISP_HOLES;
}
else if( res == ATP_NO_ACTION )
{
status = DISP_HOLES;
}
else break;
}
}
acl_close_lib();
atp_tips( oldtips, NULL );
return WAIT_ON_ACT( res );
}
else if( y )
{
#ifndef NDEBUG
extern u8 dump_patched_rom;
dump_patched_rom = 0;
#endif
atp_itemval_t position=0, field=0, value;
atp_error_t res;
atp_text_t oldtips;
atp_tips( "确定A/取消B", oldtips );
#define DISP_KPOS 1
#define DISP_SETK 2 // SET KEY
#define DISP_KMAP 3 // REMUX TYPE
#define DISP_3DSK 4 // 3DS KEY
#define DISP_GBAK 5 // GBA KEY
uint8_t status = DISP_KPOS;
char tips[32];
while ( status != DISP_DONE )
{
if ( status == DISP_KPOS)
{
res = atp_select( "选择配置项后,按A进行键位配置", KEY_REMIX_LIMIT+2, select_krp, NULL, NULL, position, 0, &position );
if( res == ATP_SUCCESS )
{
void* *dat = (void **)data;
DirList const *dList = (DirList*)dat[0];
char *file = &dList->ptrs[index][1];
if( position < KEY_REMIX_LIMIT )
{
status = DISP_SETK;
field = 0;
}
else if( position == KCP_OPTION_BASE )
{
const char *err = keyremix_dump( file );
if( !err ) status = DISP_DONE;
else atp_show( 1, disp_str, err );
}
else
{
const char *err = keyremix_load( file );
if( !err ) position = 0;
else atp_show( 1, disp_str, err );
}
}
else break;
}
else if( status == DISP_SETK )
{
ee_snprintf(tips, sizeof(tips), "编辑配置项%ld", position+1);
key_remix_t *cur = &g_keyremixConfig[position];
res = atp_select( tips, 3, select_kcf, NULL, cur, field, 0, &field );
if( res == ATP_SUCCESS )
{
status = field == KRFIELD_REMIX ? DISP_KMAP : (field == KRFIELD_DEVICE ? DISP_3DSK : DISP_GBAK);
}
else if( res == ATP_NO_ACTION )
{
int fault = 0;
for( int i=0; i < KEY_REMIX_LIMIT; ++i )
{
if( i!=position )
{
if( g_keyremixConfig[i].remix_type == cur->remix_type
&& g_keyremixConfig[i].device_keys == cur->device_keys )
{
fault = 1;
break;
}
}
}
if( fault && cur->remix_type != REMIX_TYPE_NONE )
atp_show(1, display_conflictkey, NULL);
else status = DISP_KPOS;
}
else break;
}
else if( status == DISP_KMAP )
{
key_remix_t *cur = &g_keyremixConfig[position];
res = atp_select( "配置键位功能", REMIX_TYPE_COUNT, select_remixtype, NULL, cur->remix_type, 0, 0, &value );
if( res == ATP_SUCCESS )
{
if( cur->remix_type != value )
{
cur->remix_type = value;
cur->device_keys = 0;
cur->game_keys = value == REMIX_TYPE_CHEAT ? DEFAULT_CHEATKEY : 0;
}
status = DISP_SETK;
}
else if( res == ATP_NO_ACTION )
{
status = DISP_SETK;
}
else break;
}
else if( status == DISP_3DSK )
{
key_remix_t *cur = &g_keyremixConfig[position];
atp_counter_t keys_count = 12; // old 3ds, old 3dsLL, 2ds
u8 host = MCU_getSystemModel();// 0=3ds 1=3dsll 2=n3ds 3=2ds 4=n3dsll 5=n2dsll
atp_text_t text;
if( cur->remix_type == REMIX_TYPE_CHEAT )
{
keys_count = 1;
text = "HOME";
}
else if( host > 1 && host != 3 ) // N3DS/N3DSLL/N2DSLL
{
keys_count = 14;
}
u8 selected = 31;
for( u8 i=0; i < key_val_len; ++i )
{
if( key_val[i] == cur->device_keys )
{
selected = i;
break;
}
}
if( keys_count == 1 ) res = atp_select("选择实机键位(单选)", 1, select_unikey, NULL, text, 0, 0, &value);
else res = atp_select( "选择实机键位(单选)", keys_count, select_hostkey, NULL, cur->device_keys, selected>keys_count?0:selected, 0, &value);
if( res == ATP_SUCCESS )
{
cur->device_keys = value;
status = DISP_SETK;
}
else if( res == ATP_NO_ACTION )
{
status = DISP_SETK;
}
else break;
}
else if( status == DISP_GBAK )
{
key_remix_t *cur = &g_keyremixConfig[position];
conkey_t key_editing = cur->game_keys;
res = atp_select( "选择游戏中对应键位[多选,按L/R键设置选中]", 10, select_gbakey, active_gbakey, &key_editing, 0, 0, &value);
if( res == ATP_SUCCESS )
{
cur->game_keys = key_editing;
status = DISP_SETK;
}
else if( res == ATP_NO_ACTION )
{
status = DISP_SETK;
}
else break;
}
}
atp_tips( oldtips, NULL );
return WAIT_ON_ACT(res);
}
return ATP_PAGE_NOOPTION;
}
#define PATH_SAME 0
#define PATH_PUSH 1
#define PATH_POP 2
#define DIRUP { \
char *tmpPathPtr = curDir + pathLen; \
while(*--tmpPathPtr != '/'); \
if(*(tmpPathPtr - 1) == ':') tmpPathPtr++; \
*tmpPathPtr = '\0'; \
path_recover = PATH_POP; \
upFrom = tmpPathPtr + 1; \
}
Result browseFiles(const char *const basePath, char selected[512])
{
if(basePath == NULL || selected == NULL) return RES_INVALID_ARG;
// TODO: Check if the base path is empty.
char *curDir = (char*)malloc(DIRBUFFSIZE);
if(curDir == NULL) return RES_OUT_OF_MEM;
safeStrcpy(curDir, basePath, 512);
DirList *const dList = (DirList*)malloc(sizeof(DirList));
if(dList == NULL) return RES_OUT_OF_MEM;
Result res;
if((res = scanDir(curDir, dList, ".gba", NULL, NULL)) != RES_OK)
{
if( res == RES_OUT_OF_RANGE )
atp_show(1, display_pathfull, NULL);
else if( res == RES_PATH_TOO_LONG )
atp_show(1, display_longname, NULL );
else goto end;
}
const char *upFrom = NULL;
int selecting = 0;
while( 1 )
{
atp_itemval_t value;
atp_error_t error;
int path_recover = PATH_SAME;
const u32 count = dList->num;
u32 pathLen = strlen(curDir);
if( count > 0 )
{
GFX_waitForVBlank0();
hidScanInput();
atp_tips( "指引:按START", NULL );
void *cust[2] = {dList, curDir};
error = atp_select( curDir, count, display_folder, serve_on_key, (atp_callerdata_t)cust, selecting, 0, &value );
}
else error = atp_show( 1, display_empty, NULL );
if( error == ATP_POWER_OFF )
{
res = error;
break;
}
else if( error == ATP_SUCCESS )
{// 进入目录或文件
if( count == 0 )
{
DIRUP;
}
else
{
const char *fname = &dList->ptrs[value][1];
u32 namelen = strlen( fname ) + 1;
if( namelen + pathLen > DIRBUFFSIZE-1 )
{
atp_show( 1, display_toolong, NULL );
continue;
}
curDir[pathLen++] = '/';
safeStrcpy( curDir+pathLen, fname, DIRBUFFSIZE - namelen );
if( *dList->ptrs[value] == ENT_TYPE_FILE )
{
safeStrcpy( selected, curDir, 512 );
break;
}
else
{
path_recover = PATH_PUSH;
selecting = 0;
upFrom = NULL;
}
}
}
else if( error == ATP_NO_ACTION )
{// 上层目录
if( strcmp(curDir, FS_DRIVE_NAMES) == 0 )
{
atp_show(1, disp_str, "没有上层目录");
}
else DIRUP;
}
if( path_recover )
{
if(RES_OK != (res = scanDir(curDir, dList, ".gba", upFrom, &selecting)) )
{
if( res == RES_OUT_OF_RANGE )
atp_show( 1, display_pathfull, NULL ); // give a warning for user, and keep show the list
else if( res == RES_PATH_TOO_LONG )
atp_show( 1, display_longname, NULL );
else break;
}
path_recover = PATH_SAME;
}
}
end:
free(dList);
free(curDir);
// Clear screen.
screenClean();
return res;
}