13 Commits

Author SHA1 Message Date
lifehackerhansol
06c1f22cd1 platform: add support for the Stargate 3DS (#71) 2025-12-28 20:22:56 +00:00
Edoardo Lolletti
26f27a4138 Add support for DATEL devices (GAMES n' MUSIC and Action Replay DS(i) Media Edition) (#64) 2025-12-28 19:25:22 +00:00
Edoardo Lolletti
7134c4b330 Optimize space usage of supercard platform (#74) 2025-12-28 16:38:28 +00:00
Gericom
9f6311014d Added support for nand saving in Face Training and Nintendo DS Guide (fixes #6), calculate nand save size from header, fixed Band Brothers save initialization 2025-12-28 17:34:06 +01:00
Gericom
6d12399ba4 Added support for NAND saving in WarioWare D.I.Y. and Jam with the Band (#6)
Also improved speed of creating save files
2025-12-28 13:19:23 +01:00
Gericom
64e020182a Added check to CardiTaskThreadPatch that the required slot is actually mapped to arm7. Fixes #60 2025-12-27 12:44:00 +01:00
Gericom
8036004e5a Merge remote-tracking branch 'origin/develop' into feature/refactor-cardi-task-thread-patch 2025-12-26 20:59:06 +01:00
Gericom
31d6c63e3b Removed redundant forward definition 2025-12-26 13:12:49 +01:00
Gericom
9fea5f7a51 More refactoring for CardiTaskThreadPatch 2025-12-26 13:11:49 +01:00
Gericom
c8898ff13c Attempt to improve handling of thumb signatures in CardiTaskThreadPatch 2025-12-26 12:39:10 +01:00
Mow
497fdca384 Add missing signature for Tetris DS (#66) 2025-12-25 23:01:44 +01:00
Gericom
e58a55b81c Attempt to improve handling of arm signatures in CardiTaskThreadPatch 2025-12-25 13:26:09 +01:00
TY
19cce5960b Properly set the supported language, user language, and region based on the ROM's region (#38) 2025-12-23 14:08:55 +00:00
73 changed files with 2298 additions and 371 deletions

View File

@@ -19,6 +19,7 @@ jobs:
"ACE3DS",
"AK2",
"AKRPG",
"DATEL",
"DSPICO",
"DSTT",
"EZP",
@@ -26,6 +27,7 @@ jobs:
"M3DS",
"R4",
"R4iDSN",
"STARGATE",
"SUPERCARD"
]
runs-on: ubuntu-latest

View File

@@ -12,6 +12,7 @@ jobs:
"ACE3DS",
"AK2",
"AKRPG",
"DATEL",
"DSPICO",
"DSTT",
"EZP",
@@ -19,6 +20,7 @@ jobs:
"M3DS",
"R4",
"R4iDSN",
"STARGATE",
"SUPERCARD"
]
runs-on: ubuntu-latest

View File

@@ -25,6 +25,7 @@ Note that there can be some game compatibility differences between different pla
| ACE3DS | Ace3DS+, Gateway 3DS (blue), r4isdhc.com.cn carts, r4isdhc.hk carts 2020+, various derivatives | âś… |
| AK2 | Acekard 2, 2.1, 2i, r4ids.cn, various derivatives | ❌ |
| AKRPG | Acekard RPG SD card | ❌ |
| DATEL | DATEL devices consisting of GAMES n' MUSIC and Action Replay DS(i) Media Edition | ❌ |
| DSPICO | DSpico | âś… |
| DSTT | DSTT, SuperCard DSONE SDHC, r4isdhc.com carts 2014+, r4i-sdhc.com carts, various derivatives | ❌ |
| EZP | EZ-Flash Parallel | ❌ |
@@ -34,6 +35,7 @@ Note that there can be some game compatibility differences between different pla
| MELONDS | Melon DS support for testing purposes only. | ❌ |
| R4 | Original R4DS (non-SDHC), M3 DS Simply | ❌ |
| R4iDSN | r4idsn.com | ❌ |
| STARGATE | Stargate 3DS DS-mode | âś… |
| SUPERCARD | SuperCard (Slot-2 flashcart) | ❌ |
The DMA column indicates whether DMA card reads are implemented for the platform . Without DMA card reads, some games can have cache related issues.<br>

View File

@@ -1,32 +1,54 @@
#include "common.h"
#include <algorithm>
#include <string.h>
#include "SaveList.h"
#include "SaveListFactory.h"
#include "fileInfo.h"
#include "gameCode.h"
#include "CardSaveArranger.h"
#define SAVE_LIST_PATH "/_pico/savelist.bin"
#define DEFAULT_SAVE_SIZE (512 * 1024)
#define SAVE_FILL_VALUE 0xFF
#define NTR_NAND_BLOCK_SIZE 0x20000
#define TWL_NAND_BLOCK_SIZE 0x80000
#define NAND_RW_REGION_END 0x07A00000
bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const
static const u8 sBandBrothersSaveId[12] = { 0x48, 0x8A, 0x00, 0x00, 0x42, 0x42, 0x44, 0x58, 0x31, 0x32, 0x33, 0x34 };
static const u8 sJamWithTheBandSaveId[16] = { 0xEC, 0x00, 0x9E, 0xA1, 0x51, 0x65, 0x34, 0x35, 0x30, 0x35, 0x30, 0x31, 0x19, 0x19, 0x02, 0x0A };
bool CardSaveArranger::SetupCardSave(const nds_header_ntr_t* header, const TCHAR* savePath) const
{
SaveList* saveList = SaveListFactory().CreateFromFile(SAVE_LIST_PATH);
auto saveType = CardSaveType::None;
u32 saveSize = DEFAULT_SAVE_SIZE;
if (saveList)
if (header->nandBackupRegionStart != 0)
{
const auto saveListEntry = saveList->FindEntry(gameCode);
if (!saveListEntry)
saveType = CardSaveType::Nand;
u32 blockSize = header->IsTwlRom() ? TWL_NAND_BLOCK_SIZE : NTR_NAND_BLOCK_SIZE;
u32 nandBackupRegionStart = header->nandBackupRegionStart * blockSize;
saveSize = NAND_RW_REGION_END - nandBackupRegionStart;
LOG_DEBUG("NAND save. Size: 0x%X.", saveSize);
}
else
{
SaveList* saveList = SaveListFactory().CreateFromFile(SAVE_LIST_PATH);
if (saveList)
{
LOG_WARNING("Game code %c%c%c%c not found in save list\n",
gameCode & 0xFF, (gameCode >> 8) & 0xFF, (gameCode >> 16) & 0xFF, gameCode >> 24);
const auto saveListEntry = saveList->FindEntry(header->gameCode);
if (!saveListEntry)
{
LOG_WARNING("Game code %c%c%c%c not found in save list\n",
header->gameCode & 0xFF, (header->gameCode >> 8) & 0xFF,
(header->gameCode >> 16) & 0xFF, header->gameCode >> 24);
}
else
{
saveType = saveListEntry->GetSaveType();
saveSize = saveListEntry->GetSaveSize();
saveListEntry->Dump();
}
delete saveList;
}
else
{
saveSize = saveListEntry->GetSaveSize();
saveListEntry->Dump();
}
delete saveList;
}
if (saveSize == 0)
{
@@ -50,8 +72,9 @@ bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const
return false;
}
auto ffBuffer = std::make_unique<u8[]>(512);
memset(ffBuffer.get(), SAVE_FILL_VALUE, 512);
const u32 ffBufferSize = 32 * 1024;
auto ffBuffer = std::make_unique<u8[]>(ffBufferSize);
memset(ffBuffer.get(), SAVE_FILL_VALUE, ffBufferSize);
u32 offset = initialSize;
// Align to 512 bytes
@@ -68,17 +91,45 @@ bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const
offset += remainingTo512;
}
// Write in 512-byte blocks
// Write in up to 32kb blocks
while (offset < saveSize)
{
u32 bytesToWrite = std::min<u32>(saveSize - offset, ffBufferSize);
UINT bytesWritten = 0;
if (f_write(file.get(), ffBuffer.get(), 512, &bytesWritten) != FR_OK ||
bytesWritten != 512)
if (f_write(file.get(), ffBuffer.get(), bytesToWrite, &bytesWritten) != FR_OK ||
bytesWritten != bytesToWrite)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
offset += 512;
offset += bytesToWrite;
}
// Additional save initialization
if (header->gameCode == GAMECODE("AXBJ"))
{
// Write save id for Band Brothers
UINT bytesWritten = 0;
if (f_lseek(file.get(), 0) != FR_OK ||
f_write(file.get(), sBandBrothersSaveId, sizeof(sBandBrothersSaveId), &bytesWritten) != FR_OK ||
bytesWritten != sizeof(sBandBrothersSaveId))
{
LOG_FATAL("Failed to write Band Brothers save id\n");
return false;
}
}
else if (header->gameCode == GAMECODE("UXBP"))
{
// Write save id for Jam with the Band
// See also https://github.com/melonDS-emu/melonDS/blob/3a3388c4c50e8735af125c1af4d89e457f5e9035/src/NDSCart.cpp#L1067
UINT bytesWritten = 0;
if (f_lseek(file.get(), saveSize - 0x800) != FR_OK ||
f_write(file.get(), sJamWithTheBandSaveId, sizeof(sJamWithTheBandSaveId), &bytesWritten) != FR_OK ||
bytesWritten != sizeof(sJamWithTheBandSaveId))
{
LOG_FATAL("Failed to write Jam with the Band save id\n");
return false;
}
}
}

View File

@@ -1,12 +1,13 @@
#pragma once
#include "ndsHeader.h"
/// @brief Class for setting up the save file for retail card roms.
class CardSaveArranger
{
public:
/// @brief Sets up the save file at \p savePath for a retail card rom with the given \p gameCode.
/// @param gameCode The game code of the retail card rom.
/// @param header The header of the retail card rom.
/// @param savePath The desired save file path.
/// @return \c true when setting up the save was successful, or \c false otherwise.
bool SetupCardSave(u32 gameCode, const TCHAR* savePath) const;
bool SetupCardSave(const nds_header_ntr_t* header, const TCHAR* savePath) const;
};

View File

@@ -0,0 +1,12 @@
#pragma once
/// @brief Enum for DSi console region.
enum class ConsoleRegion
{
Japan,
America,
Europe,
Australia,
China,
Korea
};

View File

@@ -1,5 +1,6 @@
#include "common.h"
#include <string.h>
#include <algorithm>
#include <memory>
#include "DsiWareSaveArranger.h"
@@ -70,7 +71,7 @@ bool DsiWareSaveArranger::SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSi
if (f_lseek(file.get(), saveSize) != FR_OK ||
f_lseek(file.get(), 0) != FR_OK)
{
LOG_FATAL("Failed to create private save file\n");
LOG_FATAL("Failed to create DSiWare save file\n");
return false;
}
@@ -80,21 +81,28 @@ bool DsiWareSaveArranger::SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSi
if (f_write(file.get(), fatHeader.get(), sizeof(fat_header_t), &bytesWritten) != FR_OK ||
bytesWritten != sizeof(fat_header_t))
{
LOG_FATAL("Failed to format private save file\n");
LOG_FATAL("Failed to format DSiWare save file\n");
return false;
}
memset(fatHeader.get(), 0, 512);
fatHeader.reset();
while (f_tell(file.get()) < saveSize)
const u32 clearBufferSize = 32 * 1024;
auto clearBuffer = std::make_unique<u8[]>(clearBufferSize);
memset(clearBuffer.get(), 0, clearBufferSize);
u32 offset = f_tell(file.get());
while (offset < saveSize)
{
u32 bytesToWrite = std::min<u32>(saveSize - offset, clearBufferSize);
bytesWritten = 0;
if (f_write(file.get(), fatHeader.get(), 512, &bytesWritten) != FR_OK ||
bytesWritten != 512)
if (f_write(file.get(), clearBuffer.get(), bytesToWrite, &bytesWritten) != FR_OK ||
bytesWritten != bytesToWrite)
{
LOG_FATAL("Failed to format private save file\n");
LOG_FATAL("Failed to format DSiWare save file\n");
return false;
}
offset += bytesToWrite;
}
}

View File

@@ -205,7 +205,11 @@ void NdsLoader::Load(BootMode bootMode)
{
if (bootMode != BootMode::SdkResetSystem)
{
HandleCardSave();
if (!CardSaveArranger().SetupCardSave(&_romHeader, _savePath))
{
ErrorDisplay().PrintError("Failed to setup save file.");
return;
}
}
HandleAntiPiracy();
@@ -608,14 +612,6 @@ bool NdsLoader::TryLoadRomHeader(u32 romOffset)
return true;
}
void NdsLoader::HandleCardSave()
{
if (!CardSaveArranger().SetupCardSave(_romHeader.gameCode, _savePath))
{
ErrorDisplay().PrintError("Failed to setup save file.");
}
}
void NdsLoader::HandleAntiPiracy()
{
auto apList = ApListFactory().CreateFromFile(AP_LIST_PATH);
@@ -865,6 +861,10 @@ void NdsLoader::StartRom(BootMode bootMode)
void NdsLoader::SetupTwlConfig()
{
ConsoleRegion romRegion = GetRomRegion(_romHeader.gameCode);
// Set language based on rom region, TODO: allow user to override this via some config or so
UserLanguage userLang = GetLanguageByRomRegion(romRegion);
twl_config_t* twlConfig = (twl_config_t*)0x02000400;
*(twl_config_t**)0x02FFFDFC = twlConfig;
*(vu8*)0x02FFFDFA = 0x80;
@@ -872,7 +872,7 @@ void NdsLoader::SetupTwlConfig()
memset(twlConfig, 0, sizeof(twl_config_t));
twlConfig->configFlags = 0x0100000F;
twlConfig->country = 0x4E;
twlConfig->language = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x64];
twlConfig->language = static_cast<u8>(userLang);
twlConfig->rtcYear = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x66];
twlConfig->rtcOffset = *(s64*)&TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x68];
twlConfig->eulaAgreeVersion[0] = 1;
@@ -880,7 +880,7 @@ void NdsLoader::SetupTwlConfig()
twlConfig->alarmMinute = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x53];
twlConfig->alarmEnable = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x56];
twlConfig->systemMenuUsedTitleSlots = 9;
twlConfig->systemMenuUsedTitleSlots = 30;
twlConfig->systemMenuFreeTitleSlots = 30;
twlConfig->field24 = 3;
memcpy(&twlConfig->touchCalibrationX1Adc, &TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x58], 0xC);
twlConfig->field3C = 0x0201209C;
@@ -906,9 +906,10 @@ void NdsLoader::SetupTwlConfig()
}
*(vu16*)0x020005E2 = swi_getCrc16(0xFFFF, (void*)0x020005E4, 0xC);
*(vu32*)0x02FFFD68 = 0x3E; // supported languages
// Set bitmask for supported languages
*(vu32*)0x02FFFD68 = GetSupportedLanguagesByRegion(romRegion);
*(vu32*)0x02FFFD6C = 0;
*(vu8*)0x02FFFD70 = 2; // region, 2 = europe
*(vu8*)0x02FFFD70 = static_cast<u8>(romRegion); // region
}
void NdsLoader::SetDeviceListEntry(dsi_devicelist_entry_t& deviceListEntry,
@@ -1011,3 +1012,102 @@ bool NdsLoader::TryDecryptSecureArea()
LOG_DEBUG("Decrypted secure area\n");
return true;
}
ConsoleRegion NdsLoader::GetRomRegion(u32 gameCode)
{
u8 gameRegionCode = (gameCode >> 24) & 0xFF;
if (gameRegionCode != 'A' && gameRegionCode != 'O')
{
// Determine region by TID
if (gameRegionCode == 'J')
{
return ConsoleRegion::Japan;
}
else if (gameRegionCode == 'E' || gameRegionCode == 'T')
{
return ConsoleRegion::America;
}
else if (gameRegionCode == 'P' || gameRegionCode == 'V')
{
return ConsoleRegion::Europe;
}
else if (gameRegionCode == 'U')
{
return ConsoleRegion::Australia;
}
else if (gameRegionCode == 'C')
{
return ConsoleRegion::China;
}
else if (gameRegionCode== 'K')
{
return ConsoleRegion::Korea;
}
}
return ConsoleRegion::Europe; // Default to EUR
}
UserLanguage NdsLoader::GetLanguageByRomRegion(ConsoleRegion romRegion)
{
UserLanguage userLang = static_cast<UserLanguage>(TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x64] & 0x07);
if (romRegion == ConsoleRegion::Japan)
{
return UserLanguage::Japanese;
}
else if (romRegion == ConsoleRegion::America &&
(userLang != UserLanguage::English ||
userLang != UserLanguage::French ||
userLang != UserLanguage::Spanish))
{
return UserLanguage::English;
}
else if (romRegion == ConsoleRegion::Europe && userLang < UserLanguage::English && userLang > UserLanguage::Spanish)
{
return UserLanguage::English;
}
else if (romRegion == ConsoleRegion::China)
{
return UserLanguage::Chinese;
}
else if (romRegion == ConsoleRegion::Korea)
{
return UserLanguage::Korean;
}
return userLang;
}
u32 NdsLoader::GetSupportedLanguagesByRegion(ConsoleRegion region)
{
switch (region)
{
case ConsoleRegion::Japan:
{
return 0x01;
}
case ConsoleRegion::America:
{
return 0x26;
}
case ConsoleRegion::Europe:
{
return 0x3E;
}
case ConsoleRegion::Australia:
{
return 0x02;
}
case ConsoleRegion::China:
{
return 0x40;
}
case ConsoleRegion::Korea:
{
return 0x80;
}
}
return 0x3E;
}

