mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-06 13:54:09 +08:00
258 lines
6.6 KiB
C
258 lines
6.6 KiB
C
/*
|
|
* 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( int i=0; i < dList->num; ++i )
|
|
{
|
|
if( strcmp(match, &dList->ptrs[i][1]) == 0 )
|
|
{
|
|
*index = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#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;
|
|
if( count > 0 )
|
|
error = atp_select( curDir, count, display_folder, NULL, (atp_callerdata_t)dList, selecting, &value );
|
|
else error = atp_show( 1, display_empty, NULL );
|
|
|
|
u32 pathLen = strlen(curDir);
|
|
|
|
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 )
|
|
{// 上层目录
|
|
DIRUP;
|
|
}
|
|
|
|
if( scan )
|
|
{
|
|
if(RES_OK != (res = scanDir(curDir, dList, ".gba", upFrom, &selecting)) )
|
|
break;
|
|
}
|
|
}
|
|
|
|
end:
|
|
free(dList);
|
|
free(curDir);
|
|
|
|
// Clear screen.
|
|
screenClean();
|
|
|
|
return res;
|
|
}
|