diff --git a/include/arm11/open_agb_firm.h b/include/arm11/open_agb_firm.h
new file mode 100644
index 0000000..e51ef1e
--- /dev/null
+++ b/include/arm11/open_agb_firm.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "error_codes.h"
+
+
+
+Result oafInitAndRun(void);
+void oafUpdate(void);
+void oafFinish(void);
diff --git a/source/arm11/main.c b/source/arm11/main.c
index 19f4c83..7741b1c 100644
--- a/source/arm11/main.c
+++ b/source/arm11/main.c
@@ -16,699 +16,41 @@
* along with this program. If not, see .
*/
-#include
-#include
-#include
-#include "types.h"
-#include "arm_intrinsic.h"
-#include "util.h"
-#include "arm11/hardware/hash.h"
+#include "fs.h"
+#include "hardware/gfx.h"
+#include "arm11/console.h"
+#include "arm11/open_agb_firm.h"
#include "arm11/hardware/hid.h"
#include "arm11/hardware/codec.h"
-#include "hardware/lgy.h"
-#include "arm11/hardware/lgyfb.h"
-#include "arm11/console.h"
-#include "arm11/fmt.h"
-#include "arm11/hardware/mcu.h"
#include "arm11/power.h"
-#include "hardware/gfx.h"
-#include "fs.h"
-#include "fsutil.h"
-#include "inih/ini.h"
-#include "arm11/filebrowser.h"
-#include "arm.h"
-#include "arm11/hardware/lcd.h"
-#include "arm11/gpu_cmd_lists.h"
-#include "kernel.h"
-#include "kevent.h"
-#define OAF_WORK_DIR "sdmc:/3ds/open_agb_firm"
-#define INI_BUF_SIZE (1024u)
-#define DEFAULT_CONFIG "[general]\n" \
- "backlight=40\n" \
- "biosIntro=true\n\n" \
- "[video]\n" \
- "inGamma=2.2\n" \
- "outGamma=1.54\n" \
- "contrast=1.0\n" \
- "brightness=0.0\n"
-
-
-typedef struct
-{
- // [general]
- u8 backlight; // Both LCDs.
- bool biosIntro;
-
- // [video]
- float inGamma;
- float outGamma;
- float contrast;
- float brightness;
-
- // [audio]
- // Maybe?
-
- // [input]
- // TODO
-} OafConfig;
-static OafConfig g_oafConfig =
-{
- 40,
- true,
- 2.2f,
- 1.54f,
- 1.f,
- 0.f
-};
-
-
-
-static u32 padRomArea(u32 romFileSize)
-{
- // Pad unused ROM area with 0xFFs (trimmed ROMs).
- // Smallest retail ROM chip is 8 Mbit (1 MiB).
- u32 romSize = nextPow2(romFileSize);
- if(romSize < 0x100000u) romSize = 0x100000u;
- memset((void*)(ROM_LOC + romFileSize), 0xFFFFFFFFu, romSize - romFileSize);
-
- if(romSize > 0x100000u)
- {
- // Fake "open bus" padding.
- u32 padding = (ROM_LOC + romSize) / 2;
- padding = __pkhbt(padding, padding + 1, 16); // Copy lower half + 1 to upper half.
- for(uintptr_t i = ROM_LOC + romSize; i < ROM_LOC + MAX_ROM_SIZE; i += 4)
- {
- *(u32*)i = padding;
- padding = __uadd16(padding, 0x00020002u); // Unsigned parallel halfword-wise addition.
- }
- }
- else
- {
- // ROM mirroring (Classic NES Series/possibly others with 8 Mbit ROM).
- // Mirror ROM across the entire 32 MiB area.
- for(uintptr_t i = ROM_LOC + romSize; i < ROM_LOC + MAX_ROM_SIZE; i += romSize)
- {
- //memcpy((void*)i, (void*)(i - romSize), romSize); // 0x23A15DD
- memcpy((void*)i, (void*)ROM_LOC, romSize); // 0x237109B
- }
- }
-
- return romSize;
-}
-
-static Result loadGbaRom(const char *const path, u32 *const romSizeOut)
-{
- Result res;
- FHandle f;
- if((res = fOpen(&f, path, FA_OPEN_EXISTING | FA_READ)) == RES_OK)
- {
- u32 fileSize;
- if((fileSize = fSize(f)) <= MAX_ROM_SIZE)
- {
- u8 *ptr = (u8*)ROM_LOC;
- u32 read;
- while((res = fRead(f, ptr, 0x100000u, &read)) == RES_OK && read == 0x100000u)
- ptr += 0x100000u;
-
- *romSizeOut = padRomArea(fileSize);
- }
- else res = RES_ROM_TOO_BIG;
-
- fClose(f);
- }
-
- return res;
-}
-
-typedef struct
-{
- char name[200];
- char gameCode[4];
- u8 sha1[20];
- u32 attr;
-} GameDbEntry;
-
-// Search for entry with first u64 of the SHA1 = x using binary search.
-static Result searchGameDb(u64 x, GameDbEntry *const db, s32 *const entryPos)
-{
- debug_printf("Database search: '%016" PRIX64 "'\n", __builtin_bswap64(x));
-
- Result res;
- FHandle f;
- if((res = fOpen(&f, "gba_db.bin", FA_OPEN_EXISTING | FA_READ)) == RES_OK)
- {
- s32 l = 0;
- s32 r = fSize(f) / sizeof(GameDbEntry) - 1; // TODO: Check for 0!
- while(1)
- {
- const s32 mid = l + (r - l) / 2;
- debug_printf("l: %ld r: %ld mid: %ld\n", l, r, mid);
-
- if((res = fLseek(f, sizeof(GameDbEntry) * mid)) != RES_OK) break;
- if((res = fRead(f, db, sizeof(GameDbEntry), NULL)) != RES_OK) break;
- const u64 tmp = *(u64*)db->sha1; // Unaligned access.
- if(tmp == x)
- {
- *entryPos = mid; // TODO: Remove.
- break;
- }
-
- if(r <= l || r < 0)
- {
- res = RES_NOT_FOUND;
- break;
- }
-
- if(tmp > x) r = mid - 1;
- else l = mid + 1;
- }
-
- fClose(f);
- }
-
- return res;
-}
-
-static u16 checkSaveOverride(u32 gameCode)
-{
- if((gameCode & 0xFFu) == 'F') // Classic NES Series.
- {
- return SAVE_TYPE_EEPROM_8k;
- }
-
- static const struct
- {
- alignas(4) char gameCode[4];
- u16 saveType;
- } overrideLut[] =
- {
- {"\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.
- {"AZL\0", SAVE_TYPE_EEPROM_64k}, // Legend of Zelda, The - A Link to the Past & Four Swords.
- };
-
- for(u32 i = 0; i < sizeof(overrideLut) / sizeof(*overrideLut); i++)
- {
- // Compare Game Code without region.
- if((gameCode & 0xFFFFFFu) == *((u32*)overrideLut[i].gameCode))
- {
- return overrideLut[i].saveType;
- }
- }
-
- return 0xFF;
-}
-
-static u16 tryDetectSaveType(u32 romSize)
-{
- const u32 *romPtr = (u32*)ROM_LOC;
- u16 saveType;
- if((saveType = checkSaveOverride(romPtr[0xAC / 4])) != 0xFF)
- {
- debug_printf("Game Code in override list. Using save type %" PRIu16 ".\n", saveType);
- return saveType;
- }
-
- // Code based on: https://github.com/Gericom/GBARunner2/blob/master/arm9/source/save/Save.vram.cpp
- romPtr += 0xE4 / 4; // Skip headers.
- saveType = SAVE_TYPE_NONE;
- for(; romPtr < (u32*)(ROM_LOC + romSize); romPtr++)
- {
- u32 tmp = *romPtr;
-
- // "EEPR" "FLAS" "SRAM"
- if(tmp == 0x52504545u || tmp == 0x53414C46u || tmp == 0x4D415253u)
- {
- static const struct
- {
- const char *str;
- u16 saveType;
- } saveTypeLut[25] =
- {
- // EEPROM
- {"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/3.
- {"EEPROM_V124", SAVE_TYPE_EEPROM_64k}, // Confirmed.
- {"EEPROM_V125", SAVE_TYPE_EEPROM_8k}, // Confirmed.
- {"EEPROM_V126", SAVE_TYPE_EEPROM_8k}, // Confirmed.
-
- // FLASH
- // Assume they all have RTC.
- {"FLASH_V120", SAVE_TYPE_FLASH_512k_PSC_RTC},
- {"FLASH_V121", SAVE_TYPE_FLASH_512k_PSC_RTC},
- {"FLASH_V123", SAVE_TYPE_FLASH_512k_PSC_RTC},
- {"FLASH_V124", SAVE_TYPE_FLASH_512k_PSC_RTC},
- {"FLASH_V125", SAVE_TYPE_FLASH_512k_PSC_RTC},
- {"FLASH_V126", SAVE_TYPE_FLASH_512k_PSC_RTC},
- {"FLASH512_V130", SAVE_TYPE_FLASH_512k_PSC_RTC},
- {"FLASH512_V131", SAVE_TYPE_FLASH_512k_PSC_RTC},
- {"FLASH512_V133", SAVE_TYPE_FLASH_512k_PSC_RTC},
- {"FLASH1M_V102", SAVE_TYPE_FLASH_1m_MRX_RTC},
- {"FLASH1M_V103", SAVE_TYPE_FLASH_1m_MRX_RTC},
-
- // FRAM & SRAM
- {"SRAM_F_V100", SAVE_TYPE_SRAM_256k},
- {"SRAM_F_V102", SAVE_TYPE_SRAM_256k},
- {"SRAM_F_V103", SAVE_TYPE_SRAM_256k},
-
- {"SRAM_V110", SAVE_TYPE_SRAM_256k},
- {"SRAM_V111", SAVE_TYPE_SRAM_256k},
- {"SRAM_V112", SAVE_TYPE_SRAM_256k},
- {"SRAM_V113", SAVE_TYPE_SRAM_256k}
- };
-
- for(u32 i = 0; i < 25; i++)
- {
- const char *const str = saveTypeLut[i].str;
- u16 tmpSaveType = saveTypeLut[i].saveType;
-
- if(memcmp(romPtr, str, strlen(str)) == 0)
- {
- if(tmpSaveType == SAVE_TYPE_EEPROM_8k || tmpSaveType == SAVE_TYPE_EEPROM_64k)
- {
- // If ROM bigger than 16 MiB --> SAVE_TYPE_EEPROM_8k_2 or SAVE_TYPE_EEPROM_64k_2.
- if(romSize > 0x1000000) tmpSaveType++;
- }
- saveType = tmpSaveType;
- debug_printf("Detected SDK save type '%s'.\n", str);
- goto saveTypeFound;
- }
- }
- }
- }
-
-saveTypeFound:
-
- return saveType;
-}
-
-static u16 saveDbDebug(const char *const savePath, u32 romSize)
-{
- FILINFO fi;
- const bool saveExists = fStat(savePath, &fi) == RES_OK;
- const u16 autoSaveType = tryDetectSaveType(romSize);
-
- // TODO: Check for homebrew before searching the db.
- u64 sha1[3];
- hash((u32*)ROM_LOC, romSize, (u32*)sha1, HASH_INPUT_BIG | HASH_MODE_1, HASH_OUTPUT_BIG);
-
- Result res;
- GameDbEntry dbEntry;
- s32 dbPos = -1;
- u16 saveType = SAVE_TYPE_NONE;
- if((res = searchGameDb(*sha1, &dbEntry, &dbPos)) == RES_OK) saveType = dbEntry.attr & 0xFu;
- else
- {
- ee_puts("Could not access the game db! Press the power button twice.");
- printErrorWaitInput(res, 0);
- return SAVE_TYPE_NONE;
- }
-
- consoleClear();
- ee_printf("Save file (Press (X) to delete): %s\n"
- "Save type (from db): %u\n"
- "Save type (auto detect): %u\n\n"
- " EEPROM 4k/8k (0, 1)\n"
- " EEPROM 64k (2, 3)\n"
- " Flash 512k RTC (4, 6, 8)\n"
- " Flash 512k (5, 7, 9)\n"
- " Flash 1m RTC (10, 12)\n"
- " Flash 1m (11, 13)\n"
- " SRAM 256k (14)\n"
- " None (15)\n\n\n", (saveExists ? "found" : "not found"), saveType, autoSaveType);
- ee_puts("Please note:\n"
- "- Auto detection is broken for EEPROM save types.\n"
- "- Choose the lowest size save type first and work your way up until the game fully works.\n"
- "- If the game works with a Flash save type try without RTC first.\n"
- "- Delete the save before you try a new save type.\n"
- "- Make sure all your dumps are verified good dumps (no-intro.org)!");
-
- static const u8 saveTypeCursorLut[16] = {0, 0, 1, 1, 2, 3, 2, 3, 2, 3, 4, 5, 4, 5, 6, 7};
- u8 oldCursor = 0;
- u8 cursor = saveTypeCursorLut[saveType];
- while(1)
- {
- ee_printf("\x1b[%u;H ", oldCursor + 4);
- ee_printf("\x1b[%u;H>", cursor + 4);
- oldCursor = cursor;
-
- u32 kDown;
- do
- {
- GFX_waitForVBlank0();
-
- hidScanInput();
- if(hidGetExtraKeys(0) & (KEY_POWER_HELD | KEY_POWER)) goto end;
- kDown = hidKeysDown();
- } while(kDown == 0);
-
- if((kDown & KEY_DUP) && cursor > 0) cursor--;
- else if((kDown & KEY_DDOWN) && cursor < 7) cursor++;
- else if(kDown & KEY_X)
- {
- fUnlink(savePath);
- ee_printf("\x1b[0;33Hdeleted ");
- }
- else if(kDown & KEY_A) break;
- }
-
- static const u8 cursorSaveTypeLut[8] = {0, 2, 8, 9, 10, 11, 14, 15};
- saveType = cursorSaveTypeLut[cursor];
- if(saveType == SAVE_TYPE_EEPROM_8k || saveType == SAVE_TYPE_EEPROM_64k)
- {
- // If ROM bigger than 16 MiB --> SAVE_TYPE_EEPROM_8k_2 or SAVE_TYPE_EEPROM_64k_2.
- if(romSize > 0x1000000) saveType++;
- }
- if(dbEntry.attr != saveType)
- {
- if(dbPos > -1 || dbPos < 3256)
- {
- dbEntry.attr = saveType;
- FHandle f;
- if(fOpen(&f, "gba_db.bin", FA_OPEN_EXISTING | FA_WRITE) == RES_OK)
- {
- fLseek(f, (sizeof(GameDbEntry) * dbPos) + offsetof(GameDbEntry, attr));
- fWrite(f, &dbEntry.attr, sizeof(dbEntry.attr), NULL);
- fClose(f);
- }
- else
- {
- ee_puts("Could not open db for write!");
- saveType = SAVE_TYPE_NONE;
- }
- }
- else
- {
- ee_puts("Db position out of range!");
- saveType = SAVE_TYPE_NONE;
- }
- }
-
-end:
- return saveType;
-}
-
-static void adjustGammaTableForGba(void)
-{
- const float inGamma = g_oafConfig.inGamma;
- const float outGamma = g_oafConfig.outGamma;
- const float contrast = g_oafConfig.contrast;
- const float brightness = g_oafConfig.brightness;
- for(u32 i = 0; i < 256; i++)
- {
- // Credits for this algo go to Extrems.
- // Originally from Game Boy Interface Standard Edition for the GameCube.
- u32 res = powf(powf(contrast, inGamma) * powf((float)i / 255.0f + brightness / contrast, inGamma),
- 1.0f / outGamma) * 255.0f;
- if(res > 255) res = 255;
-
- // Same adjustment for red/green/blue.
- REG_LCD_PDC0_GTBL_FIFO = res<<16 | res<<8 | res;
- }
-}
-
-static Result dumpFrameTex(void)
-{
- // 512x-512 (hight negative to flip vertically).
- // Pixels at offset 0x40.
- alignas(4) static const u8 bmpHeader[54] =
- {
- 0x42, 0x4D, 0x40, 0x00, 0x0C, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x28, 0x00,
- 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFE,
- 0xFF, 0xFF, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x13, 0x0B,
- 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
- };
-
- /*GX_displayTransfer((u32*)0x18200000, 160u<<16 | 256u, (u32*)0x18400000, 160u<<16 | 256u, 1u<<12 | 1u<<8);
- GFX_waitForPPF();
- //fsQuickWrite("sdmc:/lgyfb_dbg_frame.bgr", (void*)0x18400000, 256 * 160 * 3);*/
- GX_displayTransfer((u32*)0x18200000, 240u<<16 | 512u, (u32*)0x18400040, 240u<<16 | 512u, 1u<<12 | 1u<<8);
- GFX_waitForPPF();
-
- memcpy((void*)0x18400000, bmpHeader, sizeof(bmpHeader));
-
- return fsQuickWrite("texture_dump.bmp", (void*)0x18400000, 0x40 + 512 * 512 * 3);
-}
-
-static void gbaGfxHandler(void *args)
-{
- KEvent *const event = (KEvent*)args;
-
- while(1)
- {
- if(waitForEvent(event) != KRES_OK) break;
- clearEvent(event);
-
- // Rotate the frame using the GPU.
- // 240x160: TODO.
- // 360x240: about 0.623620315 ms.
- static bool inited = false;
- u32 listSize;
- const u32 *list;
- if(inited == false)
- {
- inited = true;
-
- listSize = sizeof(gbaGpuInitList);
- list = (u32*)gbaGpuInitList;
- }
- else
- {
- listSize = sizeof(gbaGpuList2);
- list = (u32*)gbaGpuList2;
- }
- GX_processCommandList(listSize, list);
- GFX_waitForP3D();
- GX_displayTransfer((u32*)(0x18180000 + (16 * 240 * 3)), 368u<<16 | 240u,
- GFX_getFramebuffer(SCREEN_TOP) + (16 * 240 * 3), 368u<<16 | 240u, 1u<<12 | 1u<<8);
- GFX_waitForPPF();
- GFX_swapFramebufs();
-
- if(hidKeysDown() & KEY_Y) dumpFrameTex();
- }
-
- taskExit();
-}
-
-#ifndef NDEBUG
-static void debugTests(void)
-{
- const u32 kDown = hidKeysDown();
-
- // Print GBA RTC date/time.
- if(kDown & KEY_X)
- {
- 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);
-
- // Trigger Game Boy Player enhancements.
- // Needs to be done on the Game Boy Player logo screen.
- // 2 frames nothing pressed and 1 frame all D-Pad buttons pressed.
- /*REG_LGY_PAD_SEL = 0x1FFF; // Override all buttons.
- static u8 gbp = 2;
- if(gbp > 0)
- {
- REG_LGY_PAD_VAL = 0x1FFF; // Force all buttons not pressed.
- gbp--;
- }
- else
- {
- REG_LGY_PAD_VAL = 0x1F0F; // All D-Pad buttons pressed.
- gbp = 2;
- }*/
- }
- //else REG_LGY_PAD_SEL = 0; // Stop overriding buttons.
-}
-#endif
-
-static int confIniCallback(void* user, const char* section, const char* name, const char* value)
-{
- OafConfig *const config = (OafConfig*)user;
-
- if(strcmp(section, "general") == 0)
- {
- if(strcmp(name, "backlight") == 0)
- config->backlight = (u8)strtoul(value, NULL, 10);
- else if(strcmp(name, "biosIntro") == 0)
- config->biosIntro = (strcmp(value, "true") == 0 ? true : false);
- }
- else if(strcmp(section, "video") == 0)
- {
- if(strcmp(name, "inGamma") == 0)
- config->inGamma = str2float(value);
- else if(strcmp(name, "outGamma") == 0)
- config->outGamma = str2float(value);
- else if(strcmp(name, "contrast") == 0)
- config->contrast = str2float(value);
- else if(strcmp(name, "brightness") == 0)
- config->brightness = str2float(value);
- }
- /*else if(strcmp(section, "audio") == 0)
- {
- }
- else if(strcmp(section, "input") == 0)
- {
- }*/
- else return 0; // Error.
-
- return 1; // 1 is no error? Really?
-}
-
-static Result parseMainConfig(void)
-{
- char *iniBuf = (char*)calloc(INI_BUF_SIZE, 1);
- if(iniBuf == NULL) return RES_OUT_OF_MEM;
-
- Result res = fsQuickRead("config.ini", iniBuf, INI_BUF_SIZE - 1);
- if(res == RES_OK) ini_parse_string(iniBuf, confIniCallback, &g_oafConfig);
- else
- {
- const char *const defaultConfig = DEFAULT_CONFIG;
- res = fsQuickWrite("config.ini", defaultConfig, strlen(defaultConfig));
- }
-
- // Apply backlight brightness.
- // TODO: Move this elsewhere.
- // FIXME: If any error happens before this point it will be invisible.
- const u8 backlight = g_oafConfig.backlight;
- GFX_setBrightness(backlight, backlight);
-
- free(iniBuf);
-
- return res;
-}
-
-static Result handleFsStuff(char romPath[512])
-{
- // Mount SD card.
- Result res = fMount(FS_DRIVE_SDMC);
- if(res == RES_OK)
- {
- char *lastDir = (char*)calloc(512, 1);
- if(lastDir != NULL)
- {
- do
- {
- // Create the work dir and switch to it.
- if((res = fsMakePath(OAF_WORK_DIR)) != RES_OK && res != RES_FR_EXIST) break;
- if((res = fChdir(OAF_WORK_DIR)) != RES_OK) break;
-
- // Parse config.
- parseMainConfig();
-
- // Get last ROM launch path.
- if((res = fsQuickRead("lastdir.bin", lastDir, 511)) != RES_OK)
- {
- if(res == RES_FR_NO_FILE) strcpy(lastDir, "sdmc:/");
- else break;
- }
-
- // Show file browser.
- *romPath = '\0';
- if((res = browseFiles(lastDir, romPath)) != RES_OK)
- {
- // Second chance in case the last dir has been deleted.
- if(res == RES_FR_NO_PATH)
- {
- strcpy(lastDir, "sdmc:/");
- if((res = browseFiles(lastDir, romPath)) != RES_OK) break;
- }
- else break;
- }
-
- size_t cmpLen = strrchr(romPath, '/') - romPath;
- if((size_t)(strchr(romPath, '/') - romPath) == cmpLen) cmpLen++; // Keep the first '/'.
- if(cmpLen < 512)
- {
- if(cmpLen < strlen(lastDir) || strncmp(lastDir, romPath, cmpLen) != 0)
- {
- strncpy(lastDir, romPath, cmpLen);
- lastDir[cmpLen] = '\0';
- res = fsQuickWrite("lastdir.bin", lastDir, cmpLen + 1);
- }
- }
- } while(0);
-
- free(lastDir);
- }
- else res = RES_OUT_OF_MEM;
- }
-
- return res;
-}
int main(void)
{
+ Result res = fMount(FS_DRIVE_SDMC);
GFX_init(GFX_BGR8, GFX_RGB565);
consoleInit(SCREEN_BOT, NULL);
//CODEC_init();
- char *romPath = (char*)malloc(512);
- Result res = handleFsStuff(romPath);
- if(res != RES_OK || *romPath == '\0') goto end;
-
- ee_puts("Reading ROM and save...");
- u32 romSize;
- res = loadGbaRom(romPath, &romSize);
- if(res != RES_OK) goto end;
-
- // Detect save type and adjust path for the save file.
- const u16 saveType = tryDetectSaveType(romSize);
- const u32 romPathLen = strlen(romPath);
- strcpy(romPath + romPathLen - 4, ".sav");
- /*const u32 romPathLen = strlen(romPath);
- strcpy(romPath + romPathLen - 4, ".sav");
- const u16 saveType = saveDbDebug(romPath, romSize);*/
-
- // Prepare ARM9 for GBA mode + settings and save loading.
- if((res = LGY_prepareGbaMode(g_oafConfig.biosIntro, saveType, romPath)) == RES_OK)
+ if(res == RES_OK && (res = oafInitAndRun()) == RES_OK)
{
-#ifdef NDEBUG
- GFX_setForceBlack(false, true);
- // Don't turn the backlight off on 2DS.
- if(MCU_getSystemModel() != 3) GFX_powerOffBacklights(GFX_BLIGHT_BOT);
-#endif
-
- KEvent *const frameReadyEvent = createEvent(false);
- LGYFB_init(frameReadyEvent); // Setup Legacy Framebuffer.
- /*KTask *const gfxTask =*/ createTask(0x800, 3, gbaGfxHandler, frameReadyEvent);
-
- // Adjust gamma table and sync LgyFb start with LCD VBlank.
- adjustGammaTableForGba();
- GFX_waitForVBlank0();
- LGY_switchMode();
-
do
{
hidScanInput();
if(hidGetExtraKeys(0) & (KEY_POWER_HELD | KEY_POWER)) break;
- LGY_handleOverrides();
-
- waitForEvent(frameReadyEvent);
+ oafUpdate();
} while(1);
- LGYFB_deinit();
- deleteEvent(frameReadyEvent); // gbaGfxHandler() will automatically terminate.
+ oafFinish();
}
+ else printErrorWaitInput(res, 0);
-end:
- free(romPath);
- if(res != RES_OK) printErrorWaitInput(res, 0);
-
- LGY_deinit();
- fUnmount(FS_DRIVE_SDMC);
CODEC_deinit();
GFX_deinit();
+ fUnmount(FS_DRIVE_SDMC);
+
power_off();
return 0;
diff --git a/source/arm11/open_agb_firm.c b/source/arm11/open_agb_firm.c
new file mode 100644
index 0000000..d3f41e7
--- /dev/null
+++ b/source/arm11/open_agb_firm.c
@@ -0,0 +1,709 @@
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include "types.h"
+#include "arm_intrinsic.h"
+#include "util.h"
+#include "arm11/hardware/hash.h"
+#include "arm11/hardware/hid.h"
+#include "hardware/lgy.h"
+#include "arm11/hardware/lgyfb.h"
+#include "arm11/console.h"
+#include "arm11/fmt.h"
+#include "hardware/gfx.h"
+#include "fs.h"
+#include "fsutil.h"
+#include "inih/ini.h"
+#include "arm11/filebrowser.h"
+#include "arm11/hardware/lcd.h"
+#include "arm11/gpu_cmd_lists.h"
+#include "arm11/hardware/mcu.h"
+#include "kernel.h"
+#include "kevent.h"
+
+
+//#define OAF_SAVE_DB_DEBUG (1)
+#define OAF_WORK_DIR "sdmc:/3ds/open_agb_firm"
+#define INI_BUF_SIZE (1024u)
+#define DEFAULT_CONFIG "[general]\n" \
+ "backlight=40\n" \
+ "biosIntro=true\n\n" \
+ "[video]\n" \
+ "inGamma=2.2\n" \
+ "outGamma=1.54\n" \
+ "contrast=1.0\n" \
+ "brightness=0.0\n"
+
+
+typedef struct
+{
+ // [general]
+ u8 backlight; // Both LCDs.
+ bool biosIntro;
+
+ // [video]
+ float inGamma;
+ float outGamma;
+ float contrast;
+ float brightness;
+} OafConfig;
+
+typedef struct
+{
+ // [game]
+ u16 saveType;
+ u8 saveSlot;
+} OafGameConfig;
+
+typedef struct
+{
+ char name[200];
+ char gameCode[4];
+ u8 sha1[20];
+ u32 attr;
+} GameDbEntry;
+
+
+// Default config.
+static OafConfig g_oafConfig =
+{
+ 40,
+ true,
+ 2.2f,
+ 1.54f,
+ 1.f,
+ 0.f
+};
+static KEvent *g_frameReadyEvent = NULL;
+
+
+
+static u32 fixRomPadding(u32 romFileSize)
+{
+ // Pad unused ROM area with 0xFFs (trimmed ROMs).
+ // Smallest retail ROM chip is 8 Mbit (1 MiB).
+ u32 romSize = nextPow2(romFileSize);
+ if(romSize < 0x100000u) romSize = 0x100000u;
+ memset((void*)(ROM_LOC + romFileSize), 0xFFFFFFFFu, romSize - romFileSize);
+
+ if(romSize > 0x100000u)
+ {
+ // Fake "open bus" padding.
+ u32 padding = (ROM_LOC + romSize) / 2;
+ padding = __pkhbt(padding, padding + 1, 16); // Copy lower half + 1 to upper half.
+ for(uintptr_t i = ROM_LOC + romSize; i < ROM_LOC + MAX_ROM_SIZE; i += 4)
+ {
+ *(u32*)i = padding;
+ padding = __uadd16(padding, 0x00020002u); // Unsigned parallel halfword-wise addition.
+ }
+ }
+ else
+ {
+ // ROM mirroring (Classic NES Series/possibly others with 8 Mbit ROM).
+ // Mirror ROM across the entire 32 MiB area.
+ for(uintptr_t i = ROM_LOC + romSize; i < ROM_LOC + MAX_ROM_SIZE; i += romSize)
+ {
+ //memcpy((void*)i, (void*)(i - romSize), romSize); // 0x23A15DD
+ memcpy((void*)i, (void*)ROM_LOC, romSize); // 0x237109B
+ }
+ }
+
+ return romSize;
+}
+
+static Result loadGbaRom(const char *const path, u32 *const romSizeOut)
+{
+ Result res;
+ FHandle f;
+ if((res = fOpen(&f, path, FA_OPEN_EXISTING | FA_READ)) == RES_OK)
+ {
+ u32 fileSize;
+ if((fileSize = fSize(f)) <= MAX_ROM_SIZE)
+ {
+ u8 *ptr = (u8*)ROM_LOC;
+ u32 read;
+ while((res = fRead(f, ptr, 0x100000u, &read)) == RES_OK && read == 0x100000u)
+ ptr += 0x100000u;
+
+ *romSizeOut = fixRomPadding(fileSize);
+ }
+ else res = RES_ROM_TOO_BIG;
+
+ fClose(f);
+ }
+
+ return res;
+}
+
+// Search for entry with first u64 of the SHA1 = x using binary search.
+static Result searchGameDb(u64 x, GameDbEntry *const db, s32 *const entryPos)
+{
+ debug_printf("Database search: '%016" PRIX64 "'\n", __builtin_bswap64(x));
+
+ Result res;
+ FHandle f;
+ if((res = fOpen(&f, "gba_db.bin", FA_OPEN_EXISTING | FA_READ)) == RES_OK)
+ {
+ s32 l = 0;
+ s32 r = fSize(f) / sizeof(GameDbEntry) - 1; // TODO: Check for 0!
+ while(1)
+ {
+ const s32 mid = l + (r - l) / 2;
+ debug_printf("l: %ld r: %ld mid: %ld\n", l, r, mid);
+
+ if((res = fLseek(f, sizeof(GameDbEntry) * mid)) != RES_OK) break;
+ if((res = fRead(f, db, sizeof(GameDbEntry), NULL)) != RES_OK) break;
+ const u64 tmp = *(u64*)db->sha1; // Unaligned access.
+ if(tmp == x)
+ {
+ *entryPos = mid; // TODO: Remove.
+ break;
+ }
+
+ if(r <= l || r < 0)
+ {
+ res = RES_NOT_FOUND;
+ break;
+ }
+
+ if(tmp > x) r = mid - 1;
+ else l = mid + 1;
+ }
+
+ fClose(f);
+ }
+
+ return res;
+}
+
+static u16 checkSaveOverride(u32 gameCode)
+{
+ if((gameCode & 0xFFu) == 'F') // Classic NES Series.
+ {
+ return SAVE_TYPE_EEPROM_8k;
+ }
+
+ static const struct
+ {
+ alignas(4) char gameCode[4];
+ u16 saveType;
+ } overrideLut[] =
+ {
+ {"\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.
+ {"AZL\0", SAVE_TYPE_EEPROM_64k}, // Legend of Zelda, The - A Link to the Past & Four Swords.
+ };
+
+ for(u32 i = 0; i < sizeof(overrideLut) / sizeof(*overrideLut); i++)
+ {
+ // Compare Game Code without region.
+ if((gameCode & 0xFFFFFFu) == *((u32*)overrideLut[i].gameCode))
+ {
+ return overrideLut[i].saveType;
+ }
+ }
+
+ return 0xFF;
+}
+
+static u16 tryDetectSaveType(u32 romSize)
+{
+ const u32 *romPtr = (u32*)ROM_LOC;
+ u16 saveType;
+ if((saveType = checkSaveOverride(romPtr[0xAC / 4])) != 0xFF)
+ {
+ debug_printf("Game Code in override list. Using save type %" PRIu16 ".\n", saveType);
+ return saveType;
+ }
+
+ // Code based on: https://github.com/Gericom/GBARunner2/blob/master/arm9/source/save/Save.vram.cpp
+ romPtr += 0xE4 / 4; // Skip headers.
+ saveType = SAVE_TYPE_NONE;
+ for(; romPtr < (u32*)(ROM_LOC + romSize); romPtr++)
+ {
+ u32 tmp = *romPtr;
+
+ // "EEPR" "FLAS" "SRAM"
+ if(tmp == 0x52504545u || tmp == 0x53414C46u || tmp == 0x4D415253u)
+ {
+ static const struct
+ {
+ const char *str;
+ u16 saveType;
+ } saveTypeLut[25] =
+ {
+ // EEPROM
+ {"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/3.
+ {"EEPROM_V124", SAVE_TYPE_EEPROM_64k}, // Confirmed.
+ {"EEPROM_V125", SAVE_TYPE_EEPROM_8k}, // Confirmed.
+ {"EEPROM_V126", SAVE_TYPE_EEPROM_8k}, // Confirmed.
+
+ // FLASH
+ // Assume they all have RTC.
+ {"FLASH_V120", SAVE_TYPE_FLASH_512k_PSC_RTC},
+ {"FLASH_V121", SAVE_TYPE_FLASH_512k_PSC_RTC},
+ {"FLASH_V123", SAVE_TYPE_FLASH_512k_PSC_RTC},
+ {"FLASH_V124", SAVE_TYPE_FLASH_512k_PSC_RTC},
+ {"FLASH_V125", SAVE_TYPE_FLASH_512k_PSC_RTC},
+ {"FLASH_V126", SAVE_TYPE_FLASH_512k_PSC_RTC},
+ {"FLASH512_V130", SAVE_TYPE_FLASH_512k_PSC_RTC},
+ {"FLASH512_V131", SAVE_TYPE_FLASH_512k_PSC_RTC},
+ {"FLASH512_V133", SAVE_TYPE_FLASH_512k_PSC_RTC},
+ {"FLASH1M_V102", SAVE_TYPE_FLASH_1m_MRX_RTC},
+ {"FLASH1M_V103", SAVE_TYPE_FLASH_1m_MRX_RTC},
+
+ // FRAM & SRAM
+ {"SRAM_F_V100", SAVE_TYPE_SRAM_256k},
+ {"SRAM_F_V102", SAVE_TYPE_SRAM_256k},
+ {"SRAM_F_V103", SAVE_TYPE_SRAM_256k},
+
+ {"SRAM_V110", SAVE_TYPE_SRAM_256k},
+ {"SRAM_V111", SAVE_TYPE_SRAM_256k},
+ {"SRAM_V112", SAVE_TYPE_SRAM_256k},
+ {"SRAM_V113", SAVE_TYPE_SRAM_256k}
+ };
+
+ for(u32 i = 0; i < 25; i++)
+ {
+ const char *const str = saveTypeLut[i].str;
+ u16 tmpSaveType = saveTypeLut[i].saveType;
+
+ if(memcmp(romPtr, str, strlen(str)) == 0)
+ {
+ if(tmpSaveType == SAVE_TYPE_EEPROM_8k || tmpSaveType == SAVE_TYPE_EEPROM_64k)
+ {
+ // If ROM bigger than 16 MiB --> SAVE_TYPE_EEPROM_8k_2 or SAVE_TYPE_EEPROM_64k_2.
+ if(romSize > 0x1000000) tmpSaveType++;
+ }
+ saveType = tmpSaveType;
+ debug_printf("Detected SDK save type '%s'.\n", str);
+ goto saveTypeFound;
+ }
+ }
+ }
+ }
+
+saveTypeFound:
+
+ return saveType;
+}
+
+static u16 saveDbDebug(const char *const savePath, u32 romSize)
+{
+ FILINFO fi;
+ const bool saveExists = fStat(savePath, &fi) == RES_OK;
+ const u16 autoSaveType = tryDetectSaveType(romSize);
+
+ // TODO: Check for homebrew before searching the db.
+ u64 sha1[3];
+ hash((u32*)ROM_LOC, romSize, (u32*)sha1, HASH_INPUT_BIG | HASH_MODE_1, HASH_OUTPUT_BIG);
+
+ Result res;
+ GameDbEntry dbEntry;
+ s32 dbPos = -1;
+ u16 saveType = SAVE_TYPE_NONE;
+ if((res = searchGameDb(*sha1, &dbEntry, &dbPos)) == RES_OK) saveType = dbEntry.attr & 0xFu;
+ else
+ {
+ ee_puts("Could not access the game db! Press the power button twice.");
+ printErrorWaitInput(res, 0);
+ return SAVE_TYPE_NONE;
+ }
+
+ consoleClear();
+ ee_printf("Save file (Press (X) to delete): %s\n"
+ "Save type (from db): %u\n"
+ "Save type (auto detect): %u\n\n"
+ " EEPROM 4k/8k (0, 1)\n"
+ " EEPROM 64k (2, 3)\n"
+ " Flash 512k RTC (4, 6, 8)\n"
+ " Flash 512k (5, 7, 9)\n"
+ " Flash 1m RTC (10, 12)\n"
+ " Flash 1m (11, 13)\n"
+ " SRAM 256k (14)\n"
+ " None (15)\n\n\n", (saveExists ? "found" : "not found"), saveType, autoSaveType);
+ ee_puts("Please note:\n"
+ "- Auto detection is broken for EEPROM save types.\n"
+ "- Choose the lowest size save type first and work your way up until the game fully works.\n"
+ "- If the game works with a Flash save type try without RTC first.\n"
+ "- Delete the save before you try a new save type.\n"
+ "- Make sure all your dumps are verified good dumps (no-intro.org)!");
+
+ static const u8 saveTypeCursorLut[16] = {0, 0, 1, 1, 2, 3, 2, 3, 2, 3, 4, 5, 4, 5, 6, 7};
+ u8 oldCursor = 0;
+ u8 cursor = saveTypeCursorLut[saveType];
+ while(1)
+ {
+ ee_printf("\x1b[%u;H ", oldCursor + 4);
+ ee_printf("\x1b[%u;H>", cursor + 4);
+ oldCursor = cursor;
+
+ u32 kDown;
+ do
+ {
+ GFX_waitForVBlank0();
+
+ hidScanInput();
+ if(hidGetExtraKeys(0) & (KEY_POWER_HELD | KEY_POWER)) goto end;
+ kDown = hidKeysDown();
+ } while(kDown == 0);
+
+ if((kDown & KEY_DUP) && cursor > 0) cursor--;
+ else if((kDown & KEY_DDOWN) && cursor < 7) cursor++;
+ else if(kDown & KEY_X)
+ {
+ fUnlink(savePath);
+ ee_printf("\x1b[0;33Hdeleted ");
+ }
+ else if(kDown & KEY_A) break;
+ }
+
+ static const u8 cursorSaveTypeLut[8] = {0, 2, 8, 9, 10, 11, 14, 15};
+ saveType = cursorSaveTypeLut[cursor];
+ if(saveType == SAVE_TYPE_EEPROM_8k || saveType == SAVE_TYPE_EEPROM_64k)
+ {
+ // If ROM bigger than 16 MiB --> SAVE_TYPE_EEPROM_8k_2 or SAVE_TYPE_EEPROM_64k_2.
+ if(romSize > 0x1000000) saveType++;
+ }
+ if(dbEntry.attr != saveType)
+ {
+ if(dbPos > -1 || dbPos < 3253)
+ {
+ dbEntry.attr = (intLog2(romSize)<<27) | saveType;
+ FHandle f;
+ if(fOpen(&f, "gba_db.bin", FA_OPEN_EXISTING | FA_WRITE) == RES_OK)
+ {
+ fLseek(f, (sizeof(GameDbEntry) * dbPos) + offsetof(GameDbEntry, attr));
+ fWrite(f, &dbEntry.attr, sizeof(dbEntry.attr), NULL);
+ fClose(f);
+ }
+ else
+ {
+ ee_puts("Could not open db for write!");
+ saveType = SAVE_TYPE_NONE;
+ }
+ }
+ else
+ {
+ ee_puts("Db position out of range!");
+ saveType = SAVE_TYPE_NONE;
+ }
+ }
+
+end:
+ return saveType;
+}
+
+static void adjustGammaTableForGba(void)
+{
+ const float inGamma = g_oafConfig.inGamma;
+ const float outGamma = g_oafConfig.outGamma;
+ const float contrast = g_oafConfig.contrast;
+ const float brightness = g_oafConfig.brightness;
+ for(u32 i = 0; i < 256; i++)
+ {
+ // Credits for this algo go to Extrems.
+ // Originally from Game Boy Interface Standard Edition for the GameCube.
+ u32 res = powf(powf(contrast, inGamma) * powf((float)i / 255.0f + brightness / contrast, inGamma),
+ 1.0f / outGamma) * 255.0f;
+ if(res > 255) res = 255;
+
+ // Same adjustment for red/green/blue.
+ REG_LCD_PDC0_GTBL_FIFO = res<<16 | res<<8 | res;
+ }
+}
+
+static Result dumpFrameTex(void)
+{
+ // 512x-512 (hight negative to flip vertically).
+ // Pixels at offset 0x40.
+ alignas(4) static const u8 bmpHeader[54] =
+ {
+ 0x42, 0x4D, 0x40, 0x00, 0x0C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x28, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFE,
+ 0xFF, 0xFF, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x13, 0x0B,
+ 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ /*GX_displayTransfer((u32*)0x18200000, 160u<<16 | 256u, (u32*)0x18400000, 160u<<16 | 256u, 1u<<12 | 1u<<8);
+ GFX_waitForPPF();
+ //fsQuickWrite("sdmc:/lgyfb_dbg_frame.bgr", (void*)0x18400000, 256 * 160 * 3);*/
+ GX_displayTransfer((u32*)0x18200000, 240u<<16 | 512u, (u32*)0x18400040, 240u<<16 | 512u, 1u<<12 | 1u<<8);
+ GFX_waitForPPF();
+
+ memcpy((void*)0x18400000, bmpHeader, sizeof(bmpHeader));
+
+ return fsQuickWrite("texture_dump.bmp", (void*)0x18400000, 0x40 + 512 * 512 * 3);
+}
+
+static void gbaGfxHandler(void *args)
+{
+ KEvent *const event = (KEvent*)args;
+
+ while(1)
+ {
+ if(waitForEvent(event) != KRES_OK) break;
+ clearEvent(event);
+
+ // Rotate the frame using the GPU.
+ // 240x160: TODO.
+ // 360x240: about 0.623620315 ms.
+ static bool inited = false;
+ u32 listSize;
+ const u32 *list;
+ if(inited == false)
+ {
+ inited = true;
+
+ listSize = sizeof(gbaGpuInitList);
+ list = (u32*)gbaGpuInitList;
+ }
+ else
+ {
+ listSize = sizeof(gbaGpuList2);
+ list = (u32*)gbaGpuList2;
+ }
+ GX_processCommandList(listSize, list);
+ GFX_waitForP3D();
+ GX_displayTransfer((u32*)(0x18180000 + (16 * 240 * 3)), 368u<<16 | 240u,
+ GFX_getFramebuffer(SCREEN_TOP) + (16 * 240 * 3), 368u<<16 | 240u, 1u<<12 | 1u<<8);
+ GFX_waitForPPF();
+ GFX_swapFramebufs();
+
+ if(hidKeysDown() & KEY_Y) dumpFrameTex();
+ }
+
+ taskExit();
+}
+
+static int confIniHandler(void* user, const char* section, const char* name, const char* value)
+{
+ OafConfig *const config = (OafConfig*)user;
+
+ if(strcmp(section, "general") == 0)
+ {
+ if(strcmp(name, "backlight") == 0)
+ config->backlight = (u8)strtoul(value, NULL, 10);
+ else if(strcmp(name, "biosIntro") == 0)
+ config->biosIntro = (strcmp(value, "true") == 0 ? true : false);
+ }
+ else if(strcmp(section, "video") == 0)
+ {
+ if(strcmp(name, "inGamma") == 0)
+ config->inGamma = str2float(value);
+ else if(strcmp(name, "outGamma") == 0)
+ config->outGamma = str2float(value);
+ else if(strcmp(name, "contrast") == 0)
+ config->contrast = str2float(value);
+ else if(strcmp(name, "brightness") == 0)
+ config->brightness = str2float(value);
+ }
+ /*else if(strcmp(section, "audio") == 0)
+ {
+ }
+ else if(strcmp(section, "input") == 0)
+ {
+ }*/
+ else return 0; // Error.
+
+ return 1; // 1 is no error? Really?
+}
+
+static int gameConfIniHandler(void* user, const char* section, const char* name, const char* value)
+{
+ //OafGameConfig *const config = (OafGameConfig*)user;
+
+ /*if(strcmp(section, "game") == 0)
+ {
+ // Save type.
+ // Save slot.
+ }
+ else if(strcmp(section, "video") == 0)
+ {
+ if(strcmp(name, "inGamma") == 0)
+ config->inGamma = str2float(value);
+ else if(strcmp(name, "outGamma") == 0)
+ config->outGamma = str2float(value);
+ else if(strcmp(name, "contrast") == 0)
+ config->contrast = str2float(value);
+ else if(strcmp(name, "brightness") == 0)
+ config->brightness = str2float(value);
+ }
+ else if(strcmp(section, "audio") == 0)
+ {
+ }
+ else if(strcmp(section, "input") == 0)
+ {
+ }
+ else return 0;*/ // Error.
+
+ return 1; // 1 is no error? Really?
+}
+
+static Result parseConfig(const char *const path, u8 confType, void *config)
+{
+ char *iniBuf = (char*)calloc(INI_BUF_SIZE, 1);
+ if(iniBuf == NULL) return RES_OUT_OF_MEM;
+
+ Result res = fsQuickRead(path, iniBuf, INI_BUF_SIZE - 1);
+ if(res == RES_OK) ini_parse_string(iniBuf, (confType == 0 ? confIniHandler : gameConfIniHandler), config);
+ else
+ {
+ const char *const defaultConfig = DEFAULT_CONFIG;
+ res = fsQuickWrite(path, defaultConfig, strlen(defaultConfig));
+ }
+
+ free(iniBuf);
+
+ return res;
+}
+
+static Result handleFsStuff(char romAndSavePath[512])
+{
+ Result res;
+ char *lastDir = (char*)calloc(512, 1);
+ if(lastDir != NULL)
+ {
+ do
+ {
+ // Create the work dir and switch to it.
+ if((res = fsMakePath(OAF_WORK_DIR)) != RES_OK && res != RES_FR_EXIST) break;
+ if((res = fChdir(OAF_WORK_DIR)) != RES_OK) break;
+
+ // Parse config.
+ parseConfig("config.ini", 0, &g_oafConfig);
+ { // TODO: Move this elsewhere?
+ const u8 backlight = g_oafConfig.backlight;
+ GFX_setBrightness(backlight, backlight);
+ }
+
+ // Get last ROM launch path.
+ if((res = fsQuickRead("lastdir.bin", lastDir, 511)) != RES_OK)
+ {
+ if(res == RES_FR_NO_FILE) strcpy(lastDir, "sdmc:/");
+ else break;
+ }
+
+ // Show file browser.
+ *romAndSavePath = '\0';
+ if((res = browseFiles(lastDir, romAndSavePath)) == RES_FR_NO_PATH)
+ {
+ // Second chance in case the last dir has been deleted.
+ strcpy(lastDir, "sdmc:/");
+ if((res = browseFiles(lastDir, romAndSavePath)) != RES_OK) break;
+ }
+ else if(res != RES_OK) break;
+
+ size_t cmpLen = strrchr(romAndSavePath, '/') - romAndSavePath;
+ if((size_t)(strchr(romAndSavePath, '/') - romAndSavePath) == cmpLen) cmpLen++; // Keep the first '/'.
+ if(cmpLen < 512)
+ {
+ if(cmpLen < strlen(lastDir) || strncmp(lastDir, romAndSavePath, cmpLen) != 0)
+ {
+ strncpy(lastDir, romAndSavePath, cmpLen);
+ lastDir[cmpLen] = '\0';
+ res = fsQuickWrite("lastdir.bin", lastDir, cmpLen + 1);
+ }
+ }
+ } while(0);
+
+ free(lastDir);
+ }
+ else res = RES_OUT_OF_MEM;
+
+ return res;
+}
+
+Result oafInitAndRun(void)
+{
+ Result res;
+ char *const romAndSavePath = (char*)malloc(512);
+ if(romAndSavePath != NULL)
+ {
+ do
+ {
+ if((res = handleFsStuff(romAndSavePath)) != RES_OK || *romAndSavePath == '\0') break;
+
+ ee_puts("Loading...");
+ u32 romSize;
+ if((res = loadGbaRom(romAndSavePath, &romSize)) != RES_OK) break;
+
+#ifndef OAF_SAVE_DB_DEBUG
+ // Detect save type and adjust path for the save file.
+ const u16 saveType = tryDetectSaveType(romSize);
+ strcpy(romAndSavePath + strlen(romAndSavePath) - 4, ".sav");
+#else
+ strcpy(romAndSavePath + strlen(romAndSavePath) - 4, ".sav");
+ const u16 saveType = saveDbDebug(romAndSavePath, romSize);
+#endif
+
+ // Prepare ARM9 for GBA mode + settings and save loading.
+ if((res = LGY_prepareGbaMode(g_oafConfig.biosIntro, saveType, romAndSavePath)) == RES_OK)
+ {
+#ifdef NDEBUG
+ GFX_setForceBlack(false, true);
+ // Don't turn the backlight off on 2DS.
+ if(MCU_getSystemModel() != 3) GFX_powerOffBacklights(GFX_BLIGHT_BOT);
+#endif
+
+ KEvent *const frameReadyEvent = createEvent(false);
+ LGYFB_init(frameReadyEvent); // Setup Legacy Framebuffer.
+ createTask(0x800, 3, gbaGfxHandler, frameReadyEvent);
+ g_frameReadyEvent = frameReadyEvent;
+
+ // Adjust gamma table and sync LgyFb start with LCD VBlank.
+ adjustGammaTableForGba();
+ GFX_waitForVBlank0();
+ LGY_switchMode();
+ }
+ } while(0);
+ }
+ else res = RES_OUT_OF_MEM;
+
+ free(romAndSavePath);
+
+ return res;
+}
+
+void oafUpdate(void)
+{
+ LGY_handleOverrides();
+ waitForEvent(g_frameReadyEvent);
+}
+
+void oafFinish(void)
+{
+ LGYFB_deinit();
+ if(g_frameReadyEvent != NULL)
+ {
+ deleteEvent(g_frameReadyEvent); // gbaGfxHandler() will automatically terminate.
+ g_frameReadyEvent = NULL;
+ }
+ LGY_deinit();
+}