oaf-boost/source/arm11/open_agb_firm.c

1208 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* This file is part of open_agb_firm
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "arm_intrinsic.h"
#include "util.h"
#include "drivers/cache.h"
#include "drivers/sha.h"
#include "arm11/drivers/hid.h"
// @MERGE 231006 START
#include "drivers/lgy_common.h"
#include "drivers/lgy11.h"
// @MERGE 231006 END
#include "arm11/drivers/lgyfb.h"
#include "arm11/console.h"
#include "arm11/fmt.h"
#include "drivers/gfx.h"
#include "fs.h"
#include "fsutil.h"
#include "inih/ini.h"
#include "arm11/filebrowser.h"
#include "arm11/drivers/lcd.h"
#include "arm11/gpu_cmd_lists.h"
#include "arm11/drivers/mcu.h"
#include "arm11/acf.h"
#include "arm11/atp.h"
#include "arm11/cheat.h"
#include "arm11/keyremix.h"
#include "arm11/pages.h"
#include "arm11/open_agb_firm.h"
#include "kernel.h"
#include "kevent.h"
#include "arm11/drivers/codec.h"
#include "arm11/drivers/timer.h"
// @MERGE 231006 START
#include "oaf_error_codes.h"
// @MERGE 231006 END
#define OAF_WORK_DIR "sdmc:/3ds/open_agb_firm"
#define OAF_SAVE_DIR "saves" // Relative to work dir.
#define INI_BUF_SIZE (1024u)
#define DEFAULT_CONFIG "[general]\n" \
"backlight=100\n" \
"directBoot=false\n" \
"useGbaDb=true\n\n" \
"[video]\n" \
"scaler=1\n" \
"gbaGamma=2.2\n" \
"lcdGamma=1.54\n" \
"contrast=1.0\n" \
"brightness=0.0\n\n" \
"[advanced]\n" \
"saveOverride=false\n" \
"defaultSave=14"
#define CHEAT_INUSE_ADDR (0x3007FE0u) // see GBATek - Default memory usage at 03007FXX
typedef struct global_oaf_config OafConfig;
typedef struct
{
char name[200];
char serial[4];
u8 sha1[20];
u32 attr;
} GameDbEntry;
// Default config.
static OafConfig g_oafConfig =
{
// [general]
100, // backlight
false, // directBoot
true, // useGbaDb
// [video]
2, // scaler
2.2f, // gbaGamma
1.54f, // lcdGamma
1.f, // contrast
0.f, // brightness
// [game]
0, // saveSlot
// [advanced]
false, // saveOverride
14 // defaultSave
// [boost]
, SAVE_POLICY_GBADB // savePolicy
, CHEAT_MODE_DISABLED// cheatMode
, HALT_MODE_POWEROFF// haltMode
};
static KHandle g_frameReadyEvent = 0;
static u16 detect_cheatKey = 0;
static bool use_border = false;
#ifndef NDEBUG
u8 dump_patched_rom = 0;
Result dump_rom( u32 size )
{
FHandle file;
Result res;
if( (res=fOpen(&file, "dump.gba", FA_OPEN_ALWAYS|FA_WRITE)) != RES_OK )
return res;
u32 len;
ee_printf("dumping into dump.gba, file size %ld\n", size);
for( u8 *p=(u8*)LGY_ROM_LOC; size > 0; size-=len, p+=len )
{
res = fWrite( file, p, size < 256 ? size : 256, &len );
if( res != RES_OK )
break;
if( ((u32)p-LGY_ROM_LOC) % (1024*4) == 0)
{
res = fSync( file );
if( res != RES_OK)
break;
if( ((u32)p-LGY_ROM_LOC) % (1024*64) == 0)
ee_printf("\x1b[3;1Hremaining %ld bytes to dump.\n", size);
}
}
fClose( file );
return size > 0 ? res : RES_OK;
}
#endif
// --------------------------
// code for oaf config page
// --------------------------
int oafCheatMode()
{
return g_oafConfig.cheatMode;
}
int oafHaltMode()
{
return g_oafConfig.haltMode;
}
atp_error_t oaf_config_page()
{
return use_config_page(&g_oafConfig);
}
// cache the savetype
static u16 from_savetype_cache( char file[512] )
{
FILINFO fi;
int len = strlen( file );
file[len-1] = 'c';
file[len-2] = 't';
file[len-3] = 's';
u16 retval;
if( fStat(file, &fi) == RES_OK )
{
u8 dat[8];
if( RES_OK == fsQuickRead(file, dat, 8 ) )
{
retval = (u16)dat[0];
}
else retval = 0xffff;
}
else retval = 0xffff;
file[len-1] = 'a';
file[len-2] = 'b';
file[len-3] = 'g';
return retval;
}
static void savetype_cache_store( char file[512], u16 savetype )
{
int len = strlen( file );
file[len-1] = 'c';
file[len-2] = 't';
file[len-3] = 's';
u8 dat[8];
dat[0] = (u8)savetype;
fsQuickWrite(file, dat, sizeof(dat));
file[len-1] = 'a';
file[len-2] = 'b';
file[len-3] = 'g';
}
static atp_error_t custom_savetype( atp_callerdata_t, atp_counter_t index, atp_itemcfg_t *cfg )
{
static const char* options[] = {
"EEPROM 8k (0, 1)",
"EEPROM 64k (2, 3)",
"Flash 512k RTC (4, 6, 8)",
"Flash 512k (5, 7, 9)",
"Flash 1m RTC (10, 12)",
"Flash 1m (11, 13)",
"SRAM 256k (14)",
"None (15)"
};
static const u8 cursorSaveTypeLut[8] = {0, 2, 8, 9, 10, 11, 14, 15};
cfg->text = options[index];
cfg->value = cursorSaveTypeLut[index];
return ATP_SUCCESS;
}
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 < 0x100000) romSize = 0x100000;
const uintptr_t romLoc = LGY_ROM_LOC;
memset((void*)(romLoc + romFileSize), 0xFFFFFFFF, romSize - romFileSize);
u32 mirroredSize = romSize;
if(romSize == 0x100000) // 1 MiB.
{
// ROM mirroring for Classic NES Series/others with 8 Mbit ROM.
// The ROM is mirrored exactly 4 times.
// Thanks to endrift for discovering this.
mirroredSize = 0x400000; // 4 MiB.
uintptr_t mirrorLoc = romLoc + romSize;
do
{
memcpy((void*)mirrorLoc, (void*)romLoc, romSize);
mirrorLoc += romSize;
} while(mirrorLoc < romLoc + mirroredSize);
}
// Fake "open bus" padding.
u32 padding = (romLoc + mirroredSize) / 2;
padding = __pkhbt(padding, padding + 1, 16); // Copy lower half + 1 to upper half.
for(uintptr_t i = romLoc + mirroredSize; i < romLoc + LGY_MAX_ROM_SIZE; i += 4)
{
*(u32*)i = padding;
padding = __uadd16(padding, 0x20002); // Unsigned parallel halfword-wise addition.
}
// We don't return the mirrored size because the db hashes are over unmirrored dumps.
return romSize;
}
static u32 read_hookpoint_file( void *path, u32 hookpoint[MAX_HOOKPOINT] )
{
FILINFO fi;
char *file = (char*)path;
int len = strlen( file );
file[len-1] = 't';
file[len-2] = 'p';
file[len-3] = 'h';
u32 retval = 0;
if( fStat(file, &fi) == RES_OK )
{
if( RES_OK == fsQuickRead(file, hookpoint, sizeof(u32) ) )
{
retval = 1;
}
else retval = 0;
}
else retval = 0;
file[len-1] = 'a';
file[len-2] = 'b';
file[len-3] = 'g';
return retval;
}
static Result loadGbaRom(char * path, u32 *const romSizeOut)
{
Result res;
FHandle f;
if((res = fOpen(&f, path, FA_OPEN_EXISTING | FA_READ)) == RES_OK)
{
// @MERGE 231006 START
u32 fileSize = fSize(f);
if(fileSize > LGY_MAX_ROM_SIZE)
{
fileSize = LGY_MAX_ROM_SIZE;
ee_puts("Warning: ROM file is too big. Expect crashes.");
}
{
u8 *ptr = (u8*)LGY_ROM_LOC;
u32 read;
// 貌似fatfs更新后fread支持一次调用读取较大size的文件了
// 然而经过测试fRead并不能解决acl.c的问题。所以目前继续用fReadSize
res = fRead(f, (void*)ptr, fileSize, &read);
if( res != RES_OK ) return res;
fClose(f);
// @MERGE 231006 END
// use the gbaatm cheat
if( g_oafConfig.cheatMode != CHEAT_MODE_DISABLED
&& info_current_cheat(NULL, NULL) == CCHT_OK )
{
char gamecode[5];
memcpy( gamecode, (void*)(LGY_ROM_LOC+0xac), 4 );
gamecode[4] = '\0';
acl_open_lib( "gba.acl" );
acl_select_game( gamecode, 0, NULL );
hookpoint_analyzer ha = {read_hookpoint_file, path};
apply_cheat( g_oafConfig.cheatMode, fileSize, &ha, keyremix_cheatkey(), CHEAT_INUSE_ADDR, &fileSize );
acl_close_lib();
fini_current_cheat();
}
*romSizeOut = fixRomPadding(fileSize);
#ifndef NDEBUG
if( dump_patched_rom )
{
dump_rom( fileSize );
}
#endif
// @MERGE 231006 START
// else
// {
// res = RES_ROM_TOO_BIG;
// fClose(f);
// }
// @MERGE 231006 END
}
}
return res;
}
static u16 checkSaveOverride(u32 gameCode) // Save type overrides for modern homebrew.
{
if( gameCode == 0 )
return 0xFF;
switch (gameCode & 0xFFu)
{
case '1': return SAVE_TYPE_EEPROM_64k; // Homebrew using EEPROM.
case '2': return SAVE_TYPE_SRAM_256k; // Homebrew using SRAM.
case '3': return SAVE_TYPE_FLASH_512k_PSC_RTC; // Homebrew using FLASH-64.
case '4': return SAVE_TYPE_FLASH_1m_MRX_RTC; // Homebrew using FLASH-128.
case 'F': return SAVE_TYPE_EEPROM_8k; // Classic NES Series.
case 'S': return SAVE_TYPE_SRAM_256k; // Homebrew using SRAM (Butano games).
}
if( g_oafConfig.savePolicy != SAVE_POLICY_GBADB ) // skip the mapping
return 0xFF;
Result res;
FHandle f;
GameDbEntry instance;
u32* code;
u32 similarCode = 0xFFFFFFu & gameCode;
u16 similar = 0;
if((res = fOpen(&f, "gba_db.bin", FA_OPEN_EXISTING | FA_READ)) == RES_OK)
{
int count = fSize(f) / sizeof( GameDbEntry );
fLseek(f, 0);
for( int i=0; i < count; ++i )
{
if((res = fRead(f, &instance, sizeof(GameDbEntry), NULL)) != RES_OK) break;
code = (u32*)instance.serial;
if( (*code & 0xFFFFFFu) == similarCode ) {
if( *code == gameCode ){
fClose(f);
return instance.attr & 0xFu;
}
else if( similar == 0 ){
similar = instance.attr & 0xFu;
}
}
}
debug_printf("serial not found: %08lx\n", gameCode);
fClose(f);
}
return similar ? similar : 0xFF;
}
static u16 detectSaveType(u32 romSize)
{
const u32 *romPtr = (u32*)LGY_ROM_LOC;
u16 saveType;
if( g_oafConfig.savePolicy == SAVE_POLICY_SRAM )
{
return SAVE_TYPE_SRAM_256k;
}
if((saveType = checkSaveOverride(romPtr[0xAC / 4])) != 0xFF)
{
debug_printf("Serial in override list.\n"
"saveType: %u\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.
const u16 defaultSave = g_oafConfig.defaultSave;
if(defaultSave > SAVE_TYPE_NONE)
saveType = SAVE_TYPE_NONE;
else
saveType = defaultSave;
for(; romPtr < (u32*)(LGY_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
// Assume common sizes for popular games to aid ROM hacks.
{"EEPROM_V111", SAVE_TYPE_EEPROM_8k},
{"EEPROM_V120", SAVE_TYPE_EEPROM_8k},
{"EEPROM_V121", SAVE_TYPE_EEPROM_64k},
{"EEPROM_V122", SAVE_TYPE_EEPROM_8k},
{"EEPROM_V124", SAVE_TYPE_EEPROM_64k},
{"EEPROM_V125", SAVE_TYPE_EEPROM_8k},
{"EEPROM_V126", SAVE_TYPE_EEPROM_8k},
// 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++;
}
debug_printf("SDK save string: %s\n"
"saveType: %u\n", str, tmpSaveType);
return tmpSaveType;
}
}
}
}
debug_printf("saveType: %u\n", saveType);
return saveType;
}
// Search for entry with first u64 of the SHA1 = x using binary search.
static Result searchGbaDb(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;
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)
{
debug_printf("Not found!");
res = RES_NOT_FOUND;
break;
}
if(tmp > x) r = mid - 1;
else l = mid + 1;
}
fClose(f);
}
return res;
}
static u16 getSaveType(u32 romSize, const char *const savePath)
{
//FILINFO fi;
const bool saveOverride = g_oafConfig.saveOverride;
const u16 autoSaveType = detectSaveType(romSize);
//const bool saveExists = fStat(savePath, &fi) == RES_OK;
u64 sha1[3];
sha((u32*)LGY_ROM_LOC, romSize, (u32*)sha1, SHA_IN_BIG | SHA_1_MODE, SHA_OUT_BIG);
Result res;
GameDbEntry dbEntry;
s32 dbPos = -1;
u16 saveType = SAVE_TYPE_NONE;
res = searchGbaDb(*sha1, &dbEntry, &dbPos);
if(res == RES_OK) saveType = dbEntry.attr & 0xFu;
else if(!saveOverride && res == RES_NOT_FOUND) return autoSaveType;
else if(res != RES_NOT_FOUND)
{
//ee_puts("Could not access gba_db.bin! Press any button to continue.");
printErrorWaitInput(res, 0);
return autoSaveType;
}
//debug_printf("saveType: %u\n", saveType);
if(saveOverride)
{
u8 cursor;
static const u8 saveTypeCursorLut[16] = {0, 0, 1, 1, 2, 3, 2, 3, 2, 3, 4, 5, 4, 5, 6, 7};
if(!g_oafConfig.useGbaDb || res == RES_NOT_FOUND)
cursor = saveTypeCursorLut[autoSaveType];
else cursor = saveTypeCursorLut[saveType];
atp_itemval_t target_type;
atp_error_t err = atp_select(
"请选择此游戏的存档类型",
8,
custom_savetype,
NULL, NULL,
(atp_counter_t)cursor, 1, &target_type
);
if( err == ATP_POWER_OFF ) goto end;
/*consoleClear();
ee_printf("==Save Type Override Menu==\n"
"Save file: %s\n"
"Save type (autodetected): %u\n"
"Save type (from gba_db.bin): ", (saveExists ? "Found" : "Not found"), autoSaveType);
if(res == RES_NOT_FOUND)
ee_puts("Not found");
else
ee_printf("%u\n", saveType);
ee_puts("\n"
"=Save Types=\n"
" EEPROM 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"
"=Controls=\n"
"Up/Down: Navigate\n"
"A: Select\n"
"X: Delete save file");
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;
if(!g_oafConfig.useGbaDb || res == RES_NOT_FOUND)
cursor = saveTypeCursorLut[autoSaveType];
else
cursor = saveTypeCursorLut[saveType];
while(1)
{
ee_printf("\x1b[%u;H ", oldCursor + 6);
ee_printf("\x1b[%u;H>", cursor + 6);
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[1;11HDeleted ");
}
else if(kDown & KEY_A) break;
}
//static const u8 cursorSaveTypeLut[8] = {0, 2, 8, 9, 10, 11, 14, 15};*/
saveType = (u16)target_type;// 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++;
}
}
end:
return saveType;
}
static void adjustGammaTableForGba(void)
{
const float gbaGamma = g_oafConfig.gbaGamma;
const float lcdGamma = g_oafConfig.lcdGamma;
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, gbaGamma) * powf((float)i / 255.0f + brightness / contrast, gbaGamma),
1.0f / lcdGamma) * 255.0f;
// Same adjustment for red/green/blue.
REG_LCD_PDC0_GTBL_FIFO = res<<16 | res<<8 | res;
REG_LCD_PDC1_GTBL_FIFO = res<<16 | res<<8 | res;
}
}
static void repairBootFirm( char *firm_path );
/**
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)
{
const KHandle event = (KHandle)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();
// 地址0x18180000保存的是400x240大小的贴图数据rgb888(序列:↑↘↑)
// 地址0x18200000保存的是512x512大小的GBA的240x160贴图数据(序列:→↙→)
// 所以GPU做的事情就是将18200000的数据逆时针旋转90°
if( g_oafConfig.scaler == 0 ) // 上屏无缩放
{
if ( !use_border )
{
int base_offset = 57600; // 3ds高240, gba高160居中需要平移40个像素rgb888每个像素3字节共120字节
// 但是gpu的transfer engine只支持16字节对齐的地址。所以只能选择16和3的公约数就是48,96,144,192,240
GX_displayTransfer((u32*)(0x18180000), 240u<<16 | 240u,
GFX_getFramebuffer(SCREEN_TOP) + base_offset, 240u<<16 | 240u, 1u<<12 | 1u<<8);
}
else GX_displayTransfer((u32*)(0x18180000), 400u<<16 | 240u,
GFX_getFramebuffer(SCREEN_TOP), 400u<<16 | 240u, 1u<<12 | 1u<<8);
}
else if( g_oafConfig.scaler == 3 )//下屏无缩放
{
int base_offset = 28800;
GX_displayTransfer((u32*)(0x18180000), 240u<<16 | 240u,
GFX_getFramebuffer(SCREEN_BOT) + base_offset, 240u<<16 | 240u, 1u<<12 | 1u<<8);
}
else 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 | KEY_SELECT)) dumpFrameTex();
// @MERGE 231006 START
CODEC_runHeadphoneDetection();
// @MERGE 231006 END
}
taskExit();
}
static int cfgIniCallback(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 = (u16)strtoul(value, NULL, 10);
else if(strcmp(name, "directBoot") == 0)
config->directBoot = (strcmp(value, "false") == 0 ? false : true);
else if(strcmp(name, "useGbaDb") == 0)
config->useGbaDb = (strcmp(value, "true") == 0 ? true : false);
}
else if(strcmp(section, "video") == 0)
{
if(strcmp(name, "scaler") == 0)
config->scaler = (u8)strtoul(value, NULL, 10);
else if(strcmp(name, "gbaGamma") == 0)
config->gbaGamma = str2float(value);
else if(strcmp(name, "lcdGamma") == 0)
config->lcdGamma = 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, "game") == 0)
{
if(strcmp(name, "saveSlot") == 0)
config->saveSlot = (u8)strtoul(value, NULL, 10);
}
else if(strcmp(section, "advanced") == 0)
{
if(strcmp(name, "saveOverride") == 0)
config->saveOverride = (strcmp(value, "false") == 0 ? false : true);
if(strcmp(name, "defaultSave") == 0)
config->defaultSave = (u16)strtoul(value, NULL, 10);
}
else if(strcmp(section, "boost") == 0)
{
if( strcmp(name, "cheatMode") == 0 )
config->cheatMode = (u8)strtoul(value, NULL, 10);
if( config->cheatMode >= CHEAT_MODE_SIZE ) config->cheatMode = CHEAT_MODE_DISABLED;
if( strcmp(name, "haltMode") == 0 )
config->haltMode = (u8)strtoul( value, NULL, 10 );
if( config->haltMode > HALT_MODE_SIZE || strlen( FIRMPATH_INCOME ) >= FIRMPATH_SIZELIMIT )
config->haltMode = HALT_MODE_POWEROFF;
}
else return 0; // Error.
return 1; // 1 is no error? Really?
}
static Result parseOafConfig(const char *const path, const bool writeDefaultCfg)
{
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, cfgIniCallback, &g_oafConfig);
else if(writeDefaultCfg)
{
const char *const defaultConfig = DEFAULT_CONFIG;
res = fsQuickWrite(path, defaultConfig, strlen(defaultConfig));
}
free(iniBuf);
return res;
}
static Result showFileBrowser(char romAndSavePath[512])
{
Result res;
char *lastDir = (char*)calloc(512, 1);
if(lastDir != NULL)
{
do
{
// Get last ROM launch path.
if((res = fsLoadPathFromFile("lastdir.txt", lastDir)) != FR_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] = '\n';
res = fsQuickWrite("lastdir.txt", lastDir, strlen(lastDir));
}
}
} while(0);
free(lastDir);
}
else res = RES_OUT_OF_MEM;
return res;
}
static void rom2GameCfgPath(char romPath[512])
{
// Extract the file name and change the extension.
// For cfg2SavePath() we need to reserve 2 extra bytes/chars.
char tmpSaveFileName[256];
safeStrcpy(tmpSaveFileName, strrchr(romPath, '/') + 1, 256 - 2);
strcpy(tmpSaveFileName + strlen(tmpSaveFileName) - 4, ".ini");
// Construct the new path.
strcpy(romPath, OAF_SAVE_DIR "/");
strcat(romPath, tmpSaveFileName);
}
static void gameCfg2SavePath(char cfgPath[512], const u8 saveSlot)
{
if(saveSlot > 9)
{
*cfgPath = '\0'; // Prevent using the ROM as save file.
return;
}
static char numberedExt[7] = {'.', 'X', '.', 's', 'a', 'v', '\0'};
// Change the extension.
// This relies on rom2GameCfgPath() to reserve 2 extra bytes/chars.
numberedExt[1] = '0' + saveSlot;
strcpy(cfgPath + strlen(cfgPath) - 4, (saveSlot == 0 ? ".sav" : numberedExt));
}
Result oafParseConfigEarly(void)
{
Result res;
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;
// Create the saves folder.
if((res = fMkdir(OAF_SAVE_DIR)) != RES_OK && res != RES_FR_EXIST) break;
if((res = fMkdir(KEYREMIX_OUTPUT_DIR)) != RES_OK && res != RES_FR_EXIST) break;
if((res = acf_initialize("wqy11.fnt")) != RES_OK ) break;
// Parse the config.
res = parseOafConfig("config.ini", true);
} while(0);
return res;
}
u16 oafGetBacklightConfig(void)
{
return g_oafConfig.backlight;
}
// @MERGE 231006 START
KHandle setupFrameCapture(const u8 scaler)
{
const bool is240x160 = scaler != 2;
static const s16 matrix[12 * 8] =
{
// Vertical.
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0x24B0, 0x4000, 0, 0x24B0, 0x4000, 0, 0,
0x4000, 0x2000, 0, 0x4000, 0x2000, 0, 0, 0,
0, -0x4B0, 0, 0, -0x4B0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
// Horizontal.
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0x24B0, 0, 0, 0x24B0, 0, 0,
0x4000, 0x4000, 0x2000, 0x4000, 0x4000, 0x2000, 0, 0,
0, 0, -0x4B0, 0, 0, -0x4B0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
ScalerCfg gbaCfg;
gbaCfg.w = (is240x160 ? 240 : 360);
gbaCfg.h = (is240x160 ? 160 : 240);
gbaCfg.vLen = 6;
gbaCfg.vPatt = 0b00011011;
memcpy(gbaCfg.vMatrix, matrix, 6 * 8 * 2);
gbaCfg.hLen = 6;
gbaCfg.hPatt = (is240x160 ? 0b00111111 : 0b00011011);
if(is240x160)
{
memset(gbaCfg.hMatrix, 0, 6 * 8 * 2);
s16 *const identityRow = &gbaCfg.hMatrix[3 * 8];
for(unsigned i = 0; i < 6; i++)
{
// Set identity entries.
identityRow[i] = 0x4000;
}
}
else
{
memcpy(gbaCfg.hMatrix, &matrix[6 * 8], 6 * 8 * 2);
}
return LGYFB_init(&gbaCfg);
}
// @MERGE 231006 END
Result oafInitAndRun( char *firm_path, bool *direct_off )
{
Result res;
flushDCache();
repairBootFirm( firm_path );
char *const filePath = (char*)calloc(512, 1);
if(filePath != NULL)
{
do
{
// Try to load the ROM path from autoboot.txt.
// If this file doesn't exist show the file browser.
if((res = fsLoadPathFromFile("autoboot.txt", filePath)) == RES_FR_NO_FILE)
{
if((res = showFileBrowser(filePath)) != RES_OK || *filePath == '\0')
{
*direct_off = true;
break;
}
ee_puts("Loading...");
}
else if(res != RES_OK) break;
// Load the ROM file.
u32 romSize;
if((res = loadGbaRom(filePath, &romSize)) != RES_OK) break;
// Load the per-game config.
rom2GameCfgPath(filePath);
if((res = parseOafConfig(filePath, false)) != RES_OK && res != RES_FR_NO_FILE) break;
// Adjust the path for the save file and get save type.
u16 saveType = from_savetype_cache(filePath);
if( saveType > SAVE_TYPE_MASK || g_oafConfig.savePolicy >= SAVE_POLICY_FIRM )
{
if( g_oafConfig.savePolicy == SAVE_POLICY_POPUP )
g_oafConfig.saveOverride = true;
else g_oafConfig.saveOverride = g_oafConfig.useGbaDb = false;
if(g_oafConfig.useGbaDb || g_oafConfig.saveOverride)
saveType = getSaveType(romSize, filePath);
else saveType = detectSaveType(romSize);
savetype_cache_store(filePath, saveType);
}
gameCfg2SavePath(filePath, g_oafConfig.saveSlot);
keyremix_freeze();
// Prepare ARM9 for GBA mode + save loading.
if((res = LGY_prepareGbaMode(g_oafConfig.directBoot, saveType, filePath)) == RES_OK)
{
//ee_puts("\x1b[2J");
detect_cheatKey = (g_oafConfig.cheatMode == CHEAT_MODE_ENABYKEY || g_oafConfig.cheatMode == CHEAT_MODE_KEYONOFF) ? keyremix_cheatkey() : 0;
#ifdef NDEBUG
// Force black and turn the backlight off on the bottom screen.
// Don't turn the backlight off on 2DS (1 panel).
if( g_oafConfig.scaler==3 ) GFX_setForceBlack(true, false);
else GFX_setForceBlack(false, true);
if(MCU_getSystemModel() != 3) GFX_powerOffBacklights(g_oafConfig.scaler==3 ? GFX_BLIGHT_TOP : GFX_BLIGHT_BOT);
#endif
// Initialize the legacy frame buffer and frame handler.
// @MERGE 231006 START
const KHandle frameReadyEvent = setupFrameCapture(g_oafConfig.scaler);
{
//LGYFB_init(frameReadyEvent, g_oafConfig.scaler); // 这里把GBA的输出转换成0x18200000处512x512大小的纹理
// @MERGE 231006 END
if(g_oafConfig.scaler == 0) // No borders for scaled modes.
{
// Abuse currently invisible frame buffer as temporary buffer.
void *const borderBuf = GFX_getFramebuffer(SCREEN_TOP);
if(fsQuickRead("border.bgr", borderBuf, 400 * 240 * 3) == RES_OK)
{
// Copy border in swizzled form to GPU render buffer.
GX_displayTransfer(borderBuf, 400u<<16 | 240, (u32*)0x18180000, 400u<<16 | 240, 1u<<12 | 1u<<8 | 1u<<1);
GFX_waitForPPF();
use_border = true;
}
}
else if( g_oafConfig.scaler == 3u )
{
memset(consoleGet()->frameBuffer, 0, 320*240*2);
GFX_setDoubleBuffering( SCREEN_BOT, true );
GFX_setFramebufFmt(GFX_BGR8, GFX_BGR8);
}
patchGbaGpuCmdList(g_oafConfig.scaler, use_border);
// @MERGE 231006 END
}
createTask(0x800, 3, gbaGfxHandler, (void*)frameReadyEvent);
g_frameReadyEvent = frameReadyEvent;
// Adjust gamma table and sync LgyFb start with LCD VBlank.
adjustGammaTableForGba();
GFX_waitForVBlank0();
LGY11_switchMode();
}
} while(0);
}
else res = RES_OUT_OF_MEM;
free(filePath);
flushDCache();
return res;
}
void oafUpdate(void)
{
keyremix_update( detect_cheatKey );
waitForEvent(g_frameReadyEvent);
}
void oafFinish(void)
{
LGYFB_deinit();
// @MERGE 231006 START
// if(g_frameReadyEvent != 0)
// {
// deleteEvent(g_frameReadyEvent); // gbaGfxHandler() will automatically terminate.
// }
g_frameReadyEvent = 0;
LGY11_deinit();
// @MERGE 231006 END
}
static const char *autorun = "sdmc:/luma/payloads/autorun.luma";
static u32 luma_reboot_ctx = 0;
#define LUMA_REBOOT_READY 1
#define LUMA_REBOOT_LOCAL 2
#define LUMA_REBOOT_GLOBAL 4
static bool checkLumaFirm( const char *file )
{
FHandle h;
char text[64]; // 比length大就行
u32 readed;
u32 length = strlen( autorun );
Result res = fOpen(&h, file, FA_OPEN_EXISTING | FA_READ);
if( RES_OK != res ) return false;
res = fLseek(h, 0x3bd50); // 有疑问就自己用16进制编辑器看这个地址
if( RES_OK != res )
{
fClose(h);
return false;
}
res = fRead(h, text, length, &readed);
fClose(h);
if( RES_OK != res || readed != length)
{
return false;
}
else
{
text[length] = '\0';
return 0 == strcmp(text, autorun);
}
}
static void repairBootFirm( char *firm_path )
{
size_t n = FIRMPATH_SIZELIMIT-1;
char tmp[512]; // 为啥不是0x40因为fsLoadPathFromFile要的是512
if( 0 == strncmp(firm_path, autorun, n) )
{
if( RES_OK == fsLoadPathFromFile("autorun", tmp) )
{
fRename( autorun, tmp );
strncpy( firm_path, tmp, n );
}
else
{
const char *default_path = "sdmc:/luma/payloads/open_agb_firm.firm";
fRename( autorun, default_path );
strncpy( firm_path, default_path, n );
}
firm_path[n] = '\0';
}
// 把Preboot存档的boot.firm恢复回去
if( RES_OK == fsQuickRead("luma.firm", tmp, 63) )
{
if( strncmp(tmp, "FIRM", 4) == 0 ){
fRename("sdmc:/boot.firm", "sdmc:/3ds/open_agb_firm/boot.firm");
fRename("sdmc:/3ds/open_agb_firm/luma.firm", "sdmc:/boot.firm");
}
}
// 检查哪个boot.firm是支持autorun.luma文件的
if( checkLumaFirm("sdmc:/3ds/open_agb_firm/boot.firm") )
{
luma_reboot_ctx |= LUMA_REBOOT_LOCAL;
}
if( checkLumaFirm( "sdmc:/boot.firm" ) )
{
luma_reboot_ctx |= LUMA_REBOOT_GLOBAL;
}
luma_reboot_ctx |= LUMA_REBOOT_READY;
if( !oafRebootReady() )
g_oafConfig.haltMode = HALT_MODE_POWEROFF;
}
Result oafPreboot( const char *firm_path )
{
// 记下来要恢复的文件名。加上\n是因为fsLoadPathFromFile靠它来识别路径结尾
char memo[FIRMPATH_SIZELIMIT+0x10];
ee_snprintf(memo, FIRMPATH_SIZELIMIT+0xF, "%s\n", firm_path);
fsQuickWrite("autorun", memo, strlen(memo));
// 用户的boot.firm没有执行autorun.luma的功能。
// 这里要用改版的boot.firm替换用户原来的boot.firm然后在repairBootFirm还原
if( 0 == (luma_reboot_ctx & LUMA_REBOOT_GLOBAL) ){
fRename("sdmc:/boot.firm", "sdmc:/3ds/open_agb_firm/luma.firm");
fRename("sdmc:/3ds/open_agb_firm/boot.firm", "sdmc:/boot.firm");
}
return fRename(firm_path, autorun);
}
bool oafRebootReady( void )
{
return (luma_reboot_ctx & LUMA_REBOOT_READY)
&& ( (luma_reboot_ctx & LUMA_REBOOT_LOCAL)
|| (luma_reboot_ctx & LUMA_REBOOT_GLOBAL )
);
}