Compare commits

...

6 Commits
1.2 ... sckill

Author SHA1 Message Date
ApacheThunder
2fa1d4e4a9
Merge pull request #4 from Wokann/sckill_patch
Fix compiler warnings and Add error file reminder
2025-05-28 23:49:43 -05:00
GitHub Wokann
8ea838bca9 Add error file reminder 2025-05-27 13:04:43 +08:00
GitHub Wokann
b1afb76b00 Fix compiler warnings 2025-05-27 13:04:21 +08:00
ApacheThunder
618b0460fe
Merge pull request #3 from Wokann/sckill_patch
Add file selector function
2025-05-26 12:49:34 -05:00
GitHub Wokann
a48750b4ad update file selector for random path
Add random path select.
Add "*.fw", "*.gba", "*.nds" extension.
2025-05-26 13:06:29 +08:00
GitHub Wokann
654e7b81df Add file select for "*.frm" "*.bin" in /firmware
Add file select for "*.frm" "*.bin" in /firmware
2025-05-25 23:07:13 +08:00
3 changed files with 386 additions and 8 deletions

357
arm9/source/fileSelector.c Normal file
View File

@ -0,0 +1,357 @@
#include <stdio.h>
#include <nds.h>
#include <fat.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include "fileSelector.h"
#define MAX(a, b) ((a) < (b) ? (b) : (a))
#define MIN(a, b) ((a) > (b) ? (b) : (a))
#define MAX_DISPLAY_WIDTH 29 // Max characters displayed per file entry line
#define PATH_DISPLAY_WIDTH 26 // Max characters for path scrolling (excluding "Path: ")
#define SCROLL_DELAY 15 // Frames delay before scrolling text
#define END_PAUSE 30 // Frames to pause at end of scrolling
#define MAX_FILE_COUNT 512 // Maximum number of directory entries
#define MAX_FILENAME_LEN 256
#define MAX_PATH_LEN 512
extern PrintConsole* currentConsole;
static char currentPath[MAX_PATH_LEN] = "/";
static char fullPath[MAX_PATH_LEN];
// Struct to store file/folder entry info
typedef struct {
char name[MAX_FILENAME_LEN];
u8 isDir;
} Entry;
static Entry entries[MAX_FILE_COUNT];
/**
* Displays a file/folder selector UI and lets the user pick a firmware file.
* Supports scrolling long filenames and paths, B key to go up a directory,
* and proper sorting with directories first.
*
* Returns a pointer to the full selected file path, or NULL on error.
*/
char* selectFirmware(void) {
int fileCount = 0;
int selection = 0;
int displayStart = 0;
const int DISPLAY_LIMIT = 20; // Number of lines to display for files
int scrollOffset = 0; // Scroll offset for long selected filename
int scrollTimer = 0; // Timer to control scrolling speed
bool atTextEnd = false; // Flag for scrolling at end of filename
int pathScrollOffset = 0; // Scroll offset for long path
int pathScrollTimer = 0; // Timer to control path scrolling
bool pathAtEnd = false; // Flag for scrolling at end of path
int holdDelay = 0;
const int INITIAL_DELAY = 15; // Initial delay before repeating key input
const int REPEAT_DELAY = 4; // Delay between repeated key input events
bool isHolding = false;
int lastKey = 0;
while (1) {
DIR* pdir = opendir(currentPath);
fileCount = 0;
memset(entries, 0, sizeof(entries));
if (pdir) {
// Add ".." entry to go up directory unless at root
if (strcmp(currentPath, "/") != 0) {
strcpy(entries[fileCount].name, "..");
entries[fileCount].isDir = 1;
fileCount++;
}
struct dirent* pent;
while ((pent = readdir(pdir)) != NULL && fileCount < MAX_FILE_COUNT) {
// Skip "." and ".."
if (strcmp(pent->d_name, ".") == 0 ||
strcmp(pent->d_name, "..") == 0)
continue;
// Skipping too long filename
if (strlen(pent->d_name) >= MAX_FILENAME_LEN)
continue;
int isDir = (pent->d_type == DT_DIR);
// If it's a file, check extension
if (!isDir) {
const char* ext = strrchr(pent->d_name, '.');
if (!ext ||
(strcasecmp(ext, ".frm") != 0 &&
strcasecmp(ext, ".bin") != 0 &&
strcasecmp(ext, ".fw") != 0 &&
strcasecmp(ext, ".gba") != 0 &&
strcasecmp(ext, ".nds") != 0))
continue; // Skip files without valid extension
}
memset(entries[fileCount].name, 0, sizeof(entries[fileCount].name));
strncpy(entries[fileCount].name, pent->d_name, MAX_FILENAME_LEN - 1);
entries[fileCount].name[MAX_FILENAME_LEN - 1] = '\0';
entries[fileCount].isDir = isDir;
fileCount++;
}
closedir(pdir);
// Sort entries: directories first, then files, both alphabetically
for (int i = 0; i < fileCount - 1; i++) {
for (int j = 0; j < fileCount - i - 1; j++) {
if ((entries[j].isDir < entries[j + 1].isDir) ||
(entries[j].isDir == entries[j + 1].isDir &&
strcasecmp(entries[j].name, entries[j + 1].name) > 0)) {
Entry temp = entries[j];
entries[j] = entries[j + 1];
entries[j + 1] = temp;
}
}
}
} else {
consoleClear();
printf("Directory not found!\n");
return NULL;
}
while (1) {
scanKeys();
// Scroll path text only (exclude "Path: " label)
int pathLen = strlen(currentPath);
if (pathLen > PATH_DISPLAY_WIDTH) {
pathScrollTimer++;
pathAtEnd = (pathScrollOffset >= pathLen - PATH_DISPLAY_WIDTH);
if (pathAtEnd) {
if (pathScrollTimer > END_PAUSE) {
pathScrollOffset = 0;
pathScrollTimer = 0;
pathAtEnd = false;
}
} else if (pathScrollTimer > SCROLL_DELAY) {
pathScrollOffset++;
pathScrollTimer = 0;
}
} else {
pathScrollOffset = 0;
pathScrollTimer = 0;
pathAtEnd = false;
}
// Scroll filename if longer than display width
if (strlen(entries[selection].name) > MAX_DISPLAY_WIDTH) {
scrollTimer++;
atTextEnd = (scrollOffset >= strlen(entries[selection].name) - MAX_DISPLAY_WIDTH);
if (atTextEnd) {
if (scrollTimer > END_PAUSE) {
scrollOffset = 0;
scrollTimer = 0;
atTextEnd = false;
}
} else if (scrollTimer > SCROLL_DELAY) {
scrollOffset++;
scrollTimer = 0;
}
} else {
scrollOffset = 0;
scrollTimer = 0;
atTextEnd = false;
}
u32 keys = keysDown();
u32 heldKeys = keysHeld();
// Handle key hold and repeat logic for navigation keys
if (keys & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT)) {
isHolding = true;
holdDelay = INITIAL_DELAY;
lastKey = keys & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT);
} else if (!(heldKeys & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT))) {
isHolding = false;
holdDelay = 0;
}
if (isHolding) {
holdDelay--;
if (holdDelay <= 0) {
holdDelay = REPEAT_DELAY;
keys |= lastKey;
}
}
// Navigate right: jump forward by DISPLAY_LIMIT entries
if (keys & KEY_RIGHT) {
if (selection + DISPLAY_LIMIT < fileCount) {
selection += DISPLAY_LIMIT;
displayStart = selection;
} else {
selection = fileCount - 1;
displayStart = MAX(0, fileCount - DISPLAY_LIMIT);
}
scrollOffset = scrollTimer = 0;
continue;
}
// Navigate left: jump backward by DISPLAY_LIMIT entries
if (keys & KEY_LEFT) {
if (selection - DISPLAY_LIMIT >= 0) {
selection -= DISPLAY_LIMIT;
displayStart = selection;
} else {
selection = 0;
displayStart = 0;
}
scrollOffset = scrollTimer = 0;
continue;
}
// Navigate down: move selection down
if (keys & KEY_DOWN && selection < fileCount - 1) {
int step = (isHolding && holdDelay == REPEAT_DELAY) ? 3 : 1;
selection = MIN(selection + step, fileCount - 1);
if (selection >= displayStart + DISPLAY_LIMIT) {
displayStart = selection - DISPLAY_LIMIT + 1;
}
scrollOffset = scrollTimer = 0;
}
// Navigate up: move selection up
if (keys & KEY_UP && selection > 0) {
int step = (isHolding && holdDelay == REPEAT_DELAY) ? 3 : 1;
selection = MAX(selection - step, 0);
if (selection < displayStart) {
displayStart = selection;
}
scrollOffset = scrollTimer = 0;
}
// B key goes up one directory level, same as selecting ".."
if (keys & KEY_B) {
if (strcmp(currentPath, "/") != 0) {
char* lastSlash = strrchr(currentPath, '/');
if (lastSlash) {
if (lastSlash == currentPath)
lastSlash[1] = '\0'; // Keep root "/"
else
*lastSlash = '\0'; // Truncate path to parent
}
selection = displayStart = scrollOffset = scrollTimer = 0;
break; // Refresh directory listing
}
}
// Clamp displayStart to valid range
displayStart = MAX(0, MIN(displayStart, fileCount - DISPLAY_LIMIT));
// Clear screen at the start of frame
consoleClear();
// Print fixed title line (line 0)
currentConsole->cursorY = 0;
currentConsole->cursorX = 0;
printf("Select firmware image to flash:");
// Print "Path: " label and current path on line 1 with scrolling
currentConsole->cursorY = 1;
currentConsole->cursorX = 0;
char pathDisplayBuf[PATH_DISPLAY_WIDTH + 6 + 1];
char pathScrollBuf[PATH_DISPLAY_WIDTH + 1];
pathScrollOffset = (pathLen <= PATH_DISPLAY_WIDTH) ? 0 : pathScrollOffset;
strncpy(pathScrollBuf, &currentPath[pathScrollOffset], PATH_DISPLAY_WIDTH);
pathScrollBuf[PATH_DISPLAY_WIDTH] = '\0';
snprintf(pathDisplayBuf, sizeof(pathDisplayBuf), "Path: %s", pathScrollBuf);
printf("%s\n", pathDisplayBuf);
// Print file/folder list starting at line 2
for (int i = 0; i < DISPLAY_LIMIT && displayStart + i < fileCount; i++) {
int index = displayStart + i;
currentConsole->cursorY = 3 + i;
currentConsole->cursorX = 0;
// Mark selected entry with '*'
if (index == selection)
printf("*");
else
printf(" ");
// Prepare filename display with scrolling if selected
char displayBuf[MAX_DISPLAY_WIDTH + 1];
if (index == selection && strlen(entries[index].name) > MAX_DISPLAY_WIDTH) {
int len = MIN(MAX_DISPLAY_WIDTH, strlen(entries[index].name) - scrollOffset);
strncpy(displayBuf, &entries[index].name[scrollOffset], len);
displayBuf[len] = '\0';
} else if (strlen(entries[index].name) > MAX_DISPLAY_WIDTH) {
strncpy(displayBuf, entries[index].name, MAX_DISPLAY_WIDTH - 3);
strcpy(displayBuf + MAX_DISPLAY_WIDTH - 3, "...");
displayBuf[MAX_DISPLAY_WIDTH] = '\0';
} else {
snprintf(displayBuf, MAX_DISPLAY_WIDTH + 1, "%s", entries[index].name);
}
// Prefix directories with '>', files with space
printf("%s%s\n", entries[index].isDir ? ">" : " ", displayBuf);
}
// If user pressed A key (select)
if (keys & KEY_A) {
if (entries[selection].isDir) {
// Change directory
if (strcmp(entries[selection].name, "..") == 0) {
// Go up one directory
if (strcmp(currentPath, "/") != 0) {
char* lastSlash = strrchr(currentPath, '/');
if (lastSlash) {
if (lastSlash == currentPath)
lastSlash[1] = '\0';
else
*lastSlash = '\0';
}
}
} else {
// Append selected folder to current path
if (strcmp(currentPath, "/") != 0) {
if (strlen(currentPath) + 1 + strlen(entries[selection].name) + 1 > sizeof(currentPath)) {
printf("Error: Path too long!\n");
return NULL;
}
strcat(currentPath, "/");
}
if (1 + strlen(entries[selection].name) + 1 > sizeof(currentPath)) {
printf("Error: Path too long!\n");
return NULL;
}
strcat(currentPath, entries[selection].name);
}
selection = displayStart = scrollOffset = scrollTimer = 0;
break; // Refresh directory listing
} else {
// File selected, build full path and return it
if (strcmp(currentPath, "/") == 0) {
if (1 + strlen(entries[selection].name) + 1 > sizeof(currentPath)) {
printf("Error: Path too long!\n");
return NULL;
}
strcpy(fullPath, "/");
strcat(fullPath, entries[selection].name);
} else {
if (strlen(currentPath) + 1 + strlen(entries[selection].name) + 1 > sizeof(currentPath)) {
printf("Error: Path too long!\n");
return NULL;
}
strcpy(fullPath, currentPath);
strcat(fullPath, "/");
strcat(fullPath, entries[selection].name);
}
return fullPath;
}
}
swiWaitForVBlank();
}
}
}

