oaf-boost/source/arm11/filebrowser.c

294 lines
7.7 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 "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"
#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 (1000u)
#define DIR_READ_BLOCKS (10u)
//#define SCREEN_COLS (53u - 1) // - 1 because the console inserts a newline after the last line otherwise.
//#define SCREEN_ROWS (24u)
#define SCREEN_COLS 19
#define SCREEN_ROWS 14u
#define LINENO_TO_Y(y) 15+(y)*15
#define CWIDTH 320u
#define CHEIGHT 240u
#define CLEFTMARGIN 20u
#define CLINELIMIT 282u
#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;
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;
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) 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 res;
}
static atp_text_t folder_help[] = {
"操 作 说 明",
"蓝色            文件夹",
"白色           游戏文件",
"A键     查看文件夹或运行游戏",
"B键          上层文件夹",
"X键            金手指",
"Y键           文件配置",
"START        查看说明",
"SELECT       系统设置"
};
static atp_error_t display_folder( atp_callerdata_t data, atp_counter_t index, atp_itemcfg_t *config )
{
DirList *const dList = (DirList*)data;
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;
}
static atp_error_t display_empty( atp_callerdata_t, atp_counter_t, atp_linecfg_t *config )
{
config->text = "没有合适的文件";
config->text_align = ATP_PLACEMENT_CENTER;
config->text_color = ATP_COLOR_RED;
return ATP_SUCCESS;
}
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;
}
static void help_page( atp_text_t *wording, atp_counter_t length )
{
atp_tips( "返回按A/B" );
atp_show( length, display_help, (atp_callerdata_t)wording );
}
#define DIRBUFFSIZE 512
#define DIRUP { \
char *tmpPathPtr = curDir + pathLen; \
while(*--tmpPathPtr != '/'); \
if(*(tmpPathPtr - 1) == ':') tmpPathPtr++; \
*tmpPathPtr = '\0'; \
scan = 1; \
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) goto end;
const char *upFrom = NULL;
int selecting = 0;
while( 1 )
{
atp_itemval_t value;
atp_boolean_t scan = 0;
atp_error_t error;
const u32 count = dList->num;
u32 pathLen = strlen(curDir);
if( count > 0 )
{
atp_tips( "指引按START" );
error = atp_select( curDir, count, display_folder, NULL, (atp_callerdata_t)dList, 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 )
{
res = RES_OUT_OF_MEM;
break;
}
curDir[pathLen++] = '/';
safeStrcpy( curDir+pathLen, fname, DIRBUFFSIZE - namelen );
if( *dList->ptrs[value] == ENT_TYPE_FILE )
{
safeStrcpy( selected, curDir, 512 );
break;
}
else
{
scan = 1;
selecting = 0;
upFrom = NULL;
}
}
}
else if( error == ATP_NO_ACTION )
{// 上层目录
if( strcmp(curDir, FS_DRIVE_NAMES) == 0 )
{
help_page( folder_help, sizeof(folder_help)/sizeof(atp_text_t) );
}
else DIRUP;
}
if( scan )
{
if(RES_OK != (res = scanDir(curDir, dList, ".gba", upFrom, &selecting)) )
break;
}
}
end:
free(dList);
free(curDir);
// Clear screen.
screenClean();
return res;
}