diff --git a/include/arm11/filebrowser.h b/include/arm11/filebrowser.h new file mode 100644 index 0000000..c513285 --- /dev/null +++ b/include/arm11/filebrowser.h @@ -0,0 +1,7 @@ +#pragma once + +#include "error_codes.h" + + + +Result browseFiles(const char *const basePath, char selected[512]); diff --git a/include/arm11/hardware/lgyfb.h b/include/arm11/hardware/lgyfb.h index f5e4812..e9a7f92 100644 --- a/include/arm11/hardware/lgyfb.h +++ b/include/arm11/hardware/lgyfb.h @@ -34,3 +34,7 @@ void LGYFB_init(void); void LGYFB_processFrame(void); void LGYFB_deinit(void); + +#ifndef NDEBUG +void LGYFB_dbgDumpFrame(void); +#endif diff --git a/include/error_codes.h b/include/error_codes.h index b79ec71..eac445d 100644 --- a/include/error_codes.h +++ b/include/error_codes.h @@ -15,28 +15,29 @@ enum RES_OK = 0u, RES_SD_CARD_REMOVED = 1u, RES_INVALID_ARG = 2u, + RES_OUT_OF_MEM = 3u, // fatfs errors. - // Caution: Update fs.c on ARM9 if this changes! - RES_FR_DISK_ERR = 3u, /* (1) A hard error occurred in the low level disk I/O layer */ - RES_FR_INT_ERR = 4u, /* (2) Assertion failed */ - RES_FR_NOT_READY = 5u, /* (3) The physical drive cannot work */ - RES_FR_NO_FILE = 6u, /* (4) Could not find the file */ - RES_FR_NO_PATH = 7u, /* (5) Could not find the path */ - RES_FR_INVALID_NAME = 8u, /* (6) The path name format is invalid */ - RES_FR_DENIED = 9u, /* (7) Access denied due to prohibited access or directory full */ - RES_FR_EXIST = 10u, /* (8) Access denied due to prohibited access */ - RES_FR_INVALID_OBJECT = 11u, /* (9) The file/directory object is invalid */ - RES_FR_WRITE_PROTECTED = 12u, /* (10) The physical drive is write protected */ - RES_FR_INVALID_DRIVE = 13u, /* (11) The logical drive number is invalid */ - RES_FR_NOT_ENABLED = 14u, /* (12) The volume has no work area */ - RES_FR_NO_FILESYSTEM = 15u, /* (13) There is no valid FAT volume */ - RES_FR_MKFS_ABORTED = 16u, /* (14) The f_mkfs() aborted due to any problem */ - RES_FR_TIMEOUT = 17u, /* (15) Could not get a grant to access the volume within defined period */ - RES_FR_LOCKED = 18u, /* (16) The operation is rejected according to the file sharing policy */ - RES_FR_NOT_ENOUGH_CORE = 19u, /* (17) LFN working buffer could not be allocated */ - RES_FR_TOO_MANY_OPEN_FILES = 20u, /* (18) Number of open files > FF_FS_LOCK */ - RES_FR_INVALID_PARAMETER = 21u, /* (19) Given parameter is invalid */ + // Caution: Update fres2Res() in fs.c on ARM9 if this changes! + RES_FR_DISK_ERR = 4u, /* (1) A hard error occurred in the low level disk I/O layer */ + RES_FR_INT_ERR = 5u, /* (2) Assertion failed */ + RES_FR_NOT_READY = 6u, /* (3) The physical drive cannot work */ + RES_FR_NO_FILE = 7u, /* (4) Could not find the file */ + RES_FR_NO_PATH = 8u, /* (5) Could not find the path */ + RES_FR_INVALID_NAME = 9u, /* (6) The path name format is invalid */ + RES_FR_DENIED = 10u, /* (7) Access denied due to prohibited access or directory full */ + RES_FR_EXIST = 11u, /* (8) Access denied due to prohibited access */ + RES_FR_INVALID_OBJECT = 12u, /* (9) The file/directory object is invalid */ + RES_FR_WRITE_PROTECTED = 13u, /* (10) The physical drive is write protected */ + RES_FR_INVALID_DRIVE = 14u, /* (11) The logical drive number is invalid */ + RES_FR_NOT_ENABLED = 15u, /* (12) The volume has no work area */ + RES_FR_NO_FILESYSTEM = 16u, /* (13) There is no valid FAT volume */ + RES_FR_MKFS_ABORTED = 17u, /* (14) The f_mkfs() aborted due to any problem */ + RES_FR_TIMEOUT = 18u, /* (15) Could not get a grant to access the volume within defined period */ + RES_FR_LOCKED = 19u, /* (16) The operation is rejected according to the file sharing policy */ + RES_FR_NOT_ENOUGH_CORE = 20u, /* (17) LFN working buffer could not be allocated */ + RES_FR_TOO_MANY_OPEN_FILES = 21u, /* (18) Number of open files > FF_FS_LOCK */ + RES_FR_INVALID_PARAMETER = 22u, /* (19) Given parameter is invalid */ // Custom errors. RES_ROM_TOO_BIG = MAKE_CUSTOM_ERR(0), diff --git a/include/hardware/lgy.h b/include/hardware/lgy.h index 7b10c5b..6f0da34 100644 --- a/include/hardware/lgy.h +++ b/include/hardware/lgy.h @@ -107,7 +107,7 @@ Result LGY_setGbaRtc(const GbaRtc rtc); Result LGY_getGbaRtc(GbaRtc *const out); Result LGY_backupGbaSave(void); #ifdef ARM11 -Result LGY_prepareGbaMode(bool biosIntro, const char *const romPath, const char *const savePath); +Result LGY_prepareGbaMode(bool biosIntro, char *const romPath); void LGY_switchMode(void); void LGY_handleEvents(void); void LGY_deinit(void); diff --git a/source/arm11/filebrowser.c b/source/arm11/filebrowser.c new file mode 100644 index 0000000..83e18d0 --- /dev/null +++ b/source/arm11/filebrowser.c @@ -0,0 +1,194 @@ +#include +#include +#include "types.h" +#include "error_codes.h" +#include "fs.h" +#include "arm11/hardware/hid.h" +#include "arm11/fmt.h" +#include "hardware/gfx.h" + + +#define MAX_DIR_ENTRIES (510u) +#define DIR_READ_BLOCKS (10u) +#define SCREEN_COLS (52u) +#define SCREEN_ROWS (24u) + + +typedef struct +{ + u32 num; + const char *strPtrs[MAX_DIR_ENTRIES]; + u8 entTypes[MAX_DIR_ENTRIES]; // 0 = file, 1 = dir + char strBufs[MAX_DIR_ENTRIES][256]; +} DirList; + + + +// num including null terminator. +size_t safeStrcpy(char *const dst, const char *const src, size_t num) +{ + if(num == 0) return 0; + + const size_t len = strlen(src) + 1; + if(len > num) + { + *dst = '\0'; + return 1; + } + + strcpy(dst, src); + return len; +} + +static Result scanDir(const char *const path, DirList *const dList) +{ + FILINFO *const fi = (FILINFO*)malloc(DIR_READ_BLOCKS * sizeof(FILINFO)); + if(fi == NULL) return RES_OUT_OF_MEM; + + memset(dList, 0, sizeof(DirList)); + + Result res; + DHandle dh; + if((res = fOpenDir(&dh, path)) == RES_OK) + { + u32 read; + u32 totalRead = 0; + do + { + if((res = fReadDir(dh, fi, DIR_READ_BLOCKS, &read)) != RES_OK) break; + if(totalRead + read > MAX_DIR_ENTRIES) break; + + for(u32 i = 0; i < read; i++) + { + const u32 dListPos = totalRead + i; + dList->strPtrs[dListPos] = dList->strBufs[dListPos]; + + // Mark as dir. + if(fi[i].fattrib & AM_DIR) dList->entTypes[dListPos] = 1; + + safeStrcpy(dList->strBufs[dListPos], fi[i].fname, 256); + } + + totalRead += read; + } while(read == DIR_READ_BLOCKS); + dList->num = totalRead; + + fCloseDir(dh); + } + + free(fi); + + return res; +} + +static void showDirList(const DirList *const dList, u32 start) +{ + // Clear screen. + ee_printf("\x1b[2J"); + + const u32 listLength = (dList->num - start > SCREEN_ROWS ? start + SCREEN_ROWS : dList->num); + for(u32 i = start; i < listLength; i++) + { + const char *const printStr = + (dList->entTypes[i] == 0 ? "\x1b[%lu;H\x1b[37m %.51s" : "\x1b[%lu;H\x1b[33m %.51s"); + + ee_printf(printStr, i - start, dList->strPtrs[i]); + } +} + +// TODO: Handle empty dirs. +Result browseFiles(const char *const basePath, char selected[512]) +{ + if(basePath == NULL || selected == NULL) return RES_INVALID_ARG; + + char *curDir = (char*)malloc(512); + if(curDir == NULL) return RES_OUT_OF_MEM; + safeStrcpy(curDir, basePath, 512); + + DirList *const dList = (DirList*)malloc(sizeof(DirList)); + if(dList == NULL) return RES_OUT_OF_MEM; + + Result res; + if((res = scanDir(curDir, dList)) != RES_OK) goto end; + showDirList(dList, 0); + + s32 cursorPos = 0; // Within the entire list. + u32 windowPos = 0; // Window start position within the list. + s32 oldCursorPos = 0; + while(1) + { + ee_printf("\x1b[%lu;H ", oldCursorPos - windowPos); // Clear old cursor. + ee_printf("\x1b[%lu;H\x1b[37m>", cursorPos - windowPos); // Draw cursor. + + u32 kDown; + do + { + GFX_waitForVBlank0(); + + hidScanInput(); + if(hidGetExtraKeys(0) & KEY_POWER) goto end; + kDown = hidKeysDown(); + } while(kDown == 0); + + oldCursorPos = cursorPos; + if(kDown & KEY_DRIGHT) cursorPos += SCREEN_ROWS; + if(kDown & KEY_DLEFT) cursorPos -= SCREEN_ROWS; + if(kDown & KEY_DUP) cursorPos -= 1; + if(kDown & KEY_DDOWN) cursorPos += 1; + + if(cursorPos < 0) cursorPos = dList->num - 1; // Wrap to end of list. + if((u32)cursorPos > (dList->num - 1)) cursorPos = 0; // Wrap to start of list. + + if((u32)cursorPos < windowPos) + { + windowPos = cursorPos; + showDirList(dList, windowPos); + } + if((u32)cursorPos >= windowPos + SCREEN_ROWS) + { + windowPos = cursorPos - (SCREEN_ROWS - 1); + showDirList(dList, windowPos); + } + + if(kDown & (KEY_A | KEY_B)) + { + if(kDown & KEY_A) + { + u32 pathLen = strlen(curDir); + // TODO: !!! Insecure !!! + if(curDir[pathLen - 1] != '/') curDir[pathLen++] = '/'; + safeStrcpy(curDir + pathLen, dList->strPtrs[cursorPos], 512); + + if(dList->entTypes[cursorPos] == 0) + { + safeStrcpy(selected, curDir, 512); + break; + } + } + if(kDown & KEY_B) + { + const u32 pathLen = strlen(curDir); + if(curDir[pathLen - 2] != ':') + { + char *tmpPathPtr = curDir + pathLen; + while(*--tmpPathPtr != '/'); + *tmpPathPtr = '\0'; + } + } + + if((res = scanDir(curDir, dList)) != RES_OK) break; + cursorPos = 0; + windowPos = 0; + showDirList(dList, 0); + } + } + +end: + free(dList); + free(curDir); + + // Clear screen. + ee_printf("\x1b[2J"); + + return res; +} diff --git a/source/arm11/hardware/lgy.c b/source/arm11/hardware/lgy.c index f8521eb..282fc69 100644 --- a/source/arm11/hardware/lgy.c +++ b/source/arm11/hardware/lgy.c @@ -84,9 +84,10 @@ static u16 checkSaveOverride(u32 gameCode) u16 saveType; } overrideLut[] = { - {"\0\0\0\0", SAVE_TYPE_SRAM_256k}, // Homebrew. + {"\0\0\0\0", SAVE_TYPE_SRAM_256k}, // Homebrew. TODO: Set WAITCNT to 0x4014? {"GMB\0", SAVE_TYPE_SRAM_256k}, // Goomba Color (Homebrew). {"AA2\0", SAVE_TYPE_EEPROM_64k}, // Super Mario Advance 2. + {"A3A\0", SAVE_TYPE_EEPROM_64k}, // Super Mario Advance 3. }; for(u32 i = 0; i < sizeof(overrideLut) / sizeof(*overrideLut); i++) @@ -131,7 +132,7 @@ static u16 tryDetectSaveType(u32 romSize) {"EEPROM_V111", SAVE_TYPE_EEPROM_8k}, // Actually EEPROM 4k. {"EEPROM_V120", SAVE_TYPE_EEPROM_8k}, // Confirmed. {"EEPROM_V121", SAVE_TYPE_EEPROM_64k}, // Confirmed. - {"EEPROM_V122", SAVE_TYPE_EEPROM_8k}, // Confirmed. Except Super Mario Advance 2. + {"EEPROM_V122", SAVE_TYPE_EEPROM_8k}, // Confirmed. Except Super Mario Advance 2/3. {"EEPROM_V124", SAVE_TYPE_EEPROM_64k}, // Confirmed. {"EEPROM_V125", SAVE_TYPE_EEPROM_8k}, // Confirmed. {"EEPROM_V126", SAVE_TYPE_EEPROM_8k}, // Confirmed. @@ -198,7 +199,7 @@ static void setupFcramForGbaMode(void) while(REG_PDN_FCRAM_CNT & PDN_FCRAM_CNT_CLK_E_ACK); // Wait until clock is disabled. } -Result LGY_prepareGbaMode(bool biosIntro, const char *const romPath, const char *const savePath) +Result LGY_prepareGbaMode(bool biosIntro, char *const romPath) { // Load the ROM image. u32 romSize; @@ -209,9 +210,12 @@ Result LGY_prepareGbaMode(bool biosIntro, const char *const romPath, const char const u16 saveType = tryDetectSaveType(romSize); // Prepare ARM9 for GBA mode + settings and save loading. + const u32 romPathLen = strlen(romPath); + strcpy(romPath + romPathLen - 4, ".sav"); + u32 cmdBuf[4]; - cmdBuf[0] = (u32)savePath; - cmdBuf[1] = strlen(savePath) + 1; + cmdBuf[0] = (u32)romPath; + cmdBuf[1] = romPathLen + 1; cmdBuf[2] = biosIntro; cmdBuf[3] = saveType; res = PXI_sendCmd(IPC_CMD9_PREPARE_GBA, cmdBuf, 4); @@ -256,6 +260,8 @@ void LGY_switchMode(void) } #ifndef NDEBUG +#include "arm11/hardware/gx.h" +#include "arm11/hardware/gpu_regs.h" void debugTests(void) { const u32 kDown = hidKeysDown(); @@ -265,7 +271,20 @@ void debugTests(void) { GbaRtc rtc; LGY_getGbaRtc(&rtc); ee_printf("RTC: %02X.%02X.%04X %02X:%02X:%02X\n", rtc.d, rtc.mon, rtc.y + 0x2000u, rtc.h, rtc.min, rtc.s); + + /*static u8 filter = 1; + filter ^= 1; + u32 texEnvSource = 0x000F000F; + u32 texEnvCombiner = 0x00000000; + if(filter == 1) + { + texEnvSource = 0x00FF00FFu; + texEnvCombiner = 0x00010001u; + } + REG_GX_P3D(GPUREG_TEXENV1_SOURCE) = texEnvSource; + REG_GX_P3D(GPUREG_TEXENV1_COMBINER) = texEnvCombiner;*/ } + if(kDown & KEY_Y) LGYFB_dbgDumpFrame(); } #endif diff --git a/source/arm11/hardware/lgyfb.c b/source/arm11/hardware/lgyfb.c index e9784d3..abc0919 100644 --- a/source/arm11/hardware/lgyfb.c +++ b/source/arm11/hardware/lgyfb.c @@ -63,13 +63,20 @@ void LGYFB_init(void) REG_LGYFB_TOP_SIZE = LGYFB_SIZE(256u, 160u); REG_LGYFB_TOP_STAT = LGYFB_IRQ_MASK; REG_LGYFB_TOP_IRQ = 0; + // With RGB8 output solid red and blue are converted to 0xF8 and green to 0xFA. + // So either the hardware uses RGB565 internally or RGB666 with different conversion for green. + // Some results: + // RGBA8: Same as RGB8 but with useless alpha component. + // RGB8: Observed best format. No dithering and best color accuracy (if we ignore the lazy conversion). + // RGB565: A little dithering. Good color accuracy. + // RGB5551: Lots of dithering. Good color accuracy (a little worse than 565). REG_LGYFB_TOP_ALPHA = 0xFF; REG_LGYFB_TOP_CNT = LGYFB_DMA_E | LGYFB_OUT_SWIZZLE | LGYFB_OUT_FMT_8880 | LGYFB_ENABLE; IRQ_registerIsr(IRQ_CDMA_EVENT0, 13, 0, lgyFbDmaIrqHandler); } -void rotateFrame(void) +static void rotateFrame(void) { // With dark filter. alignas(16) static const u8 firstList[1136] = @@ -122,7 +129,7 @@ alignas(16) static const u8 firstList[1136] = 0x18, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x4F, 0x80, 0x00, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x01, 0x00, 0x00, 0x00, // Last 4 bytes: Texture format. 0x8E, 0x00, 0x0F, 0x00, 0x01, 0x10, 0x01, 0x00, 0x80, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x07, 0x00, @@ -397,3 +404,13 @@ void LGYFB_deinit(void) IRQ_unregisterIsr(IRQ_CDMA_EVENT0); } + +#ifndef NDEBUG +#include "fsutil.h" +void LGYFB_dbgDumpFrame(void) +{ + GX_displayTransfer((u32*)0x18200000, 160u<<16 | 256u, (u32*)0x18400000, 160u<<16 | 256u, 1u<<12 | 1u<<8); + GFX_waitForEvent(GFX_EVENT_PPF, false); + fsQuickWrite((void*)0x18400000, "sdmc:/lgyfb_dbg_frame.bgr", 256 * 160 * 3); +} +#endif diff --git a/source/arm11/hardware/lgyfb.dma330 b/source/arm11/hardware/lgyfb.dma330 index ffb0ff1..46364d3 100644 --- a/source/arm11/hardware/lgyfb.dma330 +++ b/source/arm11/hardware/lgyfb.dma330 @@ -16,7 +16,9 @@ LPFE # LgyFb sometimes (at the end of each scanline?) sends # single requests. Since we can transfer all 8 scanlines # with bursts only we will ignore them. - LP 47 + # LP 31 # RGB5551 and RGB565 + LP 47 # RGB8 + # LP 63 # RGBA8 LDB STB LPENDB diff --git a/source/arm11/main.c b/source/arm11/main.c index 53fd286..1cadecc 100644 --- a/source/arm11/main.c +++ b/source/arm11/main.c @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#include #include "types.h" #include "arm11/hardware/hid.h" #include "arm11/hardware/codec.h" @@ -25,6 +26,7 @@ #include "arm11/power.h" #include "hardware/gfx.h" #include "fs.h" +#include "arm11/filebrowser.h" #include "arm.h" @@ -35,11 +37,15 @@ int main(void) GFX_setBrightness(DEFAULT_BRIGHTNESS, DEFAULT_BRIGHTNESS); consoleInit(SCREEN_BOT, NULL); //CODEC_init(); - fMount(FS_DRIVE_SDMC); + + Result res; + char *romPath = (char*)malloc(512); + *romPath = '\0'; + if((res = fMount(FS_DRIVE_SDMC)) != RES_OK || (res = browseFiles("sdmc:/", romPath)) != RES_OK || *romPath == '\0') + goto end; ee_puts("Reading ROM and save..."); - Result res; - if((res = LGY_prepareGbaMode(false, "sdmc:/rom.gba", "sdmc:/rom.sav")) == RES_OK) + if((res = LGY_prepareGbaMode(false, romPath)) == RES_OK) { #ifdef NDEBUG GFX_setForceBlack(false, true); @@ -59,7 +65,10 @@ int main(void) __wfi(); } while(1); } - else printErrorWaitInput(res, 0); + +end: + free(romPath); + if(res != RES_OK) printErrorWaitInput(res, 0); LGY_deinit(); fUnmount(FS_DRIVE_SDMC); diff --git a/source/arm9/arm7_stub.s b/source/arm9/arm7_stub.s index e4e0b89..bd00928 100644 --- a/source/arm9/arm7_stub.s +++ b/source/arm9/arm7_stub.s @@ -11,11 +11,11 @@ BEGIN_ASM_FUNC _arm7_stub_start mov r0, #PSR_INT_OFF | PSR_SVC_MODE adr r1, _arm7_stub_start + 0x200 @ 0x3008000 msr CPSR_cxsf, r0 - mov r0, #PSR_INT_OFF | PSR_IRQ_MODE + @mov r0, #PSR_INT_OFF | PSR_IRQ_MODE mov sp, r1 - msr CPSR_cxsf, r0 + @msr CPSR_cxsf, r0 mov r0, #PSR_INT_OFF | PSR_SYS_MODE - sub sp, r1, #0x60 @ 0x3007FA0 + @sub sp, r1, #0x60 @ 0x3007FA0 msr CPSR_cxsf, r0 mov r3, #0x4700000 adr r2, _arm7_stub_16 + 1 diff --git a/source/arm9/fs.c b/source/arm9/fs.c index b553fbf..266b28f 100644 --- a/source/arm9/fs.c +++ b/source/arm9/fs.c @@ -44,7 +44,7 @@ static struct static Result fres2Res(FRESULT fr) { - if(fr != FR_OK) return fr + 2; + if(fr != FR_OK) return fr + 3; else return RES_OK; } diff --git a/source/arm9/hardware/lgy.c b/source/arm9/hardware/lgy.c index fe74b42..aa320e2 100644 --- a/source/arm9/hardware/lgy.c +++ b/source/arm9/hardware/lgy.c @@ -27,7 +27,7 @@ static u32 g_saveSize = 0; static u32 g_saveHash[8] = {0}; -static char g_savePath[256] = {0}; +static char g_savePath[512] = {0}; @@ -82,7 +82,7 @@ Result LGY_prepareGbaMode(bool biosIntro, u16 saveType, const char *const savePa setupBiosOverlay(biosIntro); setupSaveType(saveType); - strncpy_s(g_savePath, savePath, 255, 256); + strncpy_s(g_savePath, savePath, 511, 512); Result res = RES_OK; if(g_saveSize != 0) diff --git a/source/error_codes.c b/source/error_codes.c index 57ad618..71f5cb2 100644 --- a/source/error_codes.c +++ b/source/error_codes.c @@ -16,6 +16,7 @@ void printError(Result res) "OK", "SD card removed", "Invalid argument", + "Out of memory", // fatfs errors. "fatfs disk error",