mirror of
https://github.com/ApacheThunder/SCKILL.git
synced 2025-06-19 11:35:33 -04:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2fa1d4e4a9 | ||
![]() |
8ea838bca9 | ||
![]() |
b1afb76b00 | ||
![]() |
618b0460fe | ||
![]() |
a48750b4ad | ||
![]() |
654e7b81df | ||
![]() |
af08464f38 | ||
![]() |
63c633fbd1 | ||
![]() |
db588bf012 | ||
![]() |
0c4185717d | ||
![]() |
72cc60ad9b | ||
![]() |
8ad1a717eb | ||
![]() |
939bac205c | ||
![]() |
2d8ad6649e | ||
![]() |
ccd4951c9a | ||
![]() |
04f43cd598 | ||
![]() |
f25fed6558 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -48,6 +48,7 @@ data
|
|||||||
# Stuff used to build custom banner/misc other things that shouldn't be in repo.
|
# Stuff used to build custom banner/misc other things that shouldn't be in repo.
|
||||||
*.dldi
|
*.dldi
|
||||||
*.zip
|
*.zip
|
||||||
|
*.rar
|
||||||
|
|
||||||
# Stuff generated by Windows.
|
# Stuff generated by Windows.
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
@ -8,6 +8,7 @@ https://gbatemp.net/threads/scfw-custom-firmware-kernel-for-supercard.647238/
|
|||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
[metroid maniac](https://github.com/metroid-maniac) - Main developer of original branch and original SCKILL
|
[metroid maniac](https://github.com/metroid-maniac) - Main developer of original branch and original SCKILL
|
||||||
|
|
||||||
[Archeychen](https://github.com/ArcheyChen) - Early development into another loader, SDHC support
|
[Archeychen](https://github.com/ArcheyChen) - Early development into another loader, SDHC support
|
||||||
[RocketRobz](https://github.com/RocketRobz) - Twilightmenu++ "gbapatcher" code for patching Supercard ROMs
|
[RocketRobz](https://github.com/RocketRobz) - Twilightmenu++ "gbapatcher" code for patching Supercard ROMs
|
||||||
[SiliconExarch](https://github.com/SiliconExarch) - Finding an old DevkitARM release with a functioning Supercard SD drive
|
[SiliconExarch](https://github.com/SiliconExarch) - Finding an old DevkitARM release with a functioning Supercard SD drive
|
||||||
|
@ -28,8 +28,8 @@ ARCH := -mthumb -mthumb-interwork -march=armv5te -mtune=arm946e-s
|
|||||||
|
|
||||||
CFLAGS := -g -Wall -Os \
|
CFLAGS := -g -Wall -Os \
|
||||||
$(ARCH) $(INCLUDE) -DARM9
|
$(ARCH) $(INCLUDE) -DARM9
|
||||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++1z
|
||||||
ASFLAGS := -g $(ARCH)
|
ASFLAGS := -g $(ARCH) $(INCLUDE)
|
||||||
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
|
100
arm9/source/dldi_32K.s
Normal file
100
arm9/source/dldi_32K.s
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Copyright (C) 2006 - 2016
|
||||||
|
Michael Chisholm (Chishm)
|
||||||
|
Dave Murphy (WinterMute)
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any
|
||||||
|
damages arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any
|
||||||
|
purpose, including commercial applications, and to alter it and
|
||||||
|
redistribute it freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you
|
||||||
|
must not claim that you wrote the original software. If you use
|
||||||
|
this software in a product, an acknowledgment in the product
|
||||||
|
documentation would be appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and
|
||||||
|
must not be misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <nds/arm9/dldi_asm.h>
|
||||||
|
|
||||||
|
.align 4
|
||||||
|
.arm
|
||||||
|
.global _io_dldi_stub
|
||||||
|
@---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.equ DLDI_ALLOCATED_SPACE, 32768
|
||||||
|
|
||||||
|
_io_dldi_stub:
|
||||||
|
|
||||||
|
dldi_start:
|
||||||
|
|
||||||
|
@---------------------------------------------------------------------------------
|
||||||
|
@ Driver patch file standard header -- 16 bytes
|
||||||
|
.word 0xBF8DA5ED @ Magic number to identify this region
|
||||||
|
.asciz " Chishm" @ Identifying Magic string (8 bytes with null terminator)
|
||||||
|
.byte 0x01 @ Version number
|
||||||
|
.byte DLDI_SIZE_32KB @32KiB @ Log [base-2] of the size of this driver in bytes.
|
||||||
|
.byte 0x00 @ Sections to fix
|
||||||
|
.byte DLDI_SIZE_32KB @32KiB @ Log [base-2] of the allocated space in bytes.
|
||||||
|
|
||||||
|
@---------------------------------------------------------------------------------
|
||||||
|
@ Text identifier - can be anything up to 47 chars + terminating null -- 16 bytes
|
||||||
|
.align 4
|
||||||
|
.asciz "Default (No interface)"
|
||||||
|
|
||||||
|
@---------------------------------------------------------------------------------
|
||||||
|
@ Offsets to important sections within the data -- 32 bytes
|
||||||
|
.align 6
|
||||||
|
.word dldi_start @ data start
|
||||||
|
.word dldi_end @ data end
|
||||||
|
.word 0x00000000 @ Interworking glue start -- Needs address fixing
|
||||||
|
.word 0x00000000 @ Interworking glue end
|
||||||
|
.word 0x00000000 @ GOT start -- Needs address fixing
|
||||||
|
.word 0x00000000 @ GOT end
|
||||||
|
.word 0x00000000 @ bss start -- Needs setting to zero
|
||||||
|
.word 0x00000000 @ bss end
|
||||||
|
|
||||||
|
@---------------------------------------------------------------------------------
|
||||||
|
@ DISC_INTERFACE data -- 32 bytes
|
||||||
|
.ascii "DLDI" @ ioType
|
||||||
|
.word 0x00000000 @ Features
|
||||||
|
.word _DLDI_startup @
|
||||||
|
.word _DLDI_isInserted @
|
||||||
|
.word _DLDI_readSectors @ Function pointers to standard device driver functions
|
||||||
|
.word _DLDI_writeSectors @
|
||||||
|
.word _DLDI_clearStatus @
|
||||||
|
.word _DLDI_shutdown @
|
||||||
|
|
||||||
|
@---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_DLDI_startup:
|
||||||
|
_DLDI_isInserted:
|
||||||
|
_DLDI_readSectors:
|
||||||
|
_DLDI_writeSectors:
|
||||||
|
_DLDI_clearStatus:
|
||||||
|
_DLDI_shutdown:
|
||||||
|
mov r0, #0x00 @ Return false for every function
|
||||||
|
bx lr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@---------------------------------------------------------------------------------
|
||||||
|
.align
|
||||||
|
.pool
|
||||||
|
|
||||||
|
dldi_data_end:
|
||||||
|
|
||||||
|
@ Pad to end of allocated space
|
||||||
|
.space DLDI_ALLOCATED_SPACE - (dldi_data_end - dldi_start)
|
||||||
|
|
||||||
|
dldi_end:
|
||||||
|
.end
|
||||||
|
@---------------------------------------------------------------------------------
|
357
arm9/source/fileSelector.c
Normal file
357
arm9/source/fileSelector.c
Normal 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, ¤tPath[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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
arm9/source/fileSelector.h
Normal file
13
arm9/source/fileSelector.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef FILESELECTOR_H
|
||||||
|
#define FILESELECTOR_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char *selectFirmware(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
@ -4,291 +4,221 @@
|
|||||||
#include <fat.h>
|
#include <fat.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <nds/arm9/console.h>
|
#include <nds/arm9/console.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "font.h"
|
#include "font.h"
|
||||||
#include "tonccpy.h"
|
#include "tonccpy.h"
|
||||||
|
#include "fileSelector.h"
|
||||||
|
|
||||||
#define SC_MODE_REG *(vu16*)0x09FFFFFE
|
enum class SC_FLASH_COMMAND : u16 {
|
||||||
#define SC_MODE_MAGIC (u16)0xA55A
|
ERASE = 0x80,
|
||||||
#define SC_MODE_FLASH_RW (u16)0x4
|
ERASE_BLOCK = 0x30,
|
||||||
#define SC_MODE_FLASH_RW_LITE (u16)0x1510
|
ERASE_CHIP = 0x10,
|
||||||
|
PROGRAM = 0xA0,
|
||||||
|
IDENTIFY = 0x90,
|
||||||
|
};
|
||||||
|
enum SUPERCARD_TYPE : u8 {
|
||||||
|
SC_SD = 0x00,
|
||||||
|
SC_LITE = 0x01,
|
||||||
|
SC_RUMBLE = (0x10 | SC_LITE),
|
||||||
|
UNK = uint8_t(~SC_RUMBLE)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#define SC_FLASH_MAGIC_ADDR_1 (*(vu16*) 0x08000b92)
|
|
||||||
#define SC_FLASH_MAGIC_ADDR_2 (*(vu16*) 0x0800046c)
|
|
||||||
#define SC_FLASH_MAGIC_1 ((u16) 0xaa)
|
|
||||||
#define SC_FLASH_MAGIC_2 ((u16) 0x55)
|
|
||||||
#define SC_FLASH_ERASE ((u16) 0x80)
|
|
||||||
#define SC_FLASH_ERASE_BLOCK ((u16) 0x30)
|
|
||||||
#define SC_FLASH_ERASE_CHIP ((u16) 0x10)
|
|
||||||
#define SC_FLASH_PROGRAM ((u16) 0xA0)
|
|
||||||
#define SC_FLASH_IDLE ((u16) 0xF0)
|
#define SC_FLASH_IDLE ((u16) 0xF0)
|
||||||
#define SC_FLASH_IDENTIFY ((u16) 0x90)
|
|
||||||
|
|
||||||
#define FlashBase 0x08000000
|
|
||||||
|
|
||||||
extern u8 scfw_bin[];
|
extern u8 scfw_bin[];
|
||||||
extern u8 scfw_binEnd[];
|
extern u8 scfw_binEnd[];
|
||||||
|
|
||||||
static u8* scfw_buffer;
|
u16* scfw_buffer;
|
||||||
|
|
||||||
static PrintConsole tpConsole;
|
static PrintConsole tpConsole;
|
||||||
static PrintConsole btConsole;
|
static PrintConsole btConsole;
|
||||||
|
|
||||||
extern PrintConsole* currentConsole;
|
extern PrintConsole* currentConsole;
|
||||||
|
|
||||||
|
char *firmwareFilename = NULL;
|
||||||
|
|
||||||
static int bg;
|
static int bg;
|
||||||
static int bgSub;
|
static int bgSub;
|
||||||
|
|
||||||
const char* textBuffer = "X------------------------------X\nX------------------------------X";
|
const char* textBuffer = "X------------------------------X\nX------------------------------X";
|
||||||
|
|
||||||
volatile u32 cachedFlashID;
|
u32 cachedFlashID;
|
||||||
volatile u32 statData = 0x00000000;
|
u32 statData = 0x00000000;
|
||||||
volatile u32 firmSize = 0x80000;
|
u32 firmSize = 0x200000;
|
||||||
volatile bool UpdateProgressText = false;
|
bool UpdateProgressText = false;
|
||||||
volatile bool PrintWithStat = true;
|
bool PrintWithStat = true;
|
||||||
volatile bool ClearOnUpdate = true;
|
bool ClearOnUpdate = true;
|
||||||
volatile bool SCLiteMode = false;
|
SUPERCARD_TYPE SuperCardType = SUPERCARD_TYPE::UNK;
|
||||||
volatile bool FileSuccess = false;
|
bool FileSuccess = false;
|
||||||
|
|
||||||
|
constexpr u16 SC_MODE_FLASH_RW = 0x0004;
|
||||||
|
constexpr u16 SC_MODE_SDCARD = 0x0002;
|
||||||
|
constexpr u16 SC_MODE_FLASH_RW_LITE_RUMBLE = 0x1510;
|
||||||
|
|
||||||
void Block_Erase(u32 blockAdd) {
|
static void change_mode(u16 mode) {
|
||||||
vu16 v1,v2;
|
auto* const SC_MODE_REG = (vu16*)0x09FFFFFE;
|
||||||
u32 Address;
|
const u16 SC_MODE_MAGIC = 0xA55A;
|
||||||
u32 loop;
|
*SC_MODE_REG = SC_MODE_MAGIC;
|
||||||
u32 off = 0;
|
*SC_MODE_REG = SC_MODE_MAGIC;
|
||||||
|
*SC_MODE_REG = mode;
|
||||||
Address = blockAdd;
|
*SC_MODE_REG = mode;
|
||||||
*((vu16 *)(FlashBase+0x555*2)) = 0xF0;
|
}
|
||||||
*((vu16 *)(FlashBase+0x1555*2)) = 0xF0;
|
|
||||||
|
SUPERCARD_TYPE detect_supercard_type() {
|
||||||
if((blockAdd == 0) || (blockAdd == 0x1FC0000) || (blockAdd == 0xFC0000) || (blockAdd == 0x1000000)) {
|
change_mode(SC_MODE_SDCARD);
|
||||||
for(loop = 0; loop < 0x40000; loop += 0x8000) {
|
auto val = *(volatile uint16_t*)0x09800000;
|
||||||
*((vu16 *)(FlashBase+off+0x555*2)) = 0xAA;
|
switch(val & 0xe300) {
|
||||||
*((vu16 *)(FlashBase+off+0x2AA*2)) = 0x55;
|
case 0xa000:
|
||||||
*((vu16 *)(FlashBase+off+0x555*2)) = 0x80;
|
return SUPERCARD_TYPE::SC_LITE;
|
||||||
*((vu16 *)(FlashBase+off+0x555*2)) = 0xAA;
|
case 0xc000:
|
||||||
*((vu16 *)(FlashBase+off+0x2AA*2)) = 0x55;
|
return SUPERCARD_TYPE::SC_RUMBLE;
|
||||||
*((vu16 *)(FlashBase+Address+loop)) = 0x30;
|
case 0xe000:
|
||||||
|
return SUPERCARD_TYPE::SC_SD;
|
||||||
*((vu16 *)(FlashBase+off+0x1555*2)) = 0xAA;
|
default:
|
||||||
*((vu16 *)(FlashBase+off+0x12AA*2)) = 0x55;
|
return SUPERCARD_TYPE::UNK;
|
||||||
*((vu16 *)(FlashBase+off+0x1555*2)) = 0x80;
|
}
|
||||||
*((vu16 *)(FlashBase+off+0x1555*2)) = 0xAA;
|
}
|
||||||
*((vu16 *)(FlashBase+off+0x12AA*2)) = 0x55;
|
|
||||||
*((vu16 *)(FlashBase+Address+loop+0x2000)) = 0x30;
|
static std::pair<vu16*, vu16*> get_magic_addrs(SUPERCARD_TYPE scType = SuperCardType) {
|
||||||
|
auto* const SCLITE_FLASH_MAGIC_ADDR_1 = (vu16*)0x08000AAA;
|
||||||
*((vu16 *)(FlashBase+off+0x2555*2)) = 0xAA;
|
auto* const SCLITE_FLASH_MAGIC_ADDR_2 = (vu16*)0x08000554;
|
||||||
*((vu16 *)(FlashBase+off+0x22AA*2)) = 0x55;
|
auto* const SC_FLASH_MAGIC_ADDR_1 = (vu16*)0x08000b92;
|
||||||
*((vu16 *)(FlashBase+off+0x2555*2)) = 0x80;
|
auto* const SC_FLASH_MAGIC_ADDR_2 = (vu16*)0x0800046c;
|
||||||
*((vu16 *)(FlashBase+off+0x2555*2)) = 0xAA;
|
if(scType == SUPERCARD_TYPE::SC_SD)
|
||||||
*((vu16 *)(FlashBase+off+0x22AA*2)) = 0x55;
|
return { SC_FLASH_MAGIC_ADDR_1, SC_FLASH_MAGIC_ADDR_2 };
|
||||||
*((vu16 *)(FlashBase+Address+loop+0x4000)) = 0x30;
|
return { SCLITE_FLASH_MAGIC_ADDR_1, SCLITE_FLASH_MAGIC_ADDR_2 };
|
||||||
|
}
|
||||||
*((vu16 *)(FlashBase+off+0x3555*2)) = 0xAA;
|
|
||||||
*((vu16 *)(FlashBase+off+0x32AA*2)) = 0x55;
|
static u32 get_max_firm_size(SUPERCARD_TYPE scType = SuperCardType) {
|
||||||
*((vu16 *)(FlashBase+off+0x3555*2)) = 0x80;
|
switch(scType){
|
||||||
*((vu16 *)(FlashBase+off+0x3555*2)) = 0xAA;
|
case SUPERCARD_TYPE::SC_SD:
|
||||||
*((vu16 *)(FlashBase+off+0x32AA*2)) = 0x55;
|
return 0x80000;
|
||||||
*((vu16 *)(FlashBase+Address+loop+0x6000)) = 0x30;
|
case SUPERCARD_TYPE::SC_LITE:
|
||||||
do {
|
return 0x7C000;
|
||||||
v1 = *((vu16 *)(FlashBase+Address+loop));
|
case SUPERCARD_TYPE::SC_RUMBLE:
|
||||||
v2 = *((vu16 *)(FlashBase+Address+loop));
|
case SUPERCARD_TYPE::UNK:
|
||||||
} while(v1!=v2);
|
return 0x200000;
|
||||||
do {
|
default:
|
||||||
v1 = *((vu16 *)(FlashBase+Address+loop+0x2000));
|
__builtin_unreachable();
|
||||||
v2 = *((vu16 *)(FlashBase+Address+loop+0x2000));
|
|
||||||
} while(v1!=v2);
|
|
||||||
do {
|
|
||||||
v1 = *((vu16 *)(FlashBase+Address+loop+0x4000));
|
|
||||||
v2 = *((vu16 *)(FlashBase+Address+loop+0x4000));
|
|
||||||
} while(v1!=v2);
|
|
||||||
do {
|
|
||||||
v1 = *((vu16 *)(FlashBase+Address+loop+0x6000));
|
|
||||||
v2 = *((vu16 *)(FlashBase+Address+loop+0x6000));
|
|
||||||
} while(v1!=v2);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*((vu16 *)(FlashBase+off+0x555*2)) = 0xAA;
|
|
||||||
*((vu16 *)(FlashBase+off+0x2AA*2)) = 0x55;
|
|
||||||
*((vu16 *)(FlashBase+off+0x555*2)) = 0x80;
|
|
||||||
*((vu16 *)(FlashBase+off+0x555*2)) = 0xAA;
|
|
||||||
*((vu16 *)(FlashBase+off+0x2AA*2)) = 0x55;
|
|
||||||
*((vu16 *)(FlashBase+Address)) = 0x30;
|
|
||||||
|
|
||||||
*((vu16 *)(FlashBase+off+0x1555*2)) = 0xAA;
|
|
||||||
*((vu16 *)(FlashBase+off+0x12AA*2)) = 0x55;
|
|
||||||
*((vu16 *)(FlashBase+off+0x1555*2)) = 0x80;
|
|
||||||
*((vu16 *)(FlashBase+off+0x1555*2)) = 0xAA;
|
|
||||||
*((vu16 *)(FlashBase+off+0x12AA*2)) = 0x55;
|
|
||||||
*((vu16 *)(FlashBase+Address+0x2000)) = 0x30;
|
|
||||||
|
|
||||||
do {
|
|
||||||
v1 = *((vu16 *)(FlashBase+Address));
|
|
||||||
v2 = *((vu16 *)(FlashBase+Address));
|
|
||||||
} while(v1!=v2);
|
|
||||||
do {
|
|
||||||
v1 = *((vu16 *)(FlashBase+Address+0x2000));
|
|
||||||
v2 = *((vu16 *)(FlashBase+Address+0x2000));
|
|
||||||
} while(v1!=v2);
|
|
||||||
|
|
||||||
*((vu16 *)(FlashBase+off+0x555*2)) = 0xAA;
|
|
||||||
*((vu16 *)(FlashBase+off+0x2AA*2)) = 0x55;
|
|
||||||
*((vu16 *)(FlashBase+off+0x555*2)) = 0x80;
|
|
||||||
*((vu16 *)(FlashBase+off+0x555*2)) = 0xAA;
|
|
||||||
*((vu16 *)(FlashBase+off+0x2AA*2)) = 0x55;
|
|
||||||
*((vu16 *)(FlashBase+Address+0x20000)) = 0x30;
|
|
||||||
|
|
||||||
*((vu16 *)(FlashBase+off+0x1555*2)) = 0xAA;
|
|
||||||
*((vu16 *)(FlashBase+off+0x12AA*2)) = 0x55;
|
|
||||||
*((vu16 *)(FlashBase+off+0x1555*2)) = 0x80;
|
|
||||||
*((vu16 *)(FlashBase+off+0x1555*2)) = 0xAA;
|
|
||||||
*((vu16 *)(FlashBase+off+0x12AA*2)) = 0x55;
|
|
||||||
*((vu16 *)(FlashBase+Address+0x2000+0x20000)) = 0x30;
|
|
||||||
|
|
||||||
do {
|
|
||||||
v1 = *((vu16 *)(FlashBase+Address+0x20000));
|
|
||||||
v2 = *((vu16 *)(FlashBase+Address+0x20000));
|
|
||||||
} while(v1!=v2);
|
|
||||||
do {
|
|
||||||
v1 = *((vu16 *)(FlashBase+Address+0x2000+0x20000));
|
|
||||||
v2 = *((vu16 *)(FlashBase+Address+0x2000+0x20000));
|
|
||||||
} while(v1!=v2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modified version of WriteNorFlash from EZFlash 3in1 card lib
|
static void send_command(SC_FLASH_COMMAND command, SUPERCARD_TYPE scType = SuperCardType) {
|
||||||
void WriteNorFlash_SCLite(u32 address, u8 *buffer, u32 size) {
|
constexpr u16 SC_FLASH_MAGIC_1 = 0x00aa;
|
||||||
vu16 v1,v2;
|
constexpr u16 SC_FLASH_MAGIC_2 = 0x0055;
|
||||||
u32 loopwrite;
|
auto [magic_addr_1, magic_addr_2] = get_magic_addrs(scType);
|
||||||
vu16* buf = (vu16*)buffer;
|
*magic_addr_1 = SC_FLASH_MAGIC_1;
|
||||||
u32 mapaddress;
|
*magic_addr_2 = SC_FLASH_MAGIC_2;
|
||||||
v1 = 0;
|
*magic_addr_1 = static_cast<u16>(command);
|
||||||
v2 = 1;
|
|
||||||
u32 off = 0; // Original offset code for alternate 3in1 revisions removed. They are not used for SC Lite.
|
|
||||||
|
|
||||||
mapaddress = address;
|
|
||||||
|
|
||||||
for(loopwrite = 0; loopwrite < (size >> 2); loopwrite++) {
|
|
||||||
*((vu16*)(FlashBase+off+0x555*2)) = 0xAA;
|
|
||||||
*((vu16*)(FlashBase+off+0x2AA*2)) = 0x55;
|
|
||||||
*((vu16*)(FlashBase+off+0x555*2)) = 0xA0;
|
|
||||||
*((vu16*)(FlashBase+mapaddress+loopwrite*2)) = buf[loopwrite];
|
|
||||||
*((vu16*)(FlashBase+off+0x1555*2)) = 0xAA;
|
|
||||||
*((vu16*)(FlashBase+off+0x12AA*2)) = 0x55;
|
|
||||||
*((vu16*)(FlashBase+off+0x1555*2)) = 0xA0;
|
|
||||||
*((vu16*)(FlashBase+mapaddress+0x2000+loopwrite*2)) = buf[0x1000+loopwrite];
|
|
||||||
do {
|
|
||||||
v1 = *((vu16*)(FlashBase+mapaddress+loopwrite*2));
|
|
||||||
v2 = *((vu16*)(FlashBase+mapaddress+loopwrite*2));
|
|
||||||
} while(v1 != v2);
|
|
||||||
do {
|
|
||||||
v1 = *((vu16*)(FlashBase+mapaddress+0x2000+loopwrite*2));
|
|
||||||
v2 = *((vu16*)(FlashBase+mapaddress+0x2000+loopwrite*2));
|
|
||||||
} while(v1 != v2);
|
|
||||||
if (!UpdateProgressText) {
|
|
||||||
textBuffer = "\n\n\n\n\n\n\n\n\n\n\n Programmed ";
|
|
||||||
statData = (0x08000000 + loopwrite);
|
|
||||||
UpdateProgressText = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 sc_flash_id() {
|
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_MAGIC_1;
|
void sc_flash_rw_enable(SUPERCARD_TYPE scType = SuperCardType) {
|
||||||
SC_FLASH_MAGIC_ADDR_2 = SC_FLASH_MAGIC_2;
|
change_mode((scType == SUPERCARD_TYPE::SC_SD) ? SC_MODE_FLASH_RW : SC_MODE_FLASH_RW_LITE_RUMBLE);
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_IDENTIFY;
|
}
|
||||||
|
|
||||||
// should equal 0x000422b9
|
u32 get_flash_id(SUPERCARD_TYPE scType = SuperCardType) {
|
||||||
u32 res = SC_FLASH_MAGIC_ADDR_1;
|
static constexpr u16 COMMAND_ERROR = 0x002e;
|
||||||
res |= *GBA_BUS << 16;
|
sc_flash_rw_enable(scType);
|
||||||
|
auto [magic_addr_1, magic_addr_2] = get_magic_addrs(scType);
|
||||||
|
send_command(SC_FLASH_COMMAND::IDENTIFY, scType);
|
||||||
|
auto upper_half = *GBA_BUS;
|
||||||
|
auto magic = *magic_addr_1;
|
||||||
*GBA_BUS = SC_FLASH_IDLE;
|
*GBA_BUS = SC_FLASH_IDLE;
|
||||||
|
if(upper_half == COMMAND_ERROR)
|
||||||
return res;
|
return 0;
|
||||||
|
return (upper_half << 16) | magic;
|
||||||
}
|
|
||||||
|
|
||||||
void sc_flash_rw_enable() {
|
|
||||||
bool buf = REG_IME;
|
|
||||||
REG_IME = 0;
|
|
||||||
SC_MODE_REG = SC_MODE_MAGIC;
|
|
||||||
SC_MODE_REG = SC_MODE_MAGIC;
|
|
||||||
SC_MODE_REG = SC_MODE_FLASH_RW;
|
|
||||||
SC_MODE_REG = SC_MODE_FLASH_RW;
|
|
||||||
REG_IME = buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sc_flash_rw_enable_lite() {
|
|
||||||
bool buf = REG_IME;
|
|
||||||
REG_IME = 0;
|
|
||||||
SC_MODE_REG = SC_MODE_MAGIC;
|
|
||||||
SC_MODE_REG = SC_MODE_MAGIC;
|
|
||||||
SC_MODE_REG = SC_MODE_FLASH_RW_LITE;
|
|
||||||
SC_MODE_REG = SC_MODE_FLASH_RW_LITE;
|
|
||||||
REG_IME = buf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sc_flash_erase_chip() {
|
void sc_flash_erase_chip() {
|
||||||
bool buf = REG_IME;
|
send_command(SC_FLASH_COMMAND::ERASE);
|
||||||
REG_IME = 0;
|
send_command(SC_FLASH_COMMAND::ERASE_CHIP);
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_MAGIC_1;
|
|
||||||
SC_FLASH_MAGIC_ADDR_2 = SC_FLASH_MAGIC_2;
|
while (*GBA_BUS != *GBA_BUS);
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_ERASE;
|
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_MAGIC_1;
|
|
||||||
SC_FLASH_MAGIC_ADDR_2 = SC_FLASH_MAGIC_2;
|
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_ERASE_CHIP;
|
|
||||||
|
|
||||||
while (*GBA_BUS != *GBA_BUS)swiWaitForVBlank();
|
|
||||||
*GBA_BUS = SC_FLASH_IDLE;
|
*GBA_BUS = SC_FLASH_IDLE;
|
||||||
REG_IME = buf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sc_flash_erase_block(vu16 *addr) {
|
bool sc_flash_erase_block_rumble(vu16 *addr) {
|
||||||
bool buf = REG_IME;
|
vu16* addr1 = (vu16*)((0xfff8000 & ((intptr_t)addr)) + 0xaaa);
|
||||||
REG_IME = 0;
|
vu16* addr2 = (vu16*)((0xfff8000 & ((intptr_t)addr)) + 0x554);
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_MAGIC_1;
|
|
||||||
SC_FLASH_MAGIC_ADDR_2 = SC_FLASH_MAGIC_2;
|
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_ERASE;
|
*addr1 = 0xaa;
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_MAGIC_1;
|
*addr2 = 0x55;
|
||||||
SC_FLASH_MAGIC_ADDR_2 = SC_FLASH_MAGIC_2;
|
*addr1 = 0x80;
|
||||||
*addr = SC_FLASH_ERASE_BLOCK;
|
|
||||||
|
*addr1 = 0xaa;
|
||||||
while (*GBA_BUS != *GBA_BUS)swiWaitForVBlank();
|
*addr2 = 0x55;
|
||||||
|
*addr = 0x30;
|
||||||
|
|
||||||
|
for(int i = 0x3d0000; i >= 0; --i) {
|
||||||
|
if(*addr == 0xffff)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_flash_erase_block(vu16 *addr, SUPERCARD_TYPE scType = SuperCardType)
|
||||||
|
{
|
||||||
|
constexpr u16 SC_FLASH_MAGIC_1 = 0x00aa;
|
||||||
|
constexpr u16 SC_FLASH_MAGIC_2 = 0x0055;
|
||||||
|
auto [magic_addr_1, magic_addr_2] = get_magic_addrs(scType);
|
||||||
|
send_command(SC_FLASH_COMMAND::ERASE);
|
||||||
|
|
||||||
|
*magic_addr_1 = SC_FLASH_MAGIC_1;
|
||||||
|
*magic_addr_2 = SC_FLASH_MAGIC_2;
|
||||||
|
*addr = (u16)SC_FLASH_COMMAND::ERASE_BLOCK;
|
||||||
|
|
||||||
|
while (*GBA_BUS != *GBA_BUS) ;
|
||||||
|
*GBA_BUS = SC_FLASH_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_flash_program_rumble(vu16 *addr, u16 val) {
|
||||||
|
vu16* addr1 = (vu16*)((0xfff8000 & ((intptr_t)addr)) + 0xaaa);
|
||||||
|
vu16* addr2 = (vu16*)((0xfff8000 & ((intptr_t)addr)) + 0x554);
|
||||||
|
|
||||||
|
*addr1 = 0xaa;
|
||||||
|
*addr2 = 0x55;
|
||||||
|
*addr1 = 0xa0;
|
||||||
|
|
||||||
|
*addr = val;
|
||||||
|
|
||||||
|
for(int i = 0x100; i >= 0; --i) {
|
||||||
|
if(*addr == val)
|
||||||
|
break;
|
||||||
|
}
|
||||||
*GBA_BUS = SC_FLASH_IDLE;
|
*GBA_BUS = SC_FLASH_IDLE;
|
||||||
REG_IME = buf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sc_flash_program(vu16 *addr, u16 val) {
|
void sc_flash_program(vu16 *addr, u16 val) {
|
||||||
bool buf = REG_IME;
|
send_command(SC_FLASH_COMMAND::PROGRAM);
|
||||||
REG_IME = 0;
|
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_MAGIC_1;
|
|
||||||
SC_FLASH_MAGIC_ADDR_2 = SC_FLASH_MAGIC_2;
|
|
||||||
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_PROGRAM;
|
|
||||||
*addr = val;
|
*addr = val;
|
||||||
|
while (*GBA_BUS != *GBA_BUS);
|
||||||
while (*GBA_BUS != *GBA_BUS)swiWaitForVBlank();
|
|
||||||
|
|
||||||
*GBA_BUS = SC_FLASH_IDLE;
|
*GBA_BUS = SC_FLASH_IDLE;
|
||||||
REG_IME = buf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool DoFlash() {
|
bool DoFlash() {
|
||||||
|
auto* const FLASH_BASE = (vu16*)0x08000000;
|
||||||
sc_flash_rw_enable();
|
sc_flash_rw_enable();
|
||||||
printf("\n Death 2 supercard :3\n");
|
printf("\n Death 2 supercard :3\n");
|
||||||
printf(" Erasing whole chip\n");
|
printf(" Erasing whole chip\n");
|
||||||
sc_flash_erase_chip();
|
sc_flash_erase_chip();
|
||||||
printf(" Erased whole chip\n");
|
printf(" Erased whole chip\n");
|
||||||
for (int i = 0; i < 60; i++)swiWaitForVBlank();
|
for (int i = 0; i < 60; i++)swiWaitForVBlank();
|
||||||
for (u32 off = 0; off < firmSize; off += 2) {
|
auto total = (firmSize + 1) / 2; // account for odd sizes
|
||||||
u16 val = 0;
|
u32 off = 0;
|
||||||
val |= scfw_buffer[off];
|
auto* flash_function = &sc_flash_program;
|
||||||
val |= (scfw_buffer[off+1] << 8);
|
if(SuperCardType == SUPERCARD_TYPE::SC_RUMBLE) { // first 0x40000 bytes of a supercard rumble are read only
|
||||||
sc_flash_program((vu16*)(0x08000000 + off), val);
|
off = (0x40000 / 2);
|
||||||
if (!UpdateProgressText && !(off & 0x00ff)) {
|
flash_function = &sc_flash_program_rumble;
|
||||||
|
}
|
||||||
|
for (; off < (total); ++off) {
|
||||||
|
(*flash_function)(FLASH_BASE+off, scfw_buffer[off]);
|
||||||
|
if (!UpdateProgressText && !(off & 0x007f)) {
|
||||||
textBuffer = "\n\n\n\n\n\n\n\n\n\n\n Programmed ";
|
textBuffer = "\n\n\n\n\n\n\n\n\n\n\n Programmed ";
|
||||||
statData = (0x08000000 + off);
|
statData = (uintptr_t)(FLASH_BASE+off);
|
||||||
UpdateProgressText = true;
|
UpdateProgressText = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,44 +227,18 @@ bool DoFlash() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DoFlash_Lite() {
|
|
||||||
sc_flash_rw_enable_lite();
|
|
||||||
for(u32 offset = 0; offset < 0x80000; offset += 0x40000) {
|
|
||||||
if (!UpdateProgressText) {
|
|
||||||
textBuffer = "\n Death 2 supercard lite :3\n\n\n Erasing whole chip\n\n\n Erased ";
|
|
||||||
statData = (0x08000000 + offset);
|
|
||||||
UpdateProgressText = true;
|
|
||||||
}
|
|
||||||
Block_Erase(offset);
|
|
||||||
}
|
|
||||||
printf("\n\n\n Erased whole chip\n");
|
|
||||||
|
|
||||||
for (int i = 0; i < 60; i++)swiWaitForVBlank();
|
|
||||||
|
|
||||||
WriteNorFlash_SCLite(0, scfw_buffer, 0x80000);
|
|
||||||
|
|
||||||
while(UpdateProgressText)swiWaitForVBlank();
|
|
||||||
|
|
||||||
for (int i = 0; i < 60; i++)swiWaitForVBlank();
|
|
||||||
|
|
||||||
printf("\n\n\n\n\n Ded!\n");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void CustomConsoleInit() {
|
void CustomConsoleInit() {
|
||||||
videoSetMode(MODE_0_2D);
|
videoSetMode(MODE_0_2D);
|
||||||
videoSetModeSub(MODE_0_2D);
|
videoSetModeSub(MODE_0_2D);
|
||||||
vramSetBankA (VRAM_A_MAIN_BG);
|
vramSetBankA (VRAM_A_MAIN_BG);
|
||||||
vramSetBankC (VRAM_C_SUB_BG);
|
vramSetBankC (VRAM_C_SUB_BG);
|
||||||
|
|
||||||
bg = bgInit(3, BgType_Bmp8, BgSize_B8_256x256, 1, 0);
|
bg = bgInit(3, BgType_Bmp8, BgSize_B8_256x256, 1, 0);
|
||||||
bgSub = bgInitSub(3, BgType_Bmp8, BgSize_B8_256x256, 1, 0);
|
bgSub = bgInitSub(3, BgType_Bmp8, BgSize_B8_256x256, 1, 0);
|
||||||
|
|
||||||
consoleInit(&btConsole, 3, BgType_Text4bpp, BgSize_T_256x256, 20, 0, false, false);
|
consoleInit(&btConsole, 3, BgType_Text4bpp, BgSize_T_256x256, 20, 0, false, false);
|
||||||
consoleInit(&tpConsole, 3, BgType_Text4bpp, BgSize_T_256x256, 20, 0, true, false);
|
consoleInit(&tpConsole, 3, BgType_Text4bpp, BgSize_T_256x256, 20, 0, true, false);
|
||||||
|
|
||||||
ConsoleFont font;
|
ConsoleFont font;
|
||||||
font.gfx = (u16*)fontTiles;
|
font.gfx = (u16*)fontTiles;
|
||||||
font.pal = (u16*)fontPal;
|
font.pal = (u16*)fontPal;
|
||||||
@ -349,22 +253,50 @@ void CustomConsoleInit() {
|
|||||||
consoleSelect(&tpConsole);
|
consoleSelect(&tpConsole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printHeader() {
|
||||||
|
consoleSelect(&tpConsole);
|
||||||
|
switch(SuperCardType) {
|
||||||
|
case SUPERCARD_TYPE::SC_SD:
|
||||||
|
textBuffer = "\n\n\n\n\n\n\n\n\n\n\n Flash ID ";
|
||||||
|
break;
|
||||||
|
case SUPERCARD_TYPE::SC_LITE:
|
||||||
|
textBuffer = "\n\n [SCLITE MODE]\n\n\n\n\n\n\n\n\n Flash ID ";
|
||||||
|
break;
|
||||||
|
case SUPERCARD_TYPE::SC_RUMBLE:
|
||||||
|
textBuffer = "\n\n [RUMBLE MODE]\n\n\n\n\n\n\n\n\n Flash ID ";
|
||||||
|
break;
|
||||||
|
case SUPERCARD_TYPE::UNK:
|
||||||
|
textBuffer = "\n\n [UNKNOWN]\n\n\n\n\n\n\n\n\n Flash ID ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
statData = cachedFlashID;
|
||||||
|
UpdateProgressText = true;
|
||||||
|
while(UpdateProgressText)swiWaitForVBlank();
|
||||||
|
statData = 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool Prompt() {
|
bool Prompt() {
|
||||||
while(1) {
|
while(1) {
|
||||||
swiWaitForVBlank();
|
swiWaitForVBlank();
|
||||||
scanKeys();
|
scanKeys();
|
||||||
if ((keysDown() & KEY_UP) || (keysDown() & KEY_DOWN) || (keysDown() & KEY_LEFT) || (keysDown() & KEY_RIGHT)) {
|
if ((keysDown() & KEY_UP) || (keysDown() & KEY_DOWN) || (keysDown() & KEY_LEFT) || (keysDown() & KEY_RIGHT)) {
|
||||||
if (SCLiteMode) { SCLiteMode = false; } else { SCLiteMode = true; }
|
|
||||||
consoleSelect(&tpConsole);
|
consoleSelect(&tpConsole);
|
||||||
if (SCLiteMode) {
|
auto get_next = [](SUPERCARD_TYPE cur_mode){
|
||||||
textBuffer = "\n\n [SCLITE MODE]\n\n\n\n\n\n\n\n\n Flash ID ";
|
switch(cur_mode) {
|
||||||
} else {
|
case SUPERCARD_TYPE::SC_SD:
|
||||||
textBuffer = "\n\n\n\n\n\n\n\n\n\n\n Flash ID ";
|
return SUPERCARD_TYPE::SC_LITE;
|
||||||
}
|
case SUPERCARD_TYPE::SC_LITE:
|
||||||
statData = cachedFlashID;
|
return SUPERCARD_TYPE::SC_RUMBLE;
|
||||||
UpdateProgressText = true;
|
case SUPERCARD_TYPE::SC_RUMBLE:
|
||||||
while(UpdateProgressText)swiWaitForVBlank();
|
return SUPERCARD_TYPE::SC_SD;
|
||||||
statData = 0;
|
case SUPERCARD_TYPE::UNK:
|
||||||
|
return SUPERCARD_TYPE::SC_SD;
|
||||||
|
default:
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
SuperCardType = get_next(SuperCardType);
|
||||||
|
printHeader();
|
||||||
consoleSelect(&btConsole);
|
consoleSelect(&btConsole);
|
||||||
} else {
|
} else {
|
||||||
switch (keysDown()) {
|
switch (keysDown()) {
|
||||||
@ -380,13 +312,13 @@ void vBlankHandler (void) {
|
|||||||
if (UpdateProgressText) {
|
if (UpdateProgressText) {
|
||||||
if (!ClearOnUpdate) { ClearOnUpdate = true; } else { consoleClear(); }
|
if (!ClearOnUpdate) { ClearOnUpdate = true; } else { consoleClear(); }
|
||||||
printf(textBuffer);
|
printf(textBuffer);
|
||||||
if (!PrintWithStat) {
|
if (!PrintWithStat) {
|
||||||
PrintWithStat = true;
|
PrintWithStat = true;
|
||||||
} else {
|
} else {
|
||||||
if (FileSuccess && (currentConsole != &btConsole)) {
|
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 {
|
} else {
|
||||||
iprintf("%lx \n", statData);
|
iprintf("%lx \n", statData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateProgressText = false;
|
UpdateProgressText = false;
|
||||||
@ -416,32 +348,56 @@ int main(void) {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
cachedFlashID = sc_flash_id();
|
SuperCardType = detect_supercard_type();
|
||||||
textBuffer = "\n\n\n\n\n\n\n\n\n\n\n Flash ID ";
|
cachedFlashID = get_flash_id();
|
||||||
statData = cachedFlashID;
|
if(cachedFlashID == 0 || SuperCardType == SUPERCARD_TYPE::UNK) {
|
||||||
UpdateProgressText = true;
|
textBuffer = "\n\n\n\n\n\n\n\n\n\nThe cart has not been recognized\n\n"
|
||||||
while(UpdateProgressText)swiWaitForVBlank();
|
"If you're sure you got a\n"
|
||||||
statData = 0;
|
"supercard\n"
|
||||||
|
"You'll have to manually\n"
|
||||||
scfw_buffer = (u8*)malloc(0x80000);
|
"pick if it's a supercard lite,\n"
|
||||||
|
"rumble or normal";
|
||||||
|
PrintWithStat = false;
|
||||||
|
UpdateProgressText = true;
|
||||||
|
while(UpdateProgressText)swiWaitForVBlank();
|
||||||
|
consoleSelect(&btConsole);
|
||||||
|
printf("\n Press [A] or [B] to continue.\n");
|
||||||
|
[] {
|
||||||
|
while(1) {
|
||||||
|
swiWaitForVBlank();
|
||||||
|
scanKeys();
|
||||||
|
switch (keysDown()) {
|
||||||
|
case KEY_A: return;
|
||||||
|
case KEY_B: return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
printHeader();
|
||||||
|
|
||||||
|
firmSize = get_max_firm_size();
|
||||||
|
scfw_buffer = (u16*)malloc(firmSize);
|
||||||
|
toncset(scfw_buffer, 0xFF, firmSize);
|
||||||
|
|
||||||
if (fatInitDefault()) {
|
if (fatInitDefault()) {
|
||||||
FILE *src = NULL;
|
FILE *src = NULL;
|
||||||
if (access("/firmware.frm", F_OK) == 0) {
|
consoleSelect(&btConsole);
|
||||||
src = fopen("/firmware.frm", "rb");
|
consoleClear();
|
||||||
} else if (access("/scfw/firmware.frm", F_OK) == 0) {
|
firmwareFilename = selectFirmware();
|
||||||
src = fopen("/scfw/firmware.frm", "rb");
|
consoleSelect(&tpConsole);
|
||||||
}
|
if(!firmwareFilename) {
|
||||||
|
FileSuccess = false;
|
||||||
|
} else {
|
||||||
|
src = fopen(firmwareFilename, "rb");
|
||||||
if (src) {
|
if (src) {
|
||||||
fseek(src, 0, SEEK_END);
|
fseek(src, 0, SEEK_END);
|
||||||
firmSize = ftell(src);
|
firmSize = ftell(src);
|
||||||
fseek(src, 0, SEEK_SET);
|
fseek(src, 0, SEEK_SET);
|
||||||
if (firmSize <= 0x80000) {
|
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);
|
consoleSelect(&btConsole);
|
||||||
printf("\n Reading FIRMWARE.FRM\n\n Please Wait...");
|
iprintf("\n Reading %s\n\n Please Wait...",firmwareFilename);
|
||||||
fread((u8*)scfw_buffer, 1, firmSize, src);
|
FileSuccess = fread((u8*)scfw_buffer, firmSize, 1, src) == 1;
|
||||||
FileSuccess = true;
|
|
||||||
fclose(src);
|
fclose(src);
|
||||||
consoleClear();
|
consoleClear();
|
||||||
} else {
|
} else {
|
||||||
@ -450,26 +406,28 @@ int main(void) {
|
|||||||
} else {
|
} else {
|
||||||
FileSuccess = false;
|
FileSuccess = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
FileSuccess = false;
|
FileSuccess = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FileSuccess) {
|
if (!FileSuccess) {
|
||||||
|
printf("\n\n\n\n\n\n\n\n [File error!]\n [USING SCFW's firmware.frm]");
|
||||||
consoleSelect(&btConsole);
|
consoleSelect(&btConsole);
|
||||||
toncset(scfw_buffer, 0xFF, 0x80000);
|
consoleClear();
|
||||||
tonccpy(scfw_buffer, scfw_bin, (scfw_binEnd - scfw_bin));
|
tonccpy(scfw_buffer, scfw_bin, (scfw_binEnd - scfw_bin));
|
||||||
firmSize = (scfw_binEnd - scfw_bin);
|
firmSize = (scfw_binEnd - scfw_bin);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n Press [A] to kill supercard.\n");
|
printf("\n Press [A] to kill supercard.\n");
|
||||||
printf(" Press [B] to spare supercard.\n");
|
printf(" Press [B] to spare supercard.\n");
|
||||||
|
|
||||||
if (!Prompt())return 0;
|
if (!Prompt())return 0;
|
||||||
|
|
||||||
consoleClear();
|
consoleClear();
|
||||||
|
|
||||||
if (SCLiteMode) { DoFlash_Lite(); } else { DoFlash(); }
|
DoFlash();
|
||||||
|
|
||||||
while(1) {
|
while(1) {
|
||||||
swiWaitForVBlank();
|
swiWaitForVBlank();
|
||||||
scanKeys();
|
scanKeys();
|
||||||
|
Loading…
Reference in New Issue
Block a user