Compare commits

...

17 Commits
1.0 ... 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
ApacheThunder
af08464f38 Add 32KB DLDI support.
* Add 32KB DLDI support.
2024-11-28 19:50:00 -06:00
ApacheThunder
63c633fbd1
Merge pull request #2 from edo9300/rumble
Add Supercard Rumble support
2024-11-28 19:37:39 -06:00
Edoardo Lolletti
db588bf012
The rumble actually has 2mb of flash, not 1 2024-11-29 00:56:47 +01:00
Edoardo Lolletti
0c4185717d Add Supercard Rumble support 2024-11-28 21:52:29 +01:00
ApacheThunder
72cc60ad9b
Merge pull request #1 from edo9300/patch-1
Unify flash routines and auto detect supercard lite
2024-10-28 14:22:08 -05:00
Edoardo Lolletti
8ad1a717eb Updated supercard flash id detection
Don't assume random flash id values, but actually check the gba bus if there was any error in the operation
2024-10-27 11:31:21 +01:00
Edoardo Lolletti
939bac205c
Unify flash routines and auto detect supercard lite 2024-10-26 18:01:22 +02:00
ApacheThunder
2d8ad6649e Finally fix SCLite!
* SC Lite is now fully flashable.
* The last 2 blocks (about 16KB total) appears to be locked by FPGA. It
is not know if this area can be written to. Still better then before
where we could only write to half the chp. :P
2024-10-16 18:37:48 -05:00
ApacheThunder
ccd4951c9a Improve Block_Erase
* Block_Erase is now able to erase 90% of 512KB flash on SC Lite.
Unknown why a small bit at end isn't erased. So this function still
needs improvement.
* Write function for SC LIte is now able to write the first 256KB of
flash. I believe it was only doing somewhere around 150ish KB before.
The second half of flash isn't written yet so this also still needs
improvement. Still more then enough for most custom firmwares though.
2024-07-02 16:43:47 -05:00
ApacheThunder
04f43cd598
Update README.md 2024-06-02 01:51:04 -05:00
ApacheThunder
f25fed6558
Update README.md 2024-06-02 01:50:13 -05:00
7 changed files with 736 additions and 306 deletions

1
.gitignore vendored
View File

@ -48,6 +48,7 @@ data
# Stuff used to build custom banner/misc other things that shouldn't be in repo.
*.dldi
*.zip
*.rar
# Stuff generated by Windows.
Thumbs.db

View File