View File

@@ -3,6 +3,8 @@
#include "ndsHeader.h"
#include "DsiWareSaveArranger.h"
#include "BootMode.h"
#include "ConsoleRegion.h"
#include "UserLanguage.h"
struct dsi_devicelist_entry_t;
@@ -54,7 +56,6 @@ private:
void ClearMainMemory();
void CreateRomClusterTable();
bool TryLoadRomHeader(u32 romOffset);
void HandleCardSave();
void HandleAntiPiracy();
void RemapWram();
bool TryLoadArm9();
@@ -72,4 +73,7 @@ private:
void InsertArgv();
bool TrySetupDsiWareSave();
bool TryDecryptSecureArea();
ConsoleRegion GetRomRegion(u32 gameCode);
UserLanguage GetLanguageByRomRegion(ConsoleRegion romRegion);
u32 GetSupportedLanguagesByRegion(ConsoleRegion region);
};

View File

@@ -7,13 +7,13 @@
class SaveListEntry
{
u32 gameCode;
u8 saveType; // see CardSaveType
CardSaveType saveType;
u8 saveSize; // 0 or 1 << x
u8 reserved[2]; // for possible future use
public:
u32 GetGameCode() const { return gameCode; }
CardSaveType GetSaveType() const { return static_cast<CardSaveType>(saveType); }
CardSaveType GetSaveType() const { return saveType; }
u32 GetSaveSize() const { return saveSize == 0 ? 0 : (1u << saveSize); }
void Dump() const

View File

@@ -0,0 +1,14 @@
#pragma once
/// @brief Enum for DSi user language.
enum class UserLanguage
{
Japanese,
English,
French,
German,
Italian,
Spanish,
Chinese,
Korean
};

View File

@@ -13,6 +13,10 @@
#include "patches/arm9/OSResetSystemPatch.h"
#include "patches/arm9/PokemonDownloaderArm9Patch.h"
#include "patches/arm9/DSProtectArm9Patch.h"
#include "patches/arm9/NandSave/FaceTrainingNandSavePatch.h"
#include "patches/arm9/NandSave/JamWithTheBandNandSavePatch.h"
#include "patches/arm9/NandSave/NintendoDSGuideNandSavePatch.h"
#include "patches/arm9/NandSave/WarioWareDiyNandSavePatch.h"
#include "patches/arm9/OverlayPatches/FsStartOverlayHookPatch.h"
#include "patches/arm9/OverlayPatches/DSProtectPatches/DSProtectOverlayPatch.h"
#include "patches/arm9/OverlayPatches/DSProtectPatches/DSProtectPuyoPuyo7Patch.h"
@@ -374,6 +378,32 @@ void Arm9Patcher::AddGameSpecificPatches(
overlayHookPatch->AddOverlayPatch(new PokemonIrApPatch(PokemonIrVersion::Bw2));
break;
}
// WarioWare: D.I.Y.
case GAMECODE("UORE"):
case GAMECODE("UORP"):
case GAMECODE("UORJ"):
{
patchCollection.AddPatch(new WarioWareDiyNandSavePatch());
break;
}
// Jam with the Band
case GAMECODE("UXBP"):
{
patchCollection.AddPatch(new JamWithTheBandNandSavePatch());
break;
}
// Face Training
case GAMECODE("USKV"):
{
patchCollection.AddPatch(new FaceTrainingNandSavePatch());
break;
}
// Nintendo DS Guide
case GAMECODE("UGDA"):
{
patchCollection.AddPatch(new NintendoDSGuideNandSavePatch());
break;
}
}
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "sections.h"
#include "../SectorRemapPatchCode.h"
#include "SectorRemapPatchCode.h"
#include "fileInfo.h"
DEFINE_SECTION_SYMBOLS(saveoffsettosdsector);