View File

@ -0,0 +1,13 @@
#ifndef FILESELECTOR_H
#define FILESELECTOR_H
#ifdef __cplusplus
extern "C" {
#endif
char *selectFirmware(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -8,6 +8,7 @@
#include "font.h"
#include "tonccpy.h"
#include "fileSelector.h"
enum class SC_FLASH_COMMAND : u16 {
ERASE = 0x80,
@ -35,6 +36,7 @@ static PrintConsole btConsole;
extern PrintConsole* currentConsole;
char *firmwareFilename = NULL;
static int bg;
static int bgSub;
@ -314,7 +316,7 @@ void vBlankHandler (void) {
PrintWithStat = true;
} else {
if (FileSuccess && (currentConsole != &btConsole)) {
iprintf("%lx \n\n\n\n\n\n\n\n\n [FOUND FIRMWARE.FRM]", statData);
iprintf("%lx \n\n\n\n\n\n\n\n\n[FOUND %s]", statData, firmwareFilename);
} else {
iprintf("%lx \n", statData);
}
@ -379,19 +381,22 @@ int main(void) {
if (fatInitDefault()) {
FILE *src = NULL;
if (access("/firmware.frm", F_OK) == 0) {
src = fopen("/firmware.frm", "rb");
} else if (access("/scfw/firmware.frm", F_OK) == 0) {
src = fopen("/scfw/firmware.frm", "rb");
}
consoleSelect(&btConsole);
consoleClear();
firmwareFilename = selectFirmware();
consoleSelect(&tpConsole);
if(!firmwareFilename) {
FileSuccess = false;
} else {
src = fopen(firmwareFilename, "rb");
if (src) {
fseek(src, 0, SEEK_END);
firmSize = ftell(src);
fseek(src, 0, SEEK_SET);
if (firmSize <= get_max_firm_size()) {
printf("\n\n\n\n\n\n\n\n [FOUND FIRMWARE.FRM]");
iprintf("\n\n\n\n\n\n\n\n[FOUND %s]",firmwareFilename);
consoleSelect(&btConsole);
printf("\n Reading FIRMWARE.FRM\n\n Please Wait...");
iprintf("\n Reading %s\n\n Please Wait...",firmwareFilename);
FileSuccess = fread((u8*)scfw_buffer, firmSize, 1, src) == 1;
fclose(src);
consoleClear();
@ -401,12 +406,15 @@ int main(void) {
} else {
FileSuccess = false;
}
}
} else {
FileSuccess = false;
}
if (!FileSuccess) {
printf("\n\n\n\n\n\n\n\n [File error!]\n [USING SCFW's firmware.frm]");
consoleSelect(&btConsole);
consoleClear();
tonccpy(scfw_buffer, scfw_bin, (scfw_binEnd - scfw_bin));
firmSize = (scfw_binEnd - scfw_bin);
}