@ -8,6 +8,7 @@ https://gbatemp.net/threads/scfw-custom-firmware-kernel-for-supercard.647238/
## Credits
[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
[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

View File

@ -28,8 +28,8 @@ ARCH := -mthumb -mthumb-interwork -march=armv5te -mtune=arm946e-s
CFLAGS := -g -Wall -Os \
$(ARCH) $(INCLUDE) -DARM9
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++1z
ASFLAGS := -g $(ARCH) $(INCLUDE)
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
#---------------------------------------------------------------------------------

100
arm9/source/dldi_32K.s Normal file
View 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
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

@ -4,291 +4,221 @@
#include <fat.h>
#include <stdio.h>
#include <nds/arm9/console.h>
#include <utility>
#include "font.h"
#include "tonccpy.h"
#include "fileSelector.h"
#define SC_MODE_REG *(vu16*)0x09FFFFFE
#define SC_MODE_MAGIC (u16)0xA55A
#define SC_MODE_FLASH_RW (u16)0x4
#define SC_MODE_FLASH_RW_LITE (u16)0x1510
enum class SC_FLASH_COMMAND : u16 {
ERASE = 0x80,
ERASE_BLOCK = 0x30,
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_IDENTIFY ((u16) 0x90)
#define FlashBase 0x08000000
extern u8 scfw_bin[];
extern u8 scfw_binEnd[];
static u8* scfw_buffer;
u16* scfw_buffer;
static PrintConsole tpConsole;
static PrintConsole btConsole;
extern PrintConsole* currentConsole;
char *firmwareFilename = NULL;
static int bg;
static int bgSub;
const char* textBuffer = "X------------------------------X\nX------------------------------X";
volatile u32 cachedFlashID;
volatile u32 statData = 0x00000000;
volatile u32 firmSize = 0x80000;
volatile bool UpdateProgressText = false;
volatile bool PrintWithStat = true;
volatile bool ClearOnUpdate = true;
volatile bool SCLiteMode = false;
volatile bool FileSuccess = false;
u32 cachedFlashID;
u32 statData = 0x00000000;
u32 firmSize = 0x200000;
bool UpdateProgressText = false;
bool PrintWithStat = true;
bool ClearOnUpdate = true;
SUPERCARD_TYPE SuperCardType = SUPERCARD_TYPE::UNK;
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) {
vu16 v1,v2;
u32 Address;
u32 loop;
u32 off = 0;
Address = blockAdd;
*((vu16 *)(FlashBase+0x555*2)) = 0xF0;
*((vu16 *)(FlashBase+0x1555*2)) = 0xF0;
if((blockAdd == 0) || (blockAdd == 0x1FC0000) || (blockAdd == 0xFC0000) || (blockAdd == 0x1000000)) {
for(loop = 0; loop < 0x40000; loop += 0x8000) {
*((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+loop)) = 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+loop+0x2000)) = 0x30;
*((vu16 *)(FlashBase+off+0x2555*2)) = 0xAA;
*((vu16 *)(FlashBase+off+0x22AA*2)) = 0x55;
*((vu16 *)(FlashBase+off+0x2555*2)) = 0x80;
*((vu16 *)(FlashBase+off+0x2555*2)) = 0xAA;
*((vu16 *)(FlashBase+off+0x22AA*2)) = 0x55;
*((vu16 *)(FlashBase+Address+loop+0x4000)) = 0x30;
*((vu16 *)(FlashBase+off+0x3555*2)) = 0xAA;
*((vu16 *)(FlashBase+off+0x32AA*2)) = 0x55;
*((vu16 *)(FlashBase+off+0x3555*2)) = 0x80;
*((vu16 *)(FlashBase+off+0x3555*2)) = 0xAA;
*((vu16 *)(FlashBase+off+0x32AA*2)) = 0x55;
*((vu16 *)(FlashBase+Address+loop+0x6000)) = 0x30;
do {
v1 = *((vu16 *)(FlashBase+Address+loop));
v2 = *((vu16 *)(FlashBase+Address+loop));
} while(v1!=v2);
do {
v1 = *((vu16 *)(FlashBase+Address+loop+0x2000));
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);
static void change_mode(u16 mode) {
auto* const SC_MODE_REG = (vu16*)0x09FFFFFE;
const u16 SC_MODE_MAGIC = 0xA55A;
*SC_MODE_REG = SC_MODE_MAGIC;
*SC_MODE_REG = SC_MODE_MAGIC;
*SC_MODE_REG = mode;
*SC_MODE_REG = mode;
}
} 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);
SUPERCARD_TYPE detect_supercard_type() {
change_mode(SC_MODE_SDCARD);
auto val = *(volatile uint16_t*)0x09800000;
switch(val & 0xe300) {
case 0xa000:
return SUPERCARD_TYPE::SC_LITE;
case 0xc000:
return SUPERCARD_TYPE::SC_RUMBLE;
case 0xe000:
return SUPERCARD_TYPE::SC_SD;
default:
return SUPERCARD_TYPE::UNK;
}
}
// Modified version of WriteNorFlash from EZFlash 3in1 card lib
void WriteNorFlash_SCLite(u32 address, u8 *buffer, u32 size) {
vu16 v1,v2;
u32 loopwrite;
vu16* buf = (vu16*)buffer;
u32 mapaddress;
v1 = 0;
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;
}
}
static std::pair<vu16*, vu16*> get_magic_addrs(SUPERCARD_TYPE scType = SuperCardType) {
auto* const SCLITE_FLASH_MAGIC_ADDR_1 = (vu16*)0x08000AAA;
auto* const SCLITE_FLASH_MAGIC_ADDR_2 = (vu16*)0x08000554;
auto* const SC_FLASH_MAGIC_ADDR_1 = (vu16*)0x08000b92;
auto* const SC_FLASH_MAGIC_ADDR_2 = (vu16*)0x0800046c;
if(scType == SUPERCARD_TYPE::SC_SD)
return { SC_FLASH_MAGIC_ADDR_1, SC_FLASH_MAGIC_ADDR_2 };
return { SCLITE_FLASH_MAGIC_ADDR_1, SCLITE_FLASH_MAGIC_ADDR_2 };
}
u32 sc_flash_id() {
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_IDENTIFY;
static u32 get_max_firm_size(SUPERCARD_TYPE scType = SuperCardType) {
switch(scType){
case SUPERCARD_TYPE::SC_SD:
return 0x80000;
case SUPERCARD_TYPE::SC_LITE:
return 0x7C000;
case SUPERCARD_TYPE::SC_RUMBLE:
case SUPERCARD_TYPE::UNK:
return 0x200000;
default:
__builtin_unreachable();
}
}
// should equal 0x000422b9
u32 res = SC_FLASH_MAGIC_ADDR_1;
res |= *GBA_BUS << 16;
static void send_command(SC_FLASH_COMMAND command, 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);
*magic_addr_1 = SC_FLASH_MAGIC_1;
*magic_addr_2 = SC_FLASH_MAGIC_2;
*magic_addr_1 = static_cast<u16>(command);
}
void sc_flash_rw_enable(SUPERCARD_TYPE scType = SuperCardType) {
change_mode((scType == SUPERCARD_TYPE::SC_SD) ? SC_MODE_FLASH_RW : SC_MODE_FLASH_RW_LITE_RUMBLE);
}
u32 get_flash_id(SUPERCARD_TYPE scType = SuperCardType) {
static constexpr u16 COMMAND_ERROR = 0x002e;
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;
return res;
}
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;
if(upper_half == COMMAND_ERROR)
return 0;
return (upper_half << 16) | magic;
}
void sc_flash_erase_chip() {
bool buf = REG_IME;
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_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;
send_command(SC_FLASH_COMMAND::ERASE);
send_command(SC_FLASH_COMMAND::ERASE_CHIP);
while (*GBA_BUS != *GBA_BUS)swiWaitForVBlank();
while (*GBA_BUS != *GBA_BUS);
*GBA_BUS = SC_FLASH_IDLE;
REG_IME = buf;
}
void sc_flash_erase_block(vu16 *addr) {
bool buf = REG_IME;
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_ERASE;
SC_FLASH_MAGIC_ADDR_1 = SC_FLASH_MAGIC_1;
SC_FLASH_MAGIC_ADDR_2 = SC_FLASH_MAGIC_2;
*addr = SC_FLASH_ERASE_BLOCK;
bool sc_flash_erase_block_rumble(vu16 *addr) {
vu16* addr1 = (vu16*)((0xfff8000 & ((intptr_t)addr)) + 0xaaa);
vu16* addr2 = (vu16*)((0xfff8000 & ((intptr_t)addr)) + 0x554);
while (*GBA_BUS != *GBA_BUS)swiWaitForVBlank();
*addr1 = 0xaa;
*addr2 = 0x55;
*addr1 = 0x80;
*addr1 = 0xaa;
*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;
REG_IME = buf;
}
void sc_flash_program(vu16 *addr, u16 val) {
bool buf = REG_IME;
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;
send_command(SC_FLASH_COMMAND::PROGRAM);
*addr = val;
while (*GBA_BUS != *GBA_BUS)swiWaitForVBlank();
while (*GBA_BUS != *GBA_BUS);
*GBA_BUS = SC_FLASH_IDLE;
REG_IME = buf;
}
bool DoFlash() {
auto* const FLASH_BASE = (vu16*)0x08000000;
sc_flash_rw_enable();
printf("\n Death 2 supercard :3\n");
printf(" Erasing whole chip\n");
sc_flash_erase_chip();
printf(" Erased whole chip\n");
for (int i = 0; i < 60; i++)swiWaitForVBlank();
for (u32 off = 0; off < firmSize; off += 2) {
u16 val = 0;
val |= scfw_buffer[off];
val |= (scfw_buffer[off+1] << 8);
sc_flash_program((vu16*)(0x08000000 + off), val);
if (!UpdateProgressText && !(off & 0x00ff)) {
auto total = (firmSize + 1) / 2; // account for odd sizes
u32 off = 0;
auto* flash_function = &sc_flash_program;
if(SuperCardType == SUPERCARD_TYPE::SC_RUMBLE) { // first 0x40000 bytes of a supercard rumble are read only
off = (0x40000 / 2);
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 ";
statData = (0x08000000 + off);
statData = (uintptr_t)(FLASH_BASE+off);
UpdateProgressText = true;
}
}
@ -297,32 +227,6 @@ bool DoFlash() {
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() {
videoSetMode(MODE_0_2D);
videoSetModeSub(MODE_0_2D);
@ -349,22 +253,50 @@ void CustomConsoleInit() {
consoleSelect(&tpConsole);
}
bool Prompt() {
while(1) {
swiWaitForVBlank();
scanKeys();
if ((keysDown() & KEY_UP) || (keysDown() & KEY_DOWN) || (keysDown() & KEY_LEFT) || (keysDown() & KEY_RIGHT)) {
if (SCLiteMode) { SCLiteMode = false; } else { SCLiteMode = true; }
void printHeader() {
consoleSelect(&tpConsole);
if (SCLiteMode) {
textBuffer = "\n\n [SCLITE MODE]\n\n\n\n\n\n\n\n\n Flash ID ";
} else {
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() {
while(1) {
swiWaitForVBlank();
scanKeys();
if ((keysDown() & KEY_UP) || (keysDown() & KEY_DOWN) || (keysDown() & KEY_LEFT) || (keysDown() & KEY_RIGHT)) {
consoleSelect(&tpConsole);
auto get_next = [](SUPERCARD_TYPE cur_mode){
switch(cur_mode) {
case SUPERCARD_TYPE::SC_SD:
return SUPERCARD_TYPE::SC_LITE;
case SUPERCARD_TYPE::SC_LITE:
return SUPERCARD_TYPE::SC_RUMBLE;
case SUPERCARD_TYPE::SC_RUMBLE:
return SUPERCARD_TYPE::SC_SD;
case SUPERCARD_TYPE::UNK:
return SUPERCARD_TYPE::SC_SD;
default:
__builtin_unreachable();
}
};
SuperCardType = get_next(SuperCardType);
printHeader();
consoleSelect(&btConsole);
} else {
switch (keysDown()) {
@ -384,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);
}
@ -416,32 +348,56 @@ int main(void) {
}
return 0;
}
cachedFlashID = sc_flash_id();
textBuffer = "\n\n\n\n\n\n\n\n\n\n\n Flash ID ";
statData = cachedFlashID;
SuperCardType = detect_supercard_type();
cachedFlashID = get_flash_id();
if(cachedFlashID == 0 || SuperCardType == SUPERCARD_TYPE::UNK) {
textBuffer = "\n\n\n\n\n\n\n\n\n\nThe cart has not been recognized\n\n"
"If you're sure you got a\n"
"supercard\n"
"You'll have to manually\n"
"pick if it's a supercard lite,\n"
"rumble or normal";
PrintWithStat = false;
UpdateProgressText = true;
while(UpdateProgressText)swiWaitForVBlank();
statData = 0;
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();
scfw_buffer = (u8*)malloc(0x80000);
firmSize = get_max_firm_size();
scfw_buffer = (u16*)malloc(firmSize);
toncset(scfw_buffer, 0xFF, firmSize);
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 <= 0x80000) {
printf("\n\n\n\n\n\n\n\n [FOUND FIRMWARE.FRM]");
if (firmSize <= get_max_firm_size()) {
iprintf("\n\n\n\n\n\n\n\n[FOUND %s]",firmwareFilename);
consoleSelect(&btConsole);
printf("\n Reading FIRMWARE.FRM\n\n Please Wait...");
fread((u8*)scfw_buffer, 1, firmSize, src);
FileSuccess = true;
iprintf("\n Reading %s\n\n Please Wait...",firmwareFilename);
FileSuccess = fread((u8*)scfw_buffer, firmSize, 1, src) == 1;
fclose(src);
consoleClear();
} else {
@ -450,13 +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);
toncset(scfw_buffer, 0xFF, 0x80000);
consoleClear();
tonccpy(scfw_buffer, scfw_bin, (scfw_binEnd - scfw_bin));
firmSize = (scfw_binEnd - scfw_bin);
}
@ -468,7 +426,7 @@ int main(void) {
consoleClear();
if (SCLiteMode) { DoFlash_Lite(); } else { DoFlash(); }
DoFlash();
while(1) {
swiWaitForVBlank();