View File

@@ -8,6 +8,7 @@ extern "C" void __patch_carditaskthread_entry_sdk4();
extern u16 __patch_carditaskthread_mov_cardicommon_to_r4;
extern u16 __patch_carditaskthread_mov_command_to_r0;
extern u16 __patch_carditaskthread_lsls_exmemstat_bit_to_r1;
extern u32 __patch_carditaskthread_failoffset;
extern u32 __patch_carditaskthread_successoffset;

View File

@@ -21,30 +21,34 @@ __patch_carditaskthread_mov_cardicommon_to_r4:
__patch_carditaskthread_mov_command_to_r0:
ldr r0, [r4, #4] // r0 = command
ldr r1,= 0x04000204
ldrh r1, [r1]
.global __patch_carditaskthread_lsls_exmemstat_bit_to_r1
__patch_carditaskthread_lsls_exmemstat_bit_to_r1:
lsls r1, r1, #20
mvns r1, r1
lsrs r1, r1, #31 // r1 = 0 when slot mapped to arm7, 1 when slot not mapped to arm7
cmp r0, #2
beq identify_backup
beq end_success // identify
cmp r0, #6
beq read_backup
beq read_backup
cmp r0, #7
beq write_backup
beq write_backup
cmp r0, #8
beq write_backup
beq write_backup
cmp r0, #9
beq verify_backup
beq verify_backup
// erase
cmp r0, #10
beq end_success
beq end_success
cmp r0, #11
beq end_success
beq end_success
cmp r0, #12
beq end_success
beq end_success
cmp r0, #15
beq end_success
beq end_success
end_fail:
ldr r0, __patch_carditaskthread_failoffset
@@ -58,18 +62,8 @@ end:
adds r3, r0
bx r3
identify_backup:
b end_success
read_backup:
ldr r0, [r4]
movs r1, #0
str r1, [r0] // result
ldr r1, [r0, #0xC] // src
ldr r2, [r0, #0x10] // dst
ldr r3, [r0, #0x14] // len
cmp r3, #0
beq end_success
bl read_write_verify_backup_common
ldr r0, __patch_carditaskthread_readsave_asm_address
bl blx_r0
@@ -77,14 +71,7 @@ read_backup:
b end_success
write_backup:
ldr r0, [r4]
movs r1, #0
str r1, [r0] // result
ldr r1, [r0, #0xC] // src
ldr r2, [r0, #0x10] // dst
ldr r3, [r0, #0x14] // len
cmp r3, #0
beq end_success
bl read_write_verify_backup_common
ldr r0, __patch_carditaskthread_writesave_asm_address
bl blx_r0
@@ -92,12 +79,7 @@ write_backup:
b end_success
verify_backup:
ldr r0, [r4]
ldr r2, [r0, #0xC] // src
ldr r1, [r0, #0x10] // dst
ldr r3, [r0, #0x14] // len
cmp r3, #0
beq end_success
bl read_write_verify_backup_common
push {r0}
ldr r0, __patch_carditaskthread_verifysave_asm_address
@@ -106,6 +88,18 @@ verify_backup:
str r0, [r1] // result
b end_success
read_write_verify_backup_common:
ldr r0, [r4]
str r1, [r0] // result
cmp r1, #0
bne end_success
ldr r1, [r0, #0xC] // src
ldr r2, [r0, #0x10] // dst
ldr r3, [r0, #0x14] // len
cmp r3, #0
beq end_success
bx lr
blx_r0:
bx r0

View File

@@ -3,16 +3,16 @@
.syntax unified
.thumb
// r1 = save src
// r2 = memory src
// r1 = memory src
// r2 = save src
// r3 = byte length
.global verifysave_asm
.type verifysave_asm, %function
verifysave_asm:
push {r4,r5,r6,r7,lr}
movs r4, r3 // remaining bytes
movs r5, r1
movs r6, r2
movs r5, r2
movs r6, r1
1:
movs r0, r5 // will be rounded down by function
ldr r3, verifysave_save_offset_to_sd_sector_asm_address

View File

@@ -8,80 +8,69 @@
#include "patches/arm7/VerifySaveAsm.h"
#include "patches/FunctionSignature.h"
#include "patches/platform/LoaderPlatform.h"
#include "patches/arm7/SaveOffsetToSdSectorAsm.h"
#include "patches/OffsetToSectorRemapAsm.h"
#include "patches/SaveOffsetToSdSectorAsm.h"
#include "patches/arm7/CardiTaskThreadPatchAsm.h"
#include "thumbInstructions.h"
#include "CardiTaskThreadPatch.h"
static constexpr auto sSignaturesArm = std::to_array<const FunctionSignature>
({
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59FA108u, 0xE28A5040u }, 0x020029A0, 0x02005015 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59FA10Cu, 0xE28A5040u }, 0x02004F50, 0x02017532 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91DCu, 0xE2895044u }, 0x02017532, 0x02017532 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91E0u, 0xE2895044u }, 0x02017532, 0x02027534 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91E0u, 0xE2895048u }, 0x030028A0, 0x03004E86 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91D4u, 0xE2895048u }, 0x03004F4C, 0x03017534 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91F8u, 0xE2895048u }, 0x03017530, 0x04017530 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEADCu }, 0x04002774, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEACEu }, 0x04007531, 0x04007531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEABFu }, 0x04007530, 0x04007531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEAAAu }, 0x04017530, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEA94u }, 0x04017530, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEA8Du }, 0x04017531, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEA77u }, 0x04017533, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA70u }, 0x04027530, 0x04027539 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA8Du }, 0x04027530, 0x04027539 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA6Cu }, 0x04027531, 0x04027531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA77u }, 0x04027531, 0x04027539 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA5Au }, 0x04027533, 0x04027533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFFAD7u }, 0x03017531, 0x03017531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFFAA5u }, 0x03027531, 0x03027531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEADBu }, 0x03027531, 0x03027531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA68u }, 0x04027533, 0x04027533 }
});
struct CardiTaskThreadSignature
{
FunctionSignature signature;
CardiTaskThreadPatch::PatchVariant patchVariant;
};
static constexpr auto sSignaturesThumb = std::to_array<const FunctionSignature>
static constexpr auto sSignatures = std::to_array<const CardiTaskThreadSignature>
({
{ (const u32[]) { 0xB081B5F0u, 0x1C2E4D2Du, 0x27103640u, 0xF7FC2400u }, 0x02004FB0, 0x02004FB4 },
{ (const u32[]) { 0xB081B5F0u, 0x1C2E4D2Bu, 0x27103640u, 0xF7FC2400u }, 0x02007531, 0x02017532 },
{ (const u32[]) { 0xB081B5F0u, 0x1C264C57u, 0x27103644u, 0xF7FC2500u }, 0x02027532, 0x02027535 },
{ (const u32[]) { 0xB081B5F0u, 0x1C264C55u, 0x27103648u, 0xF7FC2500u }, 0x03007530, 0x03012776 },
{ (const u32[]) { 0xB081B5F0u, 0x1C264C5Cu, 0x27033648u, 0xF7FC2500u }, 0x03017531, 0x03027532 },
{ (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FAB3u, 0x36481C07u }, 0x04007531, 0x04007531 },
{ (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FA97u, 0x36481C07u }, 0x04007531, 0x04007531 },
{ (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FA4Fu, 0x36481C07u }, 0x04017530, 0x04017533 },
{ (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FA23u, 0x36481C07u }, 0x04017531, 0x04017531 },
{ (const u32[]) { 0x4D62B5F8u, 0xF7FC2400u, 0x1C2EFA2Du, 0x36481C07u }, 0x04027531, 0x04027539 }
// arm
{ { (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59FA108u, 0xE28A5040u }, 0x020029A0, 0x02005015 }, CardiTaskThreadPatch::PatchVariant::ArmA },
{ { (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59FA10Cu, 0xE28A5040u }, 0x02004F50, 0x02017532 }, CardiTaskThreadPatch::PatchVariant::ArmB },
{ { (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91DCu, 0xE2895044u }, 0x02017532, 0x02017532 }, CardiTaskThreadPatch::PatchVariant::ArmC },
{ { (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91E0u, 0xE2895044u }, 0x02017532, 0x02027534 }, CardiTaskThreadPatch::PatchVariant::ArmC },
{ { (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91E0u, 0xE2895048u }, 0x030028A0, 0x03004E86 }, CardiTaskThreadPatch::PatchVariant::ArmC },
{ { (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91D4u, 0xE2895048u }, 0x03004F4C, 0x03017534 }, CardiTaskThreadPatch::PatchVariant::ArmC },
{ { (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91F8u, 0xE2895048u }, 0x03017530, 0x04017530 }, CardiTaskThreadPatch::PatchVariant::ArmD },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEADCu }, 0x04002774, 0x04017533 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEACEu }, 0x04007531, 0x04007531 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEABFu }, 0x04007530, 0x04007531 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEAAAu }, 0x04017530, 0x04017533 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEA94u }, 0x04017530, 0x04017533 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEA8Du }, 0x04017531, 0x04017533 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEA77u }, 0x04017533, 0x04017533 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA70u }, 0x04027530, 0x04027539 }, CardiTaskThreadPatch::PatchVariant::ArmF },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA8Du }, 0x04027530, 0x04027539 }, CardiTaskThreadPatch::PatchVariant::ArmF },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA6Cu }, 0x04027531, 0x04027531 }, CardiTaskThreadPatch::PatchVariant::ArmF },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA77u }, 0x04027531, 0x04027539 }, CardiTaskThreadPatch::PatchVariant::ArmF },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA5Au }, 0x04027533, 0x04027533 }, CardiTaskThreadPatch::PatchVariant::ArmF },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFFAD7u }, 0x03017531, 0x03017531 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFFAA5u }, 0x03027531, 0x03027531 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEADBu }, 0x03027531, 0x03027531 }, CardiTaskThreadPatch::PatchVariant::ArmE },
{ { (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA68u }, 0x04027533, 0x04027533 }, CardiTaskThreadPatch::PatchVariant::ArmF },
// thumb
{ { (const u32[]) { 0xB081B5F0u, 0x1C2E4D2Du, 0x27103640u, 0xF7FC2400u }, 0x02004FB0, 0x02004FB4 }, CardiTaskThreadPatch::PatchVariant::ThumbA },
{ { (const u32[]) { 0xB081B5F0u, 0x1C2E4D2Bu, 0x27103640u, 0xF7FC2400u }, 0x02007531, 0x02017532 }, CardiTaskThreadPatch::PatchVariant::ThumbB },
{ { (const u32[]) { 0xB081B5F0u, 0x1C264C57u, 0x27103644u, 0xF7FC2500u }, 0x02027532, 0x02027535 }, CardiTaskThreadPatch::PatchVariant::ThumbC },
{ { (const u32[]) { 0xB081B5F0u, 0x1C264C55u, 0x27103648u, 0xF7FC2500u }, 0x03007530, 0x03012776 }, CardiTaskThreadPatch::PatchVariant::ThumbC },
{ { (const u32[]) { 0xB081B5F0u, 0x1C264C5Cu, 0x27033648u, 0xF7FC2500u }, 0x03017531, 0x03027532 }, CardiTaskThreadPatch::PatchVariant::ThumbD },
{ { (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FAB3u, 0x36481C07u }, 0x04007531, 0x04007531 }, CardiTaskThreadPatch::PatchVariant::ThumbE },
{ { (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FA97u, 0x36481C07u }, 0x04007531, 0x04007531 }, CardiTaskThreadPatch::PatchVariant::ThumbE },
{ { (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FA4Fu, 0x36481C07u }, 0x04017530, 0x04017533 }, CardiTaskThreadPatch::PatchVariant::ThumbE },
{ { (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FA23u, 0x36481C07u }, 0x04017531, 0x04017531 }, CardiTaskThreadPatch::PatchVariant::ThumbE },
{ { (const u32[]) { 0x4D62B5F8u, 0xF7FC2400u, 0x1C2EFA2Du, 0x36481C07u }, 0x04027531, 0x04027539 }, CardiTaskThreadPatch::PatchVariant::ThumbF }
});
bool CardiTaskThreadPatch::FindPatchTarget(PatchContext& patchContext)
{
for (const auto& signature : sSignaturesArm)
for (const auto& signature : sSignatures)
{
if (CheckSignature(patchContext, signature))
if (patchContext.GetSdkVersion() >= signature.signature.GetMinimumSdkVersion() &&
patchContext.GetSdkVersion() <= signature.signature.GetMaximumSdkVersion())
{
if (signature.GetPattern()[2] == 0xE59F91DCu)
_cardiTaskThread = patchContext.FindPattern32(signature.signature.GetPattern(), 16);
if (_cardiTaskThread)
{
_peach = true;
}
else if (signature.GetPattern()[3] == 0xEBFFFAD7u
|| signature.GetPattern()[3] == 0xEBFFFAA5u
|| signature.GetPattern()[3] == 0xEBFFEADBu)
{
_pokemonDownloader = true;
}
break;
}
}
if (!_cardiTaskThread)
{
for (const auto& signature : sSignaturesThumb)
{
if (CheckSignature(patchContext, signature))
{
_thumb = true;
LOG_DEBUG("CARDi_TaskThread found at 0x%p\n", _cardiTaskThread);
_patchVariant = signature.patchVariant;
break;
}
}
@@ -98,20 +87,18 @@ bool CardiTaskThreadPatch::FindPatchTarget(PatchContext& patchContext)
void CardiTaskThreadPatch::ApplyPatch(PatchContext& patchContext)
{
if (!_cardiTaskThread)
{
return;
//0xA4 = ADDLS PC, PC, R0,LSL#2
}
u32 patch1Size = SECTION_SIZE(patch_carditaskthread);
void* patch1Address = patchContext.GetPatchHeap().Alloc(patch1Size);
auto loaderPlatform = patchContext.GetLoaderPlatform();
const SdReadPatchCode* readPatchCode;
const SdWritePatchCode* writePatchCode;
const SectorRemapPatchCode* sectorRemapPatchCode;
readPatchCode = loaderPlatform->CreateSdReadPatchCode(
auto readPatchCode = loaderPlatform->CreateSdReadPatchCode(
patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap());
writePatchCode = loaderPlatform->CreateSdWritePatchCode(
auto writePatchCode = loaderPlatform->CreateSdWritePatchCode(
patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap());
sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<SaveOffsetToSdSectorPatchCode>
auto sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<SaveOffsetToSdSectorPatchCode>
(
patchContext.GetPatchHeap(),
(const save_file_info_t*)((u32)SHARED_SAVE_FILE_INFO - 0x02F00000 + 0x02700000)
@@ -142,152 +129,204 @@ void CardiTaskThreadPatch::ApplyPatch(PatchContext& patchContext)
__patch_carditaskthread_readsave_asm_address = (u32)readSavePatchCode->GetReadSaveFunction();
__patch_carditaskthread_writesave_asm_address = (u32)writeSavePatchCode->GetWriteSaveFunction();
__patch_carditaskthread_verifysave_asm_address = (u32)verifySavePatchCode->GetVerifySaveFunction();
u32 entryAddress;
u32 patchOffset;
if (_thumb)
if (loaderPlatform->GetPlatformType() == LoaderPlatformType::Slot1)
{
if (patchContext.GetSdkVersion() >= 0x4027531)
{
patchOffset = 0x90;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOVS_REG(THUMB_R4, THUMB_R5);
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0C) + 9;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x14) + 9;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = entryAddress;
}
else if (patchContext.GetSdkVersion() >= 0x4007530)
{
patchOffset = 0x90;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0C) + 9;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x14) + 9;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = entryAddress;
}
else if (patchContext.GetSdkVersion() >= 0x3027531)
{
patchOffset = 0x98;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0E) + 8;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x18) + 8;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = entryAddress;
}
else if (patchContext.GetSdkVersion() >= 0x3012776
|| (patchContext.GetSdkVersion() >= 0x2027532 && patchContext.GetSdkVersion() <= 0x2027535))
{
patchOffset = 0x82;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0E) + 8;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x18) + 8;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x06) = entryAddress;
}
else if (patchContext.GetSdkVersion() >= 0x2004FB0)
{
patchOffset = patchContext.GetSdkVersion() >= 0x2007531 ? 0x60 : 0x64;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOVS_REG(THUMB_R4, THUMB_R5);
__patch_carditaskthread_failoffset = 0;
__patch_carditaskthread_successoffset = 0;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 0);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = 0xE001; // jump over entryAddress
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x04) = entryAddress;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x08) = THUMB_NOP;
}
else
{
LOG_FATAL("Unsupported thumb CARDi_TaskThread\n");
while(1);
}
// Test REG_EXMEMSTAT bit 11
__patch_carditaskthread_lsls_exmemstat_bit_to_r1 = THUMB_LSLS_IMM(THUMB_R1, THUMB_R1, 31 - 11);
}
else
{
if (patchContext.GetSdkVersion() < 0x2020000 && !_peach)
{
// uses a table dispatch
//0xA4 = ldr r1,= table
__patch_carditaskthread_failoffset = 4;
__patch_carditaskthread_successoffset = 4;
if (patchContext.GetSdkVersion() >= 0x2005015)//0x2007532)//0x2012774)
patchOffset = 0xA4;
else
patchOffset = 0xA0;
// Test REG_EXMEMSTAT bit 7
__patch_carditaskthread_lsls_exmemstat_bit_to_r1 = THUMB_LSLS_IMM(THUMB_R1, THUMB_R1, 31 - 7);
}
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOV_HIREG(THUMB_R4, THUMB_HI_R10);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x00) = 0xe59f0004; // ldr r0,= entryAddress
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x04) = 0xe1a0e00f; // mov lr, pc
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = 0xe12fff10; // bx r0
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x0C) = entryAddress;
}
else
{
if (patchContext.GetSdkVersion() >= 0x4027531)
{
patchOffset = 0xA8;
entryAddress = (u32)&__patch_carditaskthread_entry_sdk4 - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
}
else if (patchContext.GetSdkVersion() >= 0x4007530 || _pokemonDownloader)
{
patchOffset = 0xA8;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
}
else if (patchContext.GetSdkVersion() >= 0x3017531)
{
patchOffset = 0xB4;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOV_HIREG(THUMB_R4, THUMB_HI_R9);
}
else
{
patchOffset = 0x9C;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOV_HIREG(THUMB_R4, THUMB_HI_R9);
}
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x00) = 0xe59f000C; // ldr r0,= entryAddress
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x04) = 0xe1a0e00f; // mov lr, pc
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = 0xe12fff10; // bx r0
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x14) = entryAddress;
}
if (_patchVariant >= PatchVariant::ThumbA)
{
ApplyThumbPatch(patch1Address);
}
else
{
ApplyArmPatch(patch1Address);
}
memcpy(patch1Address, SECTION_START(patch_carditaskthread), patch1Size);
}
bool CardiTaskThreadPatch::CheckSignature(const PatchContext& patchContext, const FunctionSignature& signature)
void CardiTaskThreadPatch::ApplyArmPatch(void* patch1Address) const
{
if (patchContext.GetSdkVersion() >= signature.GetMinimumSdkVersion() &&
patchContext.GetSdkVersion() <= signature.GetMaximumSdkVersion())
u32 entryAddress;
u32 patchOffset;
if (_patchVariant == PatchVariant::ArmA || _patchVariant == PatchVariant::ArmB)
{
_cardiTaskThread = patchContext.FindPattern32(signature.GetPattern(), 16);
if (_cardiTaskThread)
// uses a table dispatch
__patch_carditaskthread_failoffset = 4;
__patch_carditaskthread_successoffset = 4;
if (_patchVariant == PatchVariant::ArmB)
{
LOG_DEBUG("CARDi_TaskThread found at 0x%p\n", _cardiTaskThread);
return true;
// [+0xA4] = 0xE59F1074 = ldr r1,= table
patchOffset = 0xA4;
}
else
{
// [+0xA0] = 0xE59F1074 = ldr r1,= table
patchOffset = 0xA0;
}
}
return false;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOV_HIREG(THUMB_R4, THUMB_HI_R10);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x00) = 0xe59f0004; // ldr r0,= entryAddress
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x04) = 0xe1a0e00f; // mov lr, pc
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = 0xe12fff10; // bx r0
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x0C) = entryAddress;
}
else
{
switch (_patchVariant)
{
case PatchVariant::ArmC:
{
// [+0x9C] = 0xE5990004 = ldr r0, [r9, #4]
patchOffset = 0x9C;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOV_HIREG(THUMB_R4, THUMB_HI_R9);
break;
}
case PatchVariant::ArmD:
{
// [+0xB4] = 0xE5990004 = ldr r0, [r9, #4]
patchOffset = 0xB4;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOV_HIREG(THUMB_R4, THUMB_HI_R9);
break;
}
case PatchVariant::ArmE:
{
// [+0xA8] = 0xE5940004 = ldr r0, [r4, #4]
patchOffset = 0xA8;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
break;
}
case PatchVariant::ArmF:
{
// [+0xA8] = 0x0A000044 = beq
patchOffset = 0xA8;
entryAddress = (u32)&__patch_carditaskthread_entry_sdk4 - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
break;
}
default:
{
LOG_FATAL("Unsupported CARDi_TaskThread\n");
while (1);
break;
}
}
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x00) = 0xE59F000C; // ldr r0,= entryAddress
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x04) = 0xE1A0E00F; // mov lr, pc
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = 0xE12FFF10; // bx r0
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x14) = entryAddress;
}
}
void CardiTaskThreadPatch::ApplyThumbPatch(void* patch1Address) const
{
u32 entryAddress;
u32 patchOffset;
if (_patchVariant == PatchVariant::ThumbA || _patchVariant == PatchVariant::ThumbB)
{
if (_patchVariant == PatchVariant::ThumbB)
{
// [+0x60] = 28 1C 69 68
// adds r0, r5, #0
// ldr r1, [r5, #4]
patchOffset = 0x60;
}
else
{
// [+0x64] = 28 1C 69 68
// adds r0, r5, #0
// ldr r1, [r5, #4]
patchOffset = 0x64;
}
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOVS_REG(THUMB_R4, THUMB_R5);
__patch_carditaskthread_failoffset = 0;
__patch_carditaskthread_successoffset = 0;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 0);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = 0xE001; // jump over entryAddress
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x04) = entryAddress;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x08) = THUMB_NOP;
}
else
{
switch (_patchVariant)
{
case PatchVariant::ThumbC:
{
// [+0x82] = 00 18 78 44
// adds r0, r0, r0
// add r0, pc
patchOffset = 0x82;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0E) + 8;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x18) + 8;
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x06) = entryAddress;
break;
}
case PatchVariant::ThumbD:
{
// [+0x98] = 00 18 78 44
// adds r0, r0, r0
// add r0, pc
patchOffset = 0x98;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0E) + 8;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x18) + 8;
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = entryAddress;
break;
}
case PatchVariant::ThumbE:
{
// [+0x90] = 00 18 78 44
// adds r0, r0, r0
// add r0, pc
patchOffset = 0x90;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0C) + 9;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x14) + 9;
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = entryAddress;
break;
}
case PatchVariant::ThumbF:
{
// [+0x90] = 00 18 78 44
// adds r0, r0, r0
// add r0, pc
patchOffset = 0x90;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOVS_REG(THUMB_R4, THUMB_R5);
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0C) + 9;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x14) + 9;
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = entryAddress;
break;
}
default:
{
LOG_FATAL("Unsupported thumb CARDi_TaskThread\n");
while (1);
break;
}
}
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
}
}

