/* * 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 "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; } const 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 = index == 0 ? ATP_PLACEMENT_CENTER : ATP_PLACEMENT_LEFT; 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; }