234 lines
5.7 KiB
C
234 lines
5.7 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/fmt.h"
|
|
#include "drivers/gfx.h"
|
|
|
|
|
|
#define MAX_DIR_ENTRIES (510u)
|
|
#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)
|
|
|
|
|
|
typedef struct
|
|
{
|
|
u8 type; // 0 = file, 1 = dir
|
|
char str[256];
|
|
} DirListEnt;
|
|
|
|
typedef struct
|
|
{
|
|
u32 num;
|
|
DirListEnt entries[MAX_DIR_ENTRIES];
|
|
DirListEnt *ptrs[MAX_DIR_ENTRIES];
|
|
} DirList;
|
|
|
|
|
|
|
|
int dlistCompare(const void *a, const void *b)
|
|
{
|
|
const DirListEnt *const entA = *(DirListEnt**)a;
|
|
const DirListEnt *const entB = *(DirListEnt**)b;
|
|
|
|
if(entA->type != entB->type) return (int)entB->type - entA->type;
|
|
|
|
const char *strA = entA->str;
|
|
const char *strB = entB->str;
|
|
int res = *strA - *strB;
|
|
while(*strA != '\0' && *strB != '\0' && res == 0) res = *++strA - *++strB;
|
|
|
|
return res;
|
|
}
|
|
|
|
static Result scanDir(const char *const path, DirList *const dList, const char *const filter)
|
|
{
|
|
FILINFO *const fi = (FILINFO*)malloc(sizeof(FILINFO) * DIR_READ_BLOCKS);
|
|
if(fi == NULL) return RES_OUT_OF_MEM;
|
|
|
|
dList->num = 0;
|
|
|
|
Result res;
|
|
DHandle dh;
|
|
if((res = fOpenDir(&dh, path)) == RES_OK)
|
|
{
|
|
u32 read;
|
|
u32 dListPos = 0;
|
|
const u32 filterLen = strlen(filter);
|
|
do
|
|
{
|
|
if((res = fReadDir(dh, fi, DIR_READ_BLOCKS, &read)) != RES_OK) break;
|
|
if(dListPos + read > MAX_DIR_ENTRIES) break;
|
|
|
|
for(u32 i = 0; i < read; i++)
|
|
{
|
|
const u8 isDir = (fi[i].fattrib & AM_DIR ? 1u : 0u);
|
|
if(isDir == 0) // File
|
|
{
|
|
const u32 entLen = strlen(fi[i].fname);
|
|
if(entLen <= filterLen || strcmp(filter, fi[i].fname + entLen - filterLen) != 0)
|
|
continue;
|
|
}
|
|
|
|
dList->entries[dListPos].type = isDir;
|
|
safeStrcpy(dList->entries[dListPos].str, fi[i].fname, 256);
|
|
dList->ptrs[dListPos] = &dList->entries[dListPos];
|
|
dListPos++;
|
|
}
|
|
} while(read == DIR_READ_BLOCKS);
|
|
dList->num = dListPos;
|
|
|
|
fCloseDir(dh);
|
|
}
|
|
|
|
free(fi);
|
|
|
|
qsort(dList->ptrs, dList->num, sizeof(DirListEnt*), dlistCompare);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void showDirList(const DirList *const dList, u32 start)
|
|
{
|
|
// Clear screen.
|
|
ee_printf("\x1b[2J");
|
|
|
|
const u32 listLength = (dList->num - start > SCREEN_ROWS ? start + SCREEN_ROWS : dList->num);
|
|
for(u32 i = start; i < listLength; i++)
|
|
{
|
|
const char *const printStr =
|
|
(dList->ptrs[i]->type == 0 ? "\x1b[%lu;H\x1b[37m %.51s" : "\x1b[%lu;H\x1b[33m %.51s");
|
|
|
|
ee_printf(printStr, i - start, dList->ptrs[i]->str);
|
|
}
|
|
}
|
|
|
|
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(512);
|
|
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")) != RES_OK) goto end;
|
|
showDirList(dList, 0);
|
|
|
|
s32 cursorPos = 0; // Within the entire list.
|
|
u32 windowPos = 0; // Window start position within the list.
|
|
s32 oldCursorPos = 0;
|
|
while(1)
|
|
{
|
|
ee_printf("\x1b[%lu;H ", oldCursorPos - windowPos); // Clear old cursor.
|
|
ee_printf("\x1b[%lu;H\x1b[37m>", cursorPos - windowPos); // Draw cursor.
|
|
|
|
u32 kDown;
|
|
do
|
|
{
|
|
GFX_waitForVBlank0();
|
|
|
|
hidScanInput();
|
|
if(hidGetExtraKeys(0) & (KEY_POWER_HELD | KEY_POWER)) goto end;
|
|
kDown = hidKeysDown();
|
|
} while(kDown == 0);
|
|
|
|
const u32 num = dList->num;
|
|
if(num != 0)
|
|
{
|
|
oldCursorPos = cursorPos;
|
|
if(kDown & KEY_DRIGHT)
|
|
{
|
|
cursorPos += SCREEN_ROWS;
|
|
if((u32)cursorPos > num) cursorPos = num - 1;
|
|
}
|
|
if(kDown & KEY_DLEFT)
|
|
{
|
|
cursorPos -= SCREEN_ROWS;
|
|
if(cursorPos < -1) cursorPos = 0;
|
|
}
|
|
if(kDown & KEY_DUP) cursorPos -= 1;
|
|
if(kDown & KEY_DDOWN) cursorPos += 1;
|
|
}
|
|
|
|
if(cursorPos < 0) cursorPos = num - 1; // Wrap to end of list.
|
|
if((u32)cursorPos > (num - 1)) cursorPos = 0; // Wrap to start of list.
|
|
|
|
if((u32)cursorPos < windowPos)
|
|
{
|
|
windowPos = cursorPos;
|
|
showDirList(dList, windowPos);
|
|
}
|
|
if((u32)cursorPos >= windowPos + SCREEN_ROWS)
|
|
{
|
|
windowPos = cursorPos - (SCREEN_ROWS - 1);
|
|
showDirList(dList, windowPos);
|
|
}
|
|
|
|
if(kDown & (KEY_A | KEY_B))
|
|
{
|
|
u32 pathLen = strlen(curDir);
|
|
|
|
if(kDown & KEY_A && num != 0)
|
|
{
|
|
// TODO: !!! Insecure !!!
|
|
if(curDir[pathLen - 1] != '/') curDir[pathLen++] = '/';
|
|
safeStrcpy(curDir + pathLen, dList->ptrs[cursorPos]->str, 256);
|
|
|
|
if(dList->ptrs[cursorPos]->type == 0)
|
|
{
|
|
safeStrcpy(selected, curDir, 512);
|
|
break;
|
|
}
|
|
}
|
|
if(kDown & KEY_B)
|
|
{
|
|
char *tmpPathPtr = curDir + pathLen;
|
|
while(*--tmpPathPtr != '/');
|
|
if(*(tmpPathPtr - 1) == ':') tmpPathPtr++;
|
|
*tmpPathPtr = '\0';
|
|
}
|
|
|
|
if((res = scanDir(curDir, dList, ".gba")) != RES_OK) break;
|
|
cursorPos = 0;
|
|
windowPos = 0;
|
|
showDirList(dList, 0);
|
|
}
|
|
}
|
|
|
|
end:
|
|
free(dList);
|
|
free(curDir);
|
|
|
|
// Clear screen.
|
|
ee_printf("\x1b[2J");
|
|
|
|
return res;
|
|
}
|