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)
This commit is contained in:
Edoardo Lolletti
2026-01-10 23:00:39 +01:00
committed by GitHub
parent eac8f7e734
commit 6a97b677a7
22 changed files with 945 additions and 3 deletions

View File

@@ -27,7 +27,11 @@ jobs:
"R4", "R4",
"R4iDSN", "R4iDSN",
"STARGATE", "STARGATE",
"SUPERCARD" "SUPERCARD",
"SUPERCARDCF",
"M3CF",
"MMCF",
"MPCF"
] ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: skylyrac/blocksds:slim-v1.13.1 container: skylyrac/blocksds:slim-v1.13.1

View File

@@ -21,7 +21,11 @@ jobs:
"R4", "R4",
"R4iDSN", "R4iDSN",
"STARGATE", "STARGATE",
"SUPERCARD" "SUPERCARD",
"SUPERCARDCF",
"M3CF",
"MMCF",
"MPCF"
] ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: skylyrac/blocksds:slim-v1.13.1 container: skylyrac/blocksds:slim-v1.13.1

View File

@@ -32,11 +32,15 @@ Note that there can be some game compatibility differences between different pla
| G003 | M3i Zero (GMP-Z003) | ✅ | | G003 | M3i Zero (GMP-Z003) | ✅ |
| ISNITRO | Supports the IS-NITRO-EMULATOR through agb semihosting. | ❌ | | ISNITRO | Supports the IS-NITRO-EMULATOR through agb semihosting. | ❌ |
| M3DS | M3 DS Real, M3i Zero, iTouchDS, r4rts.com, r4isdhc.com RTS (black) | ❌ | | 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. | ❌ | | 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 | ❌ | | R4 | Original R4DS (non-SDHC), M3 DS Simply | ❌ |
| R4iDSN | r4idsn.com | ❌ | | R4iDSN | r4idsn.com | ❌ |
| STARGATE | Stargate 3DS DS-mode | ✅ | | 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.<br> 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>
Note that there are still SDK versions and variants for which Pico Loader does not yet support DMA card reads. Note that there are still SDK versions and variants for which Pico Loader does not yet support DMA card reads.

View File

@@ -15,6 +15,10 @@
#include "patches/platform/ezp/EzpLoaderPlatform.h" #include "patches/platform/ezp/EzpLoaderPlatform.h"
#include "patches/platform/datel/DatelLoaderPlatform.h" #include "patches/platform/datel/DatelLoaderPlatform.h"
#include "patches/platform/stargate/StargateLoaderPlatform.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" #include "LoaderPlatformFactory.h"
LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const
@@ -49,6 +53,14 @@ LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const
return new DatelLoaderPlatform(); return new DatelLoaderPlatform();
#elif defined(PICO_LOADER_TARGET_STARGATE) #elif defined(PICO_LOADER_TARGET_STARGATE)
return new StargateLoaderPlatform(); 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 #else
#error "No loader platform defined" #error "No loader platform defined"
return nullptr; return nullptr;

View File

@@ -0,0 +1,66 @@
#include "common.h"
#include <libtwl/mem/memExtern.h>
#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;
}

View File

@@ -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();
};

View File

@@ -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);
}
};

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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);
}
};

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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;
}
};

View File

@@ -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);
}
};

View File

@@ -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

View File

@@ -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;
}
};

View File

@@ -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;
}
};

View File

@@ -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);
}

View File

@@ -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;
}
};

View File

@@ -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);
}
};

View File

@@ -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