View File

@@ -1,20 +1,34 @@
#pragma once
#include "patches/Patch.h"
class FunctionSignature;
/// @brief Arm7 patch for redirecting save reads and writes on SDK 2-4.
class CardiTaskThreadPatch : public Patch
{
public:
enum class PatchVariant : u16
{
None,
ArmA,
ArmB,
ArmC,
ArmD,
ArmE,
ArmF,
ThumbA,
ThumbB,
ThumbC,
ThumbD,
ThumbE,
ThumbF
};
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
u32* _cardiTaskThread = nullptr;
u16 _thumb = false;
u16 _peach = false;
u16 _pokemonDownloader = false;
PatchVariant _patchVariant = PatchVariant::None;
bool CheckSignature(const PatchContext& patchContext, const FunctionSignature& signature);
void ApplyArmPatch(void* patch1Address) const;
void ApplyThumbPatch(void* patch1Address) const;
};

View File

@@ -5,9 +5,8 @@
#include "patches/arm7/ReadSaveAsm.h"
#include "patches/arm7/WriteSaveAsm.h"
#include "patches/arm7/VerifySaveAsm.h"
#include "patches/arm7/SaveOffsetToSdSectorAsm.h"
#include "patches/SaveOffsetToSdSectorAsm.h"
#include "patches/platform/LoaderPlatform.h"
#include "patches/OffsetToSectorRemapAsm.h"
#include "patches/arm7/CardiTaskThreadPatchAsm.h"
#include "CardiDoTaskFromArm9Patch.h"
@@ -93,6 +92,16 @@ void CardiDoTaskFromArm9Patch::ApplyPatch(PatchContext& patchContext)
__patch_carditaskthread_readsave_asm_address = (u32)readSavePatchCode->GetReadSaveFunction();
__patch_carditaskthread_writesave_asm_address = (u32)writeSavePatchCode->GetWriteSaveFunction();
__patch_carditaskthread_verifysave_asm_address = (u32)verifySavePatchCode->GetVerifySaveFunction();
if (loaderPlatform->GetPlatformType() == LoaderPlatformType::Slot1)
{
// Test REG_EXMEMSTAT bit 11
__patch_carditaskthread_lsls_exmemstat_bit_to_r1 = THUMB_LSLS_IMM(THUMB_R1, THUMB_R1, 31 - 11);
}
else
{
// Test REG_EXMEMSTAT bit 7
__patch_carditaskthread_lsls_exmemstat_bit_to_r1 = THUMB_LSLS_IMM(THUMB_R1, THUMB_R1, 31 - 7);
}
u32 patchOffset;

Some files were not shown because too many files have changed in this diff Show More