oaf-boost/source/arm11/filebrowser.c
2023-04-25 11:20:04 +08:00

1063 lines
28 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.

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "types.h"
#include "error_codes.h"
#include "fs.h"
#include "util.h"
#include "arm11/drivers/hid.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, "文件名总计过长,只显示前面的文件" )
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_tips( NULL, "返回按A/B" );
atp_error_t res = atp_show( length, display_help, (atp_callerdata_t)wording );
atp_tips( NULL, "指引按START" );
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_UNLINK )
{
if( checking && 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;
}
}
}
}
static atp_error_t select_krp( atp_callerdata_t, atp_counter_t index, atp_itemcfg_t *cfg )
{
static char name[20];
ee_snprintf(name, sizeof(name), "键位配置项%d", index+1);
cfg->text = name;
cfg->value = index;
key_remix_t *p = &g_keyremixConfig[index];
key_tips(p, cfg);
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, cfg);
}
else if( index == KRFIELD_DEVICE )
{
cfg->text = "选择实机键位";
if( kcfg->device_keys )
{
for( int 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 = "未设置";
cfg->extra_text_color = ATP_COLOR_LIGHT;
}
else
{
cfg->extra_text = "HOME";
cfg->extra_text_color = ATP_COLOR_GREEN;
}
}
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 = "自动蓄力";
else if( index == REMIX_TYPE_UNLINK ) cfg->text = "禁用原键位";
cfg->value = index;
return ATP_SUCCESS;
}
static atp_error_t select_keyhome( atp_callerdata_t, atp_counter_t index, atp_itemcfg_t *cfg )
{
cfg->text = "HOME";
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 )
{
atp_tips( "", NULL );
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_tips(NULL, "确定A/取消B");
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( NULL, "指引按START" );
return WAIT_ON_ACT( res );
}
else if( y )
{
#ifndef NDEBUG
extern u8 dump_patched_rom;
dump_patched_rom = 1;
#endif
atp_itemval_t position=0, field=0, value;
atp_error_t res;
#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, select_krp, NULL, NULL, position, 0, &position );
if( res == ATP_SUCCESS )
{
status = DISP_SETK;
field = 0;
}
else break;
}
else if( status == DISP_SETK )
{
ee_snprintf(tips, sizeof(tips), "编辑配置项%d", position+1);
key_remix_t *cur = &g_keyremixConfig[position];
res = atp_select( tips, cur->remix_type==REMIX_TYPE_UNLINK?2: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 )
{
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->game_keys = 0;
cur->device_keys = 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
if( cur->remix_type == REMIX_TYPE_CHEAT ) keys_count = 1;
else if( cur->remix_type == REMIX_TYPE_UNLINK ) keys_count = 10;
else if( host > 1 & host != 3 ) // N3DS/N3DSLL/N2DSLL
keys_count = 14;
u8 selected = 31;
for( int 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_keyhome, NULL, NULL, 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;
}
}
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( NULL, "指引按START" );
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_tips("没有上层目录", NULL);
help_page( folder_help, sizeof(folder_help)/sizeof(atp_text_t) );
atp_tips("", NULL);
}
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;
}