From 6a97b677a7b21c92612537e910ed3eb69031dc13 Mon Sep 17 00:00:00 2001 From: Edoardo Lolletti Date: Sat, 10 Jan 2026 23:00:39 +0100 Subject: [PATCH] Add support for Slot 2 flashcarts using Compact Flash (#84) - Supercard CF (SUPERCARDCF) - GBA Media Player CF (MPCF) - M3 Adapter CF (M3CF) - Max Media Dock CF (MMCF) --- .github/workflows/nightly.yml | 6 +- .github/workflows/release.yml | 6 +- README.md | 6 +- .../platform/LoaderPlatformFactory.cpp | 12 ++ .../CompactFlashCommonLoaderPlatform.cpp | 66 ++++++ .../CompactFlashCommonLoaderPlatform.h | 79 +++++++ .../CompactFlashReadWriteSectorPatchCode.h | 86 ++++++++ .../CompactFlashReadWriteSectorPatchCode.s | 202 ++++++++++++++++++ .../CompactFlashRegisters.h | 15 ++ .../CompactFlashStatusFunctionsPatchCode.h | 32 +++ .../CompactFlashStatusFunctionsPatchCode.s | 77 +++++++ .../ICompactFlashLockUnlockPatchCode.h | 13 ++ .../platform/m3cf/M3CFLoaderPlatform.cpp | 41 ++++ .../platform/m3cf/M3CFLoaderPlatform.h | 37 ++++ .../m3cf/M3CFLockUnlockCardPatchCode.h | 20 ++ .../m3cf/M3CFLockUnlockCardPatchCode.s | 92 ++++++++ .../platform/mmcf/MMCFLoaderPlatform.h | 27 +++ .../platform/mpcf/MPCFLoaderPlatform.h | 27 +++ .../supercardcf/SuperCardCFLoaderPlatform.cpp | 21 ++ .../supercardcf/SuperCardCFLoaderPlatform.h | 37 ++++ .../SuperCardCFLockUnlockCardPatchCode.h | 20 ++ .../SuperCardCFLockUnlockCardPatchCode.s | 26 +++ 22 files changed, 945 insertions(+), 3 deletions(-) create mode 100644 arm9/source/patches/platform/compactflash-common/CompactFlashCommonLoaderPlatform.cpp create mode 100644 arm9/source/patches/platform/compactflash-common/CompactFlashCommonLoaderPlatform.h create mode 100644 arm9/source/patches/platform/compactflash-common/CompactFlashReadWriteSectorPatchCode.h create mode 100644 arm9/source/patches/platform/compactflash-common/CompactFlashReadWriteSectorPatchCode.s create mode 100644 arm9/source/patches/platform/compactflash-common/CompactFlashRegisters.h create mode 100644 arm9/source/patches/platform/compactflash-common/CompactFlashStatusFunctionsPatchCode.h create mode 100644 arm9/source/patches/platform/compactflash-common/CompactFlashStatusFunctionsPatchCode.s create mode 100644 arm9/source/patches/platform/compactflash-common/ICompactFlashLockUnlockPatchCode.h create mode 100644 arm9/source/patches/platform/m3cf/M3CFLoaderPlatform.cpp create mode 100644 arm9/source/patches/platform/m3cf/M3CFLoaderPlatform.h create mode 100644 arm9/source/patches/platform/m3cf/M3CFLockUnlockCardPatchCode.h create mode 100644 arm9/source/patches/platform/m3cf/M3CFLockUnlockCardPatchCode.s create mode 100644 arm9/source/patches/platform/mmcf/MMCFLoaderPlatform.h create mode 100644 arm9/source/patches/platform/mpcf/MPCFLoaderPlatform.h create mode 100644 arm9/source/patches/platform/supercardcf/SuperCardCFLoaderPlatform.cpp create mode 100644 arm9/source/patches/platform/supercardcf/SuperCardCFLoaderPlatform.h create mode 100644 arm9/source/patches/platform/supercardcf/SuperCardCFLockUnlockCardPatchCode.h create mode 100644 arm9/source/patches/platform/supercardcf/SuperCardCFLockUnlockCardPatchCode.s diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ab65cda..2bb9aa3 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -27,7 +27,11 @@ jobs: "R4", "R4iDSN", "STARGATE", - "SUPERCARD" + "SUPERCARD", + "SUPERCARDCF", + "M3CF", + "MMCF", + "MPCF" ] runs-on: ubuntu-latest container: skylyrac/blocksds:slim-v1.13.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7d1c55..a7f29bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,11 @@ jobs: "R4", "R4iDSN", "STARGATE", - "SUPERCARD" + "SUPERCARD", + "SUPERCARDCF", + "M3CF", + "MMCF", + "MPCF" ] runs-on: ubuntu-latest container: skylyrac/blocksds:slim-v1.13.1 diff --git a/README.md b/README.md index a9c716d..cd0e863 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,15 @@ Note that there can be some game compatibility differences between different pla | G003 | M3i Zero (GMP-Z003) | ✅ | | ISNITRO | Supports the IS-NITRO-EMULATOR through agb semihosting. | ❌ | | M3DS | M3 DS Real, M3i Zero, iTouchDS, r4rts.com, r4isdhc.com RTS (black) | ❌ | +| M3CF | M3 Compact Flash (Slot-2 flashcart) | ❌ | | MELONDS | Melon DS support for testing purposes only. | ❌ | +| MMCF | DATEL Max Media Dock Compact Flash (Slot-2 flashcart) | ❌ | +| MPCF | GBA Media Player Compact Flash (Slot-2 cart) | ❌ | | R4 | Original R4DS (non-SDHC), M3 DS Simply | ❌ | | R4iDSN | r4idsn.com | ❌ | | STARGATE | Stargate 3DS DS-mode | ✅ | -| SUPERCARD | SuperCard (Slot-2 flashcart) | ❌ | +| SUPERCARD | SuperCard SD, Lite and Rumble (Slot-2 flashcart) | ❌ | +| SUPERCARDCF | SuperCard CF (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.
Note that there are still SDK versions and variants for which Pico Loader does not yet support DMA card reads. diff --git a/arm9/source/patches/platform/LoaderPlatformFactory.cpp b/arm9/source/patches/platform/LoaderPlatformFactory.cpp index e09a548..f57d38b 100644 --- a/arm9/source/patches/platform/LoaderPlatformFactory.cpp +++ b/arm9/source/patches/platform/LoaderPlatformFactory.cpp @@ -15,6 +15,10 @@ #include "patches/platform/ezp/EzpLoaderPlatform.h" #include "patches/platform/datel/DatelLoaderPlatform.h" #include "patches/platform/stargate/StargateLoaderPlatform.h" +#include "patches/platform/supercardcf/SuperCardCFLoaderPlatform.h" +#include "patches/platform/mpcf/MPCFLoaderPlatform.h" +#include "patches/platform/m3cf/M3CFLoaderPlatform.h" +#include "patches/platform/mmcf/MMCFLoaderPlatform.h" #include "LoaderPlatformFactory.h" LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const @@ -49,6 +53,14 @@ LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const return new DatelLoaderPlatform(); #elif defined(PICO_LOADER_TARGET_STARGATE) return new StargateLoaderPlatform(); +#elif defined(PICO_LOADER_TARGET_SUPERCARDCF) + return new SuperCardCFLoaderPlatform(); +#elif defined(PICO_LOADER_TARGET_MPCF) + return new MPCFLoaderPlatform(); +#elif defined(PICO_LOADER_TARGET_M3CF) + return new M3CFLoaderPlatform(); +#elif defined(PICO_LOADER_TARGET_MMCF) + return new MMCFLoaderPlatform(); #else #error "No loader platform defined" return nullptr; diff --git a/arm9/source/patches/platform/compactflash-common/CompactFlashCommonLoaderPlatform.cpp b/arm9/source/patches/platform/compactflash-common/CompactFlashCommonLoaderPlatform.cpp new file mode 100644 index 0000000..e038bd0 --- /dev/null +++ b/arm9/source/patches/platform/compactflash-common/CompactFlashCommonLoaderPlatform.cpp @@ -0,0 +1,66 @@ +#include "common.h" +#include +#include "CompactFlashCommonLoaderPlatform.h" + +static constexpr int CF_CARD_TIMEOUT = 10000000; +static constexpr int CF_STS_READY = 0x40; +static constexpr int CF_STS_DSC = 0x10; +static constexpr int CF_STS_BUSY = 0x80; + +bool CompactFlashCommonLoaderPlatform::InitializeSdCard() +{ + u32 oldMemCnt = REG_EXMEMCNT; + mem_setGbaCartridgeRomWaits(EXMEMCNT_SLOT2_ROM_WAIT1_10, EXMEMCNT_SLOT2_ROM_WAIT2_6); + SetCardLocked(false); + auto res = InitializeCfCard(); + SetCardLocked(true); + REG_EXMEMCNT = oldMemCnt; + return res; +} + +static bool waitAvailableForCommands(const cf_registers_t& registers) +{ + // wait for card to finish previous commands + for (int i = 0; i < CF_CARD_TIMEOUT; i++) + { + if ((*registers.command & CF_STS_BUSY) == 0) + { + break; + } + } + + // wait for card to be ready for new commands + for (int i = 0; i < CF_CARD_TIMEOUT; i++) + { + if ((*registers.command & (CF_STS_READY | CF_STS_DSC)) != 0) + { + return true; + } + } + + return false; +} + +bool CompactFlashCommonLoaderPlatform::InitializeCfCard() +{ + const auto& registers = GetCfRegisters(); + if (!waitAvailableForCommands(registers)) + { + return false; + } + // Check that the registers are writable and hold the values we set + u16 temp = *registers.lba1; + *registers.lba1 = (~temp & 0xFF); + temp = (~temp & 0xFF); + if (*registers.lba1 != temp) + { + return false; + } + + *registers.lba1 = 0xAA55; + if (*registers.lba1 == 0xAA55) + { + return false; + } + return true; +} diff --git a/arm9/source/patches/platform/compactflash-common/CompactFlashCommonLoaderPlatform.h b/arm9/source/patches/platform/compactflash-common/CompactFlashCommonLoaderPlatform.h new file mode 100644 index 0000000..5bf70ed --- /dev/null +++ b/arm9/source/patches/platform/compactflash-common/CompactFlashCommonLoaderPlatform.h @@ -0,0 +1,79 @@ +#pragma once +#include "common.h" +#include "../LoaderPlatform.h" +#include "CompactFlashRegisters.h" +#include "CompactFlashStatusFunctionsPatchCode.h" +#include "CompactFlashReadWriteSectorPatchCode.h" +#include "ICompactFlashLockUnlockPatchCode.h" + +/// @brief Base implementation of LoaderPlatform for the Compact Flash slot 2 flashcarts +class CompactFlashCommonLoaderPlatform : public LoaderPlatform +{ +public: + LoaderPlatformType GetPlatformType() const override { return LoaderPlatformType::Slot2; } + + bool InitializeSdCard() override; + + const IReadSectorsPatchCode* CreateSdReadPatchCode( + PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override + { + const auto& registers = GetCfRegisters(); + auto statusFunctions = patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new CompactFlashStatusFunctionsPatchCode(patchHeap, registers); + }); + + auto transferSector = patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new CompactFlashTransferSectorPatchCode(patchHeap, registers, statusFunctions); + }); + + auto lockUnlock = CreateLockingPatchCode(patchCodeCollection, patchHeap); + + return patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new CompactFlashReadWriteSectorPatchCode(patchHeap, registers, transferSector, lockUnlock); + }); + } + + const IWriteSectorsPatchCode* CreateSdWritePatchCode( + PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override + { + const auto& registers = GetCfRegisters(); + auto statusFunctions = patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new CompactFlashStatusFunctionsPatchCode(patchHeap, registers); + }); + + auto transferSector = patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new CompactFlashTransferSectorPatchCode(patchHeap, registers, statusFunctions); + }); + + auto lockUnlock = CreateLockingPatchCode(patchCodeCollection, patchHeap); + + return patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new CompactFlashReadWriteSectorPatchCode(patchHeap, registers, transferSector, lockUnlock); + }); + } + +protected: + /// @brief Locks/Unlocks the cart to operate on the inserted CF card + /// @param locked Whether the card should be locked (prevent R/W operations) or unlocked (allows R/W operations) + virtual void SetCardLocked(bool locked) const = 0; + + /// @brief Generates the patch code containing the lock/unlock routines equivalent to \see SetCardLocked + /// If a card requires no locking, this doesn't have to be implemented. + /// @note The returned routine, is only allowed to modify r0, other registers should be left untouched + /// @return A pointer to the allocated \see ICompactFlashLockUnlockPatchCode + virtual const ICompactFlashLockUnlockPatchCode* CreateLockingPatchCode( + PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const { return nullptr; } + + /// @brief Returns the exposed address associated to the Compact Flash registers + /// @return the \see cf_registers_t struct containing the registers + virtual const cf_registers_t& GetCfRegisters() const = 0; + +private: + bool InitializeCfCard(); +}; diff --git a/arm9/source/patches/platform/compactflash-common/CompactFlashReadWriteSectorPatchCode.h b/arm9/source/patches/platform/compactflash-common/CompactFlashReadWriteSectorPatchCode.h new file mode 100644 index 0000000..ae99968 --- /dev/null +++ b/arm9/source/patches/platform/compactflash-common/CompactFlashReadWriteSectorPatchCode.h @@ -0,0 +1,86 @@ +#pragma once +#include "common.h" +#include "sections.h" +#include "patches/PatchCode.h" +#include "thumbInstructions.h" +#include "../IReadSectorsPatchCode.h" +#include "../IWriteSectorsPatchCode.h" +#include "CompactFlashRegisters.h" +#include "ICompactFlashLockUnlockPatchCode.h" + +DEFINE_SECTION_SYMBOLS(cf_perform_transfer); +DEFINE_SECTION_SYMBOLS(cf_read_write_functions); + +extern "C" bool cf_performTransferSectors(u32 numSectors, u32 sector, u8 command, void* srcAddr, void* dstAddr); + +extern "C" bool cf_readSectors(u32 sector, void* buffer, u32 numSectors); +extern "C" bool cf_writeSectors(u32 sector, void* buffer, u32 numSectors); + +extern vu16* cf_performTransferSectors_reg_sector_count; +extern u32 cf_performTransferSectors_waitCardAvailableForCommands; +extern u32 cf_performTransferSectors_waitNextBlockReady; + +extern vu16* cf_performTransfer_reg_data; +extern u32 cf_performTransfer_performTransferSectors; +extern u32 cf_performTransfer_lockUnlockCard; + +extern u16 cf_performTransfer_unlock_label[2]; +extern u16 cf_performTransfer_lock_label[2]; + +class CompactFlashTransferSectorPatchCode : public PatchCode +{ +public: + CompactFlashTransferSectorPatchCode(PatchHeap& patchHeap, + const cf_registers_t& registers, + const CompactFlashStatusFunctionsPatchCode* compactFlashStatusFunctionsPatchCode) + : PatchCode(SECTION_START(cf_perform_transfer), SECTION_SIZE(cf_perform_transfer), patchHeap) + { + cf_performTransferSectors_reg_sector_count = registers.sectorCount; + cf_performTransferSectors_waitCardAvailableForCommands = (u32)compactFlashStatusFunctionsPatchCode->GetWaitCardAvailableForCommandsFunction(); + cf_performTransferSectors_waitNextBlockReady = (u32)compactFlashStatusFunctionsPatchCode->GetWaitNextBlockReadyFunction(); + } + + const void* GetPerformTransferSectorsFunction() const + { + return GetAddressAtTarget((void*)cf_performTransferSectors); + } +}; + +class CompactFlashReadWriteSectorPatchCode : public PatchCode, public IReadSectorsPatchCode, public IWriteSectorsPatchCode +{ +public: + CompactFlashReadWriteSectorPatchCode(PatchHeap& patchHeap, + const cf_registers_t& registers, + const CompactFlashTransferSectorPatchCode* compactFlashTransferSectorPatchCode, + const ICompactFlashLockUnlockPatchCode* lockUnlockCard) + : PatchCode(SECTION_START(cf_read_write_functions), SECTION_SIZE(cf_read_write_functions), patchHeap) + { + cf_performTransfer_reg_data = registers.data; + + cf_performTransfer_performTransferSectors = (u32)compactFlashTransferSectorPatchCode->GetPerformTransferSectorsFunction(); + + if (lockUnlockCard) + { + cf_performTransfer_lockUnlockCard = (u32)lockUnlockCard->GetLockUnlockFunction(); + } + else + { + // what is getting replaced is a `bl`, taking 4 bytes + const u16 noLockingOpcode = THUMB_MOV_HIREG(THUMB_HI_R8, THUMB_HI_R8); + cf_performTransfer_unlock_label[0] = noLockingOpcode; + cf_performTransfer_unlock_label[1] = noLockingOpcode; + cf_performTransfer_lock_label[0] = noLockingOpcode; + cf_performTransfer_lock_label[1] = noLockingOpcode; + } + } + + const ReadSectorsFunc GetReadSectorsFunction() const override + { + return (const ReadSectorsFunc)GetAddressAtTarget((void*)cf_readSectors); + } + + const WriteSectorsFunc GetWriteSectorFunction() const override + { + return (const WriteSectorsFunc)GetAddressAtTarget((void*)cf_writeSectors); + } +}; diff --git a/arm9/source/patches/platform/compactflash-common/CompactFlashReadWriteSectorPatchCode.s b/arm9/source/patches/platform/compactflash-common/CompactFlashReadWriteSectorPatchCode.s new file mode 100644 index 0000000..5123938 --- /dev/null +++ b/arm9/source/patches/platform/compactflash-common/CompactFlashReadWriteSectorPatchCode.s @@ -0,0 +1,202 @@ +.cpu arm7tdmi +.syntax unified +.thumb + +.section "cf_perform_transfer", "ax" + +.equ CF_CMD_LBA, 0xE0 +.equ CF_CMD_READ, 0x20 +.equ CF_CMD_WRITE, 0x30 + +@ bool cf_performTransferSectors(u32 numSectors, u32 sector, void* srcAddr, void* dstAddr, u8 command) +.type cf_performTransferSectors, %function +.global cf_performTransferSectors +cf_performTransferSectors: + push {r0,r1,r4-r7,lr} + ldr r7, cf_performTransferSectors_waitCardAvailableForCommands + @ calls waitCardAvailableForCommands + @ this function doesn't alter r0, but sets the zero flag on failure and clears it on success + bl cf_performTransferSectors_interwork + beq cf_performTransferSectors_error + + ldr r5, cf_performTransferSectors_reg_sector_count + + @ load 0x20000 + movs r6, #0x01 + lsls r6, #17 + + @ store sector count + strh r0, [r5] + adds r5, r6 + + lsls r7, r1, #24 + lsrs r7, r7, #24 + @ store lba1 + strh r7, [r5] + adds r5, r6 + + lsls r7, r1, #16 + lsrs r7, r7, #24 + @ store lba2 + strh r7, [r5] + adds r5, r6 + + lsls r7, r1, #8 + lsrs r7, r7, #24 + @ store lba3 + strh r7, [r5] + adds r5, r6 + + @ Only lower nibble is transferred + lsls r7, r1, #4 + lsrs r7, r7, #28 + @ store lba4 + adds r7, CF_CMD_LBA + strh r7, [r5] + + @ store command + strh r4, [r5, r6] + + @ get total number of bytes to write + lsls r0, #9 + + ldr r7, cf_performTransferSectors_waitNextBlockReady +read_next_block: + @ calls waitCardNextBlockReady + @ this function doesn't alter r0, but sets the zero flag on failure and clears it on success + bl cf_performTransferSectors_interwork + beq cf_performTransferSectors_error + +read_next_int: + ldm r2!, {r1,r4,r5,r6} + stm r3!, {r1,r4,r5,r6} + + subs r0, #16 + beq done + + @ Shifting left by 0x17 will set the Zero flag if the number that was shifted is a multiple + @ of 0x200 (indicating a full sector has been written) + lsls r1, r0, #0x17 + bne read_next_int + + b read_next_block +done: + movs r0, #1 +cf_performTransferSectors_error: + pop {r0,r1,r4-r7, pc} + +cf_performTransferSectors_interwork: + bx r7 + +.balign 4 +.pool +.global cf_performTransferSectors_reg_sector_count +cf_performTransferSectors_reg_sector_count: + .word 0 +.global cf_performTransferSectors_waitCardAvailableForCommands +cf_performTransferSectors_waitCardAvailableForCommands: + .word 0 +.global cf_performTransferSectors_waitNextBlockReady +cf_performTransferSectors_waitNextBlockReady: + .word 0 + +.section "cf_read_write_functions", "ax" +.global cf_performTransfer_unlock_label +.global cf_performTransfer_lock_label +@ r2 srcAddr +@ r3 dstAddr +@ r4 command +@ top of stack startSector +@ below it numSectors +@ cf_performTransfer(dstAddr, srcAddr, command, startSector, numSectors) +cf_performTransfer: + @ loads EXMEMCNT register address + ldr r6, =0x04000200 + @ waitstate 4,2 and arm9 slot2 access + @ r6 + 4 is EXMEMCNT, use lower 8 bits as 0 + strb r6, [r6, #4] + + ldr r7, cf_performTransfer_lockUnlockCard + movs r0, #0 + + @ if the cart requires no lock/unlock sequence, this is replaced with a nop +cf_performTransfer_unlock_label: + @ calls lockUnlockCard + bl cf_performTransfer_interwork + + @ r2,r3,r4 hold variables not to be touched + + ldr r7, cf_performTransfer_performTransferSectors + + @ r1 holds startSector + @ r5 holds remainingSectors + pop {r1,r5} + movs r0, #0xFF +readNextSectorBlock: + subs r5, r0 + ble lastRead + + @ calls performTransferSectors + @ this function doesn't alter r0, but sets the zero flag on failure and clears it on success + bl cf_performTransfer_interwork + beq error + @ increment sector + adds r1, r0 + b readNextSectorBlock + +lastRead: + adds r0, r5 + @ calls performTransferSectors + @ this function doesn't alter r0, but sets the zero flag on failure and clears it on success + bl cf_performTransfer_interwork + +error: + ldr r7, cf_performTransfer_lockUnlockCard + movs r0, #1 + + @ if the cart requires no lock/unlock sequence, this is replaced with a nop +cf_performTransfer_lock_label: + @ calls lockUnlockCard + bl cf_performTransfer_interwork + + @ waitstate 4,2 and arm7 slot2 access + movs r2, #0x80 + @ r6 + 4 is EXMEMCNT + strb r2, [r6, #4] + pop {r4-r7, pc} +cf_performTransfer_interwork: + bx r7 + +@ cf_readSectors(u32 sector, void* buffer, u32 numSectors) +.type cf_readSectors, %function +.global cf_readSectors +cf_readSectors: + push {r0,r2,r4-r7, lr} + movs r4, CF_CMD_READ + movs r3, r1 + ldr r2, cf_performTransfer_reg_data + + b cf_performTransfer + +@ cf_writeSectors(u32 sector, void* buffer, u32 numSectors) +.type cf_writeSectors, %function +.global cf_writeSectors +cf_writeSectors: + push {r0,r2,r4-r7, lr} + movs r4, CF_CMD_WRITE + ldr r3, cf_performTransfer_reg_data + movs r2, r1 + + b cf_performTransfer + +.balign 4 +.pool +.global cf_performTransfer_reg_data +cf_performTransfer_reg_data: + .word 0 +.global cf_performTransfer_performTransferSectors +cf_performTransfer_performTransferSectors: + .word 0 +.global cf_performTransfer_lockUnlockCard +cf_performTransfer_lockUnlockCard: + .word 0 diff --git a/arm9/source/patches/platform/compactflash-common/CompactFlashRegisters.h b/arm9/source/patches/platform/compactflash-common/CompactFlashRegisters.h new file mode 100644 index 0000000..ece8d22 --- /dev/null +++ b/arm9/source/patches/platform/compactflash-common/CompactFlashRegisters.h @@ -0,0 +1,15 @@ +#pragma once +#include "common.h" + +struct cf_registers_t +{ + vu16* data; + vu16* altStatus; + vu16* command; + vu16* error; + vu16* sectorCount; + vu16* lba1; + vu16* lba2; + vu16* lba3; + vu16* lba4; +}; \ No newline at end of file diff --git a/arm9/source/patches/platform/compactflash-common/CompactFlashStatusFunctionsPatchCode.h b/arm9/source/patches/platform/compactflash-common/CompactFlashStatusFunctionsPatchCode.h new file mode 100644 index 0000000..83c50c6 --- /dev/null +++ b/arm9/source/patches/platform/compactflash-common/CompactFlashStatusFunctionsPatchCode.h @@ -0,0 +1,32 @@ +#pragma once +#include "common.h" +#include "sections.h" +#include "../LoaderPlatform.h" +#include "CompactFlashRegisters.h" + +DEFINE_SECTION_SYMBOLS(cf_wait_functions); + +extern "C" bool cf_waitCardAvailableForCommands(); +extern "C" bool cf_waitNextBlockReady(); + +extern vu16* cf_waitFunctions_reg_cmd; + +class CompactFlashStatusFunctionsPatchCode : public PatchCode +{ +public: + CompactFlashStatusFunctionsPatchCode(PatchHeap& patchHeap, const cf_registers_t& registers) + : PatchCode(SECTION_START(cf_wait_functions), SECTION_SIZE(cf_wait_functions), patchHeap) + { + cf_waitFunctions_reg_cmd = registers.command; + } + + const void* GetWaitCardAvailableForCommandsFunction() const + { + return GetAddressAtTarget((void*)cf_waitCardAvailableForCommands); + } + + const void* GetWaitNextBlockReadyFunction() const + { + return GetAddressAtTarget((void*)cf_waitNextBlockReady); + } +}; diff --git a/arm9/source/patches/platform/compactflash-common/CompactFlashStatusFunctionsPatchCode.s b/arm9/source/patches/platform/compactflash-common/CompactFlashStatusFunctionsPatchCode.s new file mode 100644 index 0000000..b325284 --- /dev/null +++ b/arm9/source/patches/platform/compactflash-common/CompactFlashStatusFunctionsPatchCode.s @@ -0,0 +1,77 @@ +.cpu arm7tdmi +.syntax unified +.thumb + +.section "cf_wait_functions", "ax" + +.equ CF_STS_DRQ, 0x08 +.equ CF_STS_DSC, 0x10 +.equ CF_STS_READY, 0x40 +.equ CF_STS_INSERTED, 0x50 +.equ CF_STS_BUSY, 0x80 + +.equ CF_CARD_TIMEOUT, 10000000 + +@ Waits until the card is ready to receive commands +@ this function doesn't alter r0, but sets the zero flag on failure and clears it on success +@ bool cf_waitCardAvailableForCommands() +.global cf_waitCardAvailableForCommands +.type cf_waitCardAvailableForCommands, %function +cf_waitCardAvailableForCommands: + push {r0-r4, lr} + @ wait for card to finish previous commands + ldr r1, =CF_CARD_TIMEOUT + ldr r2, cf_waitFunctions_reg_cmd + movs r4, r1 +still_busy: + ldrh r0, [r2] + @ r0 & CF_STS_BUSY == 0 + lsls r0, #25 + bcc no_longer_busy + subs r1, #1 + bne still_busy + +no_longer_busy: + @ wait for card to be ready for commands + movs r3, (CF_STS_READY | CF_STS_DSC) +1: + ldrh r1, [r2] + tst r1, r3 + @ timeout expired + bne ready + subs r4, #1 + @ not ready + bne 1b + + @ timeout expired, return 0 + pop {r0-r4, pc} + +@ Waits until the card is ready to write/return the next block +@ this function doesn't alter r0, but sets the zero flag on failure and clears it on success +@ bool cf_waitNextBlockReady() +.global cf_waitNextBlockReady +.type cf_waitNextBlockReady, %function +cf_waitNextBlockReady: + push {r0-r4, lr} + ldr r0, =CF_CARD_TIMEOUT + ldr r1, cf_waitFunctions_reg_cmd +1: + ldrb r2, [r1] + cmp r2, (CF_STS_READY | CF_STS_DSC | CF_STS_DRQ) + @ timeout expired + beq ready + subs r0, #1 + @ not ready + bne 1b + + @ timeout expired, return 0 + pop {r0-r4, pc} + +ready: + movs r0, #1 + pop {r0-r4, pc} + +.balign 4 +.global cf_waitFunctions_reg_cmd +cf_waitFunctions_reg_cmd: + .word 0 diff --git a/arm9/source/patches/platform/compactflash-common/ICompactFlashLockUnlockPatchCode.h b/arm9/source/patches/platform/compactflash-common/ICompactFlashLockUnlockPatchCode.h new file mode 100644 index 0000000..ca8ac67 --- /dev/null +++ b/arm9/source/patches/platform/compactflash-common/ICompactFlashLockUnlockPatchCode.h @@ -0,0 +1,13 @@ +#pragma once + +/// @brief Interface for patch code implementing Compact Flash carts lock/unlock routines +class ICompactFlashLockUnlockPatchCode +{ +protected: + ICompactFlashLockUnlockPatchCode() { } + +public: + /// @brief Gets a pointer to the SD write function in the patch code. + /// @return The pointer to the SD write function. + virtual const void* GetLockUnlockFunction() const = 0; +}; diff --git a/arm9/source/patches/platform/m3cf/M3CFLoaderPlatform.cpp b/arm9/source/patches/platform/m3cf/M3CFLoaderPlatform.cpp new file mode 100644 index 0000000..a291112 --- /dev/null +++ b/arm9/source/patches/platform/m3cf/M3CFLoaderPlatform.cpp @@ -0,0 +1,41 @@ +#include "common.h" +#include "M3CFLoaderPlatform.h" + +#define M3_MODE_ROM 8 +#define M3_MODE_MEDIA 6 + +static u16 volatileReadHalfword(u32 addr) +{ + return *((vu16*)addr); +} + +static void changeM3Mode(u32 mode) +{ + volatileReadHalfword(0x08e00002); + volatileReadHalfword(0x0800000e); + volatileReadHalfword(0x08801FFC); + volatileReadHalfword(0x0800104A); + volatileReadHalfword(0x08800612); + volatileReadHalfword(0x08000000); + volatileReadHalfword(0x08801B66); + volatileReadHalfword(0x08800000 + mode); + volatileReadHalfword(0x0800080E); + volatileReadHalfword(0x08000000); + + if(mode == M3_MODE_ROM) + { + volatileReadHalfword(0x080001E4); + volatileReadHalfword(0x080001E4); + volatileReadHalfword(0x08000188); + volatileReadHalfword(0x08000188); + } + else + { + volatileReadHalfword(0x09000000); + } +} + +void M3CFLoaderPlatform::SetCardLocked(bool locked) const +{ + changeM3Mode(locked ? M3_MODE_ROM : M3_MODE_MEDIA); +} diff --git a/arm9/source/patches/platform/m3cf/M3CFLoaderPlatform.h b/arm9/source/patches/platform/m3cf/M3CFLoaderPlatform.h new file mode 100644 index 0000000..502025c --- /dev/null +++ b/arm9/source/patches/platform/m3cf/M3CFLoaderPlatform.h @@ -0,0 +1,37 @@ +#pragma once +#include "common.h" +#include "M3CFLockUnlockCardPatchCode.h" +#include "../compactflash-common/CompactFlashCommonLoaderPlatform.h" + +/// @brief Implementation of LoaderPlatform for the DATEL line of flashcarts +class M3CFLoaderPlatform : public CompactFlashCommonLoaderPlatform +{ +protected: + const ICompactFlashLockUnlockPatchCode* CreateLockingPatchCode( + PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override + { + return patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new M3CFLockUnlockCardPatchCode(patchHeap); + }); + } + + void SetCardLocked(bool locked) const override; + + const cf_registers_t& GetCfRegisters() const override + { + static const cf_registers_t registers + { + .data = (vu16*)0x08800000, + .altStatus = (vu16*)0x080C0000, + .command = (vu16*)0x088E0000, + .error = (vu16*)0x08820000, + .sectorCount = (vu16*)0x08840000, + .lba1 = (vu16*)0x08860000, + .lba2 = (vu16*)0x08880000, + .lba3 = (vu16*)0x088A0000, + .lba4 = (vu16*)0x088C0000, + }; + return registers; + } +}; diff --git a/arm9/source/patches/platform/m3cf/M3CFLockUnlockCardPatchCode.h b/arm9/source/patches/platform/m3cf/M3CFLockUnlockCardPatchCode.h new file mode 100644 index 0000000..a9ad40c --- /dev/null +++ b/arm9/source/patches/platform/m3cf/M3CFLockUnlockCardPatchCode.h @@ -0,0 +1,20 @@ +#pragma once +#include "sections.h" +#include "patches/PatchCode.h" +#include "../compactflash-common/ICompactFlashLockUnlockPatchCode.h" + +DEFINE_SECTION_SYMBOLS(m3cf_lock_unlock); + +extern "C" void m3cf_lockUnlockCard(bool lock); + +class M3CFLockUnlockCardPatchCode : public PatchCode, public ICompactFlashLockUnlockPatchCode +{ +public: + explicit M3CFLockUnlockCardPatchCode(PatchHeap& patchHeap) + : PatchCode(SECTION_START(m3cf_lock_unlock), SECTION_SIZE(m3cf_lock_unlock), patchHeap) { } + + const void* GetLockUnlockFunction() const override + { + return GetAddressAtTarget((void*)m3cf_lockUnlockCard); + } +}; diff --git a/arm9/source/patches/platform/m3cf/M3CFLockUnlockCardPatchCode.s b/arm9/source/patches/platform/m3cf/M3CFLockUnlockCardPatchCode.s new file mode 100644 index 0000000..95dbbc5 --- /dev/null +++ b/arm9/source/patches/platform/m3cf/M3CFLockUnlockCardPatchCode.s @@ -0,0 +1,92 @@ +.cpu arm7tdmi +.syntax unified +.thumb + +.section "m3cf_lock_unlock", "ax" + +.equ M3_MODE_ROM, 8 +.equ M3_MODE_MEDIA, 6 + +.global m3cf_lockUnlockCard +.type m3cf_lockUnlockCard, %function +m3cf_lockUnlockCard: + cmp r0, #1 + bne unlock + movs r0, #0x08 +M3_changeMode: + push {r1-r7, lr} + @ READ_REG #0x08e00002 + ldr r1, =#0x08e00002 + ldrh r1, [r1] + + adr r1, M3_regs + @ r2 has #0x08801ffc + @ r3 has #0x0800104a + @ r4 has #0x08800612 + @ r5 has #0x08801b66 + @ r6 has #0x0800080e + @ r7 has #0x09000000 + ldm r1!, {r2-r7} + + @ load 0x08000000 + movs r1, #1 + lsls r1, #27 + + @ READ_REG #0x0800000e + ldrh r1, [r1, #0x0E] + + @ READ_REG #0x08801ffc + ldrh r1, [r2] + @ READ_REG #0x0800104a + ldrh r1, [r3] + @ READ_REG #0x08800612 + ldrh r1, [r4] + + @ READ_REG #0x08000000 + @ load 0x08000000 + movs r2, #1 + lsls r2, #27 + ldrh r1, [r2] + + @ READ_REG #0x08801b66 + ldrh r1, [r5] + + @ READ_REG #0x08800000 + r0 + @ ldr r1, =#0x08800000 + movs r1, 0x88 + lsls r1, #20 + ldrh r1, [r1, r0] + + @ READ_REG #0x0800080e + ldrh r1, [r6] + + @ READ_REG #0x08000000 + ldrh r1, [r2] + + cmp r0, M3_MODE_ROM + beq lastRomRead + @ READ_REG #0x09000000 + ldrh r1, [r7] + pop {r1-r7, pc} + +lastRomRead: + @ READ_REG #0x080001e4 + ldr r1, =#0x080001e4 + ldrh r3, [r1] + ldrh r3, [r1] + @ READ_REG #0x08000188 + subs r1, #0x5C + ldrh r3, [r1] + ldrh r3, [r1] + pop {r1-r7, pc} +unlock: + movs r0, #0x06 + b M3_changeMode +.pool +M3_regs: + .word 0x08801ffc + .word 0x0800104a + .word 0x08800612 + .word 0x08801b66 + .word 0x0800080e + .word 0x09000000 diff --git a/arm9/source/patches/platform/mmcf/MMCFLoaderPlatform.h b/arm9/source/patches/platform/mmcf/MMCFLoaderPlatform.h new file mode 100644 index 0000000..4e18d6a --- /dev/null +++ b/arm9/source/patches/platform/mmcf/MMCFLoaderPlatform.h @@ -0,0 +1,27 @@ +#pragma once +#include "common.h" +#include "../compactflash-common/CompactFlashCommonLoaderPlatform.h" + +/// @brief Implementation of LoaderPlatform for the DATEL line of flashcarts +class MMCFLoaderPlatform : public CompactFlashCommonLoaderPlatform +{ +protected: + void SetCardLocked(bool locked) const override { } + + const cf_registers_t& GetCfRegisters() const override + { + static const cf_registers_t registers + { + .data = (vu16*)0x09000000, + .altStatus = (vu16*)0x088C0000, // should be this, untested, but unused + .command = (vu16*)0x080E0000, + .error = (vu16*)0x08020000, + .sectorCount = (vu16*)0x08040000, + .lba1 = (vu16*)0x08060000, + .lba2 = (vu16*)0x08080000, + .lba3 = (vu16*)0x080A0000, + .lba4 = (vu16*)0x080C0000, + }; + return registers; + } +}; diff --git a/arm9/source/patches/platform/mpcf/MPCFLoaderPlatform.h b/arm9/source/patches/platform/mpcf/MPCFLoaderPlatform.h new file mode 100644 index 0000000..f31a1fb --- /dev/null +++ b/arm9/source/patches/platform/mpcf/MPCFLoaderPlatform.h @@ -0,0 +1,27 @@ +#pragma once +#include "common.h" +#include "../compactflash-common/CompactFlashCommonLoaderPlatform.h" + +/// @brief Implementation of LoaderPlatform for the DATEL line of flashcarts +class MPCFLoaderPlatform : public CompactFlashCommonLoaderPlatform +{ +protected: + void SetCardLocked(bool locked) const override { } + + const cf_registers_t& GetCfRegisters() const override + { + static const cf_registers_t registers + { + .data = (vu16*)0x09000000, + .altStatus = (vu16*)0x098C0000, + .command = (vu16*)0x090E0000, + .error = (vu16*)0x09020000, + .sectorCount = (vu16*)0x09040000, + .lba1 = (vu16*)0x09060000, + .lba2 = (vu16*)0x09080000, + .lba3 = (vu16*)0x090A0000, + .lba4 = (vu16*)0x090C0000, + }; + return registers; + } +}; diff --git a/arm9/source/patches/platform/supercardcf/SuperCardCFLoaderPlatform.cpp b/arm9/source/patches/platform/supercardcf/SuperCardCFLoaderPlatform.cpp new file mode 100644 index 0000000..8f524df --- /dev/null +++ b/arm9/source/patches/platform/supercardcf/SuperCardCFLoaderPlatform.cpp @@ -0,0 +1,21 @@ +#include "common.h" +#include "SuperCardCFLoaderPlatform.h" + +#define SC_MODE_REG (*(vu16*)0x09FFFFFE) +#define SC_MODE_MAGIC 0xA55A + +#define SC_MODE_MEDIA 0x3 +#define SC_MODE_RAM_RO 0x1 + +static void changeSupercardMode(u8 mode) +{ + SC_MODE_REG = SC_MODE_MAGIC; + SC_MODE_REG = SC_MODE_MAGIC; + SC_MODE_REG = mode; + SC_MODE_REG = mode; +} + +void SuperCardCFLoaderPlatform::SetCardLocked(bool locked) const +{ + changeSupercardMode(locked ? SC_MODE_RAM_RO : SC_MODE_MEDIA); +} diff --git a/arm9/source/patches/platform/supercardcf/SuperCardCFLoaderPlatform.h b/arm9/source/patches/platform/supercardcf/SuperCardCFLoaderPlatform.h new file mode 100644 index 0000000..571bbf1 --- /dev/null +++ b/arm9/source/patches/platform/supercardcf/SuperCardCFLoaderPlatform.h @@ -0,0 +1,37 @@ +#pragma once +#include "common.h" +#include "SuperCardCFLockUnlockCardPatchCode.h" +#include "../compactflash-common/CompactFlashCommonLoaderPlatform.h" + +/// @brief Implementation of LoaderPlatform for the DATEL line of flashcarts +class SuperCardCFLoaderPlatform : public CompactFlashCommonLoaderPlatform +{ +protected: + const ICompactFlashLockUnlockPatchCode* CreateLockingPatchCode( + PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override + { + return patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new SuperCardCFLockUnlockCardPatchCode(patchHeap); + }); + } + + void SetCardLocked(bool locked) const override; + + const cf_registers_t& GetCfRegisters() const override + { + static const cf_registers_t registers + { + .data = (vu16*)0x09000000, + .altStatus = (vu16*)0x098C0000, + .command = (vu16*)0x090E0000, + .error = (vu16*)0x09020000, + .sectorCount = (vu16*)0x09040000, + .lba1 = (vu16*)0x09060000, + .lba2 = (vu16*)0x09080000, + .lba3 = (vu16*)0x090A0000, + .lba4 = (vu16*)0x090C0000, + }; + return registers; + } +}; diff --git a/arm9/source/patches/platform/supercardcf/SuperCardCFLockUnlockCardPatchCode.h b/arm9/source/patches/platform/supercardcf/SuperCardCFLockUnlockCardPatchCode.h new file mode 100644 index 0000000..dfcf23b --- /dev/null +++ b/arm9/source/patches/platform/supercardcf/SuperCardCFLockUnlockCardPatchCode.h @@ -0,0 +1,20 @@ +#pragma once +#include "sections.h" +#include "patches/PatchCode.h" +#include "../compactflash-common/ICompactFlashLockUnlockPatchCode.h" + +DEFINE_SECTION_SYMBOLS(sccf_lock_unlock); + +extern "C" void sccf_lockUnlockCard(bool lock); + +class SuperCardCFLockUnlockCardPatchCode : public PatchCode, public ICompactFlashLockUnlockPatchCode +{ +public: + explicit SuperCardCFLockUnlockCardPatchCode(PatchHeap& patchHeap) + : PatchCode(SECTION_START(sccf_lock_unlock), SECTION_SIZE(sccf_lock_unlock), patchHeap) { } + + const void* GetLockUnlockFunction() const override + { + return GetAddressAtTarget((void*)sccf_lockUnlockCard); + } +}; diff --git a/arm9/source/patches/platform/supercardcf/SuperCardCFLockUnlockCardPatchCode.s b/arm9/source/patches/platform/supercardcf/SuperCardCFLockUnlockCardPatchCode.s new file mode 100644 index 0000000..cdfc29f --- /dev/null +++ b/arm9/source/patches/platform/supercardcf/SuperCardCFLockUnlockCardPatchCode.s @@ -0,0 +1,26 @@ +.cpu arm7tdmi +.syntax unified +.thumb +.section "sccf_lock_unlock", "ax" + +.global sccf_lockUnlockCard +.type sccf_lockUnlockCard, %function +sccf_lockUnlockCard: + cmp r0, #1 + bne unlock +@ void sc_change_mode(uint16_t mode); +sccf_changeMode: + push {r1-r3, lr} + ldr r2,= 0x09FFFFFE + ldr r3,= 0xA55A + strh r3, [r2] + strh r3, [r2] + strh r0, [r2] + strh r0, [r2] + pop {r1-r3, pc} + +unlock: + movs r0, #3 + b sccf_changeMode + +.pool