mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-06 05:44:11 +08:00
1208 lines
34 KiB
C
1208 lines
34 KiB
C
/*
|
||
* 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 )
|
||
);
|
||
} |