diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index dddab13..297b51e 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -27,6 +27,7 @@ jobs:
"M3DS",
"R4",
"R4iDSN",
+ "STARGATE",
"SUPERCARD"
]
runs-on: ubuntu-latest
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ef89cf6..a7d1c55 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -20,6 +20,7 @@ jobs:
"M3DS",
"R4",
"R4iDSN",
+ "STARGATE",
"SUPERCARD"
]
runs-on: ubuntu-latest
diff --git a/README.md b/README.md
index b4d8b47..f9adb17 100644
--- a/README.md
+++ b/README.md
@@ -35,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.
diff --git a/arm9/source/patches/platform/LoaderPlatformFactory.cpp b/arm9/source/patches/platform/LoaderPlatformFactory.cpp
index 53c2f0a..a1297a5 100644
--- a/arm9/source/patches/platform/LoaderPlatformFactory.cpp
+++ b/arm9/source/patches/platform/LoaderPlatformFactory.cpp
@@ -14,6 +14,7 @@
#include "patches/platform/supercard/SuperCardLoaderPlatform.h"
#include "patches/platform/ezp/EZPLoaderPlatform.h"
#include "patches/platform/datel/DatelLoaderPlatform.h"
+#include "patches/platform/stargate/StargateLoaderPlatform.h"
#include "LoaderPlatformFactory.h"
LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const
@@ -46,6 +47,8 @@ LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const
return new EZPLoaderPlatform();
#elif defined(PICO_LOADER_TARGET_DATEL)
return new DatelLoaderPlatform();
+#elif defined(PICO_LOADER_TARGET_STARGATE)
+ return new StargateLoaderPlatform();
#else
#error "No loader platform defined"
return nullptr;
diff --git a/arm9/source/patches/platform/stargate/StargateLoaderPlatform.h b/arm9/source/patches/platform/stargate/StargateLoaderPlatform.h
new file mode 100644
index 0000000..008528c
--- /dev/null
+++ b/arm9/source/patches/platform/stargate/StargateLoaderPlatform.h
@@ -0,0 +1,40 @@
+#pragma once
+#include "common.h"
+#include "../LoaderPlatform.h"
+#include "stargateReadSdAsm.h"
+#include "stargateReadSdDmaAsm.h"
+#include "stargateWriteSdAsm.h"
+
+/// @brief Implementation of LoaderPlatform for the Stargate 3DS flashcard
+class StargateLoaderPlatform : public LoaderPlatform
+{
+public:
+ const SdReadPatchCode* CreateSdReadPatchCode(
+ PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override
+ {
+ return patchCodeCollection.GetOrAddSharedPatchCode([&]
+ {
+ return new StargateReadSdPatchCode(patchHeap);
+ });
+ }
+
+ const SdReadDmaPatchCode* CreateSdReadDmaPatchCode(PatchCodeCollection& patchCodeCollection,
+ PatchHeap& patchHeap, const void* miiCardDmaCopy32Ptr) const override
+ {
+ return patchCodeCollection.AddUniquePatchCode(
+ patchHeap, miiCardDmaCopy32Ptr);
+ }
+
+ const SdWritePatchCode* CreateSdWritePatchCode(
+ PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override
+ {
+ return patchCodeCollection.GetOrAddSharedPatchCode([&]
+ {
+ return new StargateWriteSdPatchCode(patchHeap);
+ });
+ }
+
+ LoaderPlatformType GetPlatformType() const override { return LoaderPlatformType::Slot1; }
+
+ bool HasDmaSdReads() const override { return true; }
+};
diff --git a/arm9/source/patches/platform/stargate/stargateReadSdAsm.h b/arm9/source/patches/platform/stargate/stargateReadSdAsm.h
new file mode 100644
index 0000000..01a597f
--- /dev/null
+++ b/arm9/source/patches/platform/stargate/stargateReadSdAsm.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "sections.h"
+#include "../SdReadPatchCode.h"
+
+DEFINE_SECTION_SYMBOLS(stargate_readsd);
+
+extern "C" void stargate_readSd(u32 srcSector, void* dst, u32 sectorCount);
+
+class StargateReadSdPatchCode : public SdReadPatchCode
+{
+public:
+ explicit StargateReadSdPatchCode(PatchHeap& patchHeap)
+ : SdReadPatchCode(SECTION_START(stargate_readsd), SECTION_SIZE(stargate_readsd), patchHeap) { }
+
+ const SdReadFunc GetSdReadFunction() const override
+ {
+ return (const SdReadFunc)GetAddressAtTarget((void*)stargate_readSd);
+ }
+};
diff --git a/arm9/source/patches/platform/stargate/stargateReadSdAsm.s b/arm9/source/patches/platform/stargate/stargateReadSdAsm.s
new file mode 100644
index 0000000..63df8df
--- /dev/null
+++ b/arm9/source/patches/platform/stargate/stargateReadSdAsm.s
@@ -0,0 +1,83 @@
+.cpu arm7tdmi
+.section "stargate_readsd", "ax"
+.syntax unified
+.thumb
+
+// r0 = src sector
+// r1 = dst
+// r2 = sector count
+.global stargate_readSd
+.type stargate_readSd, %function
+stargate_readSd:
+ push {r4-r7,lr}
+
+ ldr r4, =0x040001A0
+ movs r3, #0x80
+ strb r3, [r4,#1]
+
+sector_loop:
+ // request sd read for sector 0xaabbccdd
+ // B9 aa bb cc dd 00 00 00
+ movs r3, #0xB9
+ strb r3, [r4,#0x8]
+ lsrs r3, r0, #24
+ strb r3, [r4,#0x9]
+ lsrs r3, r0, #16
+ strb r3, [r4,#0xA]
+ lsrs r3, r0, #8
+ strb r3, [r4,#0xB]
+ lsls r7, r0, #24
+ lsrs r7, r7, #24 // r7 = dd
+ str r7, [r4,#0xC] // storing as little-endian puts the bottom 8 bits as first byte
+
+ ldr r6, =0x04100010
+
+B9_poll_loop:
+ ldr r3, =0xA7406000
+ str r3, [r4,#4]
+
+B9_poll_transfer_loop:
+ ldrb r3, [r4,#6]
+ lsrs r3, r3, #8
+ bcc B9_poll_check_transfer_end
+ ldr r5, [r6]
+
+B9_poll_check_transfer_end:
+ ldrb r3, [r4,#7]
+ lsrs r3, r3, #8
+ bcs B9_poll_transfer_loop
+
+ cmp r5, #0
+ bne B9_poll_loop
+
+ movs r3, #0xBA
+ str r3, [r4,#0x8]
+ str r5, [r4,#0xC] // r5 is 0 here
+
+ ldr r3, =0xA1406000
+ str r3, [r4,#4]
+
+BA_data_loop:
+ ldrb r3, [r4,#6]
+ lsrs r3, r3, #8 // check if data is ready
+ bcc BA_data_loop_check_transfer_end // if not skip reading
+
+ ldr r3, [r6]
+ stmia r1!, {r3}
+
+BA_data_loop_check_transfer_end:
+ ldrb r3, [r4,#7]
+ lsrs r3, r3, #8 // check if transfer is done
+ bcs BA_data_loop
+
+ adds r0, #1
+ subs r2, #1
+ bne sector_loop
+
+ pop {r4-r7,pc}
+
+.balign 4
+
+.pool
+
+.end
\ No newline at end of file
diff --git a/arm9/source/patches/platform/stargate/stargateReadSdDmaAsm.h b/arm9/source/patches/platform/stargate/stargateReadSdDmaAsm.h
new file mode 100644
index 0000000..c09dfec
--- /dev/null
+++ b/arm9/source/patches/platform/stargate/stargateReadSdDmaAsm.h
@@ -0,0 +1,30 @@
+#pragma once
+#include "sections.h"
+#include "../SdReadDmaPatchCode.h"
+
+DEFINE_SECTION_SYMBOLS(stargate_readsddma);
+
+extern "C" void stargate_readSdDma(u32 srcSector, u32 previousSrcSector, u32 dmaChannel, void* dst);
+extern "C" void stargate_finishReadSdDma(void);
+
+extern u32 stargate_readSdDma_miiCardDmaCopy32Ptr;
+
+class StargateReadSdDmaPatchCode : public SdReadDmaPatchCode
+{
+public:
+ StargateReadSdDmaPatchCode(PatchHeap& patchHeap, const void* miiCardDmaCopy32Ptr)
+ : SdReadDmaPatchCode(SECTION_START(stargate_readsddma), SECTION_SIZE(stargate_readsddma), patchHeap)
+ {
+ stargate_readSdDma_miiCardDmaCopy32Ptr = (u32)miiCardDmaCopy32Ptr;
+ }
+
+ const SdReadDmaFunc GetSdReadDmaFunction() const override
+ {
+ return (const SdReadDmaFunc)GetAddressAtTarget((void*)stargate_readSdDma);
+ }
+
+ const SdReadDmaFinishFunc GetSdReadDmaFinishFunction() const override
+ {
+ return (const SdReadDmaFinishFunc)GetAddressAtTarget((void*)stargate_finishReadSdDma);
+ }
+};
diff --git a/arm9/source/patches/platform/stargate/stargateReadSdDmaAsm.s b/arm9/source/patches/platform/stargate/stargateReadSdDmaAsm.s
new file mode 100644
index 0000000..c7edc9f
--- /dev/null
+++ b/arm9/source/patches/platform/stargate/stargateReadSdDmaAsm.s
@@ -0,0 +1,94 @@
+.cpu arm946e-s
+.section "stargate_readsddma", "ax"
+.syntax unified
+.thumb
+
+// r0 = src sector
+// r1 = previous src sector
+// r2 = dma channel
+// r3 = dst
+.global stargate_readSdDma
+.type stargate_readSdDma, %function
+stargate_readSdDma:
+ push {r4-r7,lr}
+
+ ldr r4, =0x040001A0
+ movs r7, #0x80
+ strb r7, [r4,#0x1]
+
+sector_loop:
+ // request sd read for sector 0xaabbccdd
+ // B9 aa bb cc dd 00 00 00
+ movs r7, #0xB9
+ strb r7, [r4,#0x8]
+ lsrs r7, r0, #24
+ strb r7, [r4,#0x9]
+ lsrs r7, r0, #16
+ strb r7, [r4,#0xA]
+ lsrs r7, r0, #8
+ strb r7, [r4,#0xB]
+ lsls r7, r0, #24
+ lsrs r7, r7, #24 // r7 = dd
+ str r7, [r4,#0xC] // storing as little-endian puts the bottom 8 bits as first byte
+
+ ldr r1, =0x04100010
+
+B9_poll_loop:
+ ldr r7, =0xA7406000
+ str r7, [r4,#0x4]
+
+B9_poll_transfer_loop:
+ ldrb r7, [r4,#0x6]
+ lsrs r7, r7, #8
+ bcc B9_poll_check_transfer_end
+ ldr r5, [r1]
+
+B9_poll_check_transfer_end:
+ ldrb r7, [r4,#0x7]
+ lsrs r7, r7, #8
+ bcs B9_poll_transfer_loop
+
+ cmp r5, #0
+ bne B9_poll_loop
+
+ // Setup data read card command
+ movs r7, #0xBA
+ str r7, [r4,#0x8]
+ str r5, [r4,#0xC] // r5 is 0 here
+
+readDataWithDma:
+ movs r0, r2 // DMA channel
+ movs r2, r3 // Destination
+ movs r3, #1
+ lsls r3, r3, #9 // (1 << 9) = 512 = count
+
+ ldr r6, stargate_readSdDma_miiCardDmaCopy32Ptr
+ blx r6
+
+BA_data_dma:
+ movs r7, #0xC0 // select rom mode, with irq
+ strb r7, [r4,#0x1]
+ ldr r7, =0xA1406000
+ str r7, [r4,#0x4]
+
+ pop {r4-r7,pc}
+
+.balign 4
+
+.global stargate_readSdDma_miiCardDmaCopy32Ptr
+stargate_readSdDma_miiCardDmaCopy32Ptr:
+ .word 0
+
+.pool
+
+.global stargate_finishReadSdDma
+.type stargate_finishReadSdDma, %function
+stargate_finishReadSdDma:
+ // No cleanup needed on this platform.
+ bx lr
+
+.balign 4
+
+.pool
+
+.end
diff --git a/arm9/source/patches/platform/stargate/stargateWriteSdAsm.h b/arm9/source/patches/platform/stargate/stargateWriteSdAsm.h
new file mode 100644
index 0000000..70560e9
--- /dev/null
+++ b/arm9/source/patches/platform/stargate/stargateWriteSdAsm.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "sections.h"
+#include "../SdWritePatchCode.h"
+
+DEFINE_SECTION_SYMBOLS(stargate_writesd);
+
+extern "C" void stargate_writeSd(u32 dstSector, const void* src, u32 sectorCount);
+
+class StargateWriteSdPatchCode : public SdWritePatchCode
+{
+public:
+ explicit StargateWriteSdPatchCode(PatchHeap& patchHeap)
+ : SdWritePatchCode(SECTION_START(stargate_writesd), SECTION_SIZE(stargate_writesd), patchHeap) { }
+
+ const SdWriteFunc GetSdWriteFunction() const override
+ {
+ return (const SdWriteFunc)GetAddressAtTarget((void*)stargate_writeSd);
+ }
+};
diff --git a/arm9/source/patches/platform/stargate/stargateWriteSdAsm.s b/arm9/source/patches/platform/stargate/stargateWriteSdAsm.s
new file mode 100644
index 0000000..3b377b2
--- /dev/null
+++ b/arm9/source/patches/platform/stargate/stargateWriteSdAsm.s
@@ -0,0 +1,82 @@
+.cpu arm7tdmi
+.section "stargate_writesd", "ax"
+.syntax unified
+.thumb
+
+// r0 = dst sector
+// r1 = src
+// r2 = sector count
+.global stargate_writeSd
+.type stargate_writeSd, %function
+stargate_writeSd:
+ push {r4-r7,lr}
+
+ ldr r4, =0x040001A0
+ movs r3, #0x80
+ strb r3, [r4,#1]
+
+sector_loop:
+ // write at sd sector 0xaabbccdd
+ // BB aa bb cc dd 00 00 00
+ movs r3, #0xBB
+ strb r3, [r4,#0x8]
+ lsrs r3, r0, #24
+ strb r3, [r4,#0x9]
+ lsrs r3, r0, #16
+ strb r3, [r4,#0xA]
+ lsrs r3, r0, #8
+ strb r3, [r4,#0xB]
+ lsls r7, r0, #24
+ lsrs r7, r7, #24 // r7 = dd
+ str r7, [r4,#0xC] // storing as little-endian puts the bottom 8 bits as first byte
+
+ ldr r6, =0x04100010
+
+ ldr r3, =0xE1400000
+ str r3, [r4,#4]
+
+BB_data_loop:
+ ldrb r3, [r4,#6]
+ lsrs r3, r3, #8 // check if ready to write
+ bcc BB_data_loop_check_transfer_end // if not skip reading
+
+ ldmia r1!, {r3}
+ str r3, [r6]
+
+BB_data_loop_check_transfer_end:
+ ldrb r3, [r4,#7]
+ lsrs r3, r3, #8 // check if transfer is done
+ bcs BB_data_loop
+
+ movs r3, #0xBC
+ strb r3, [r4,#0x8]
+
+BC_poll_loop:
+ ldr r3, =0xA7406000
+ str r3, [r4,#4]
+
+BC_poll_transfer_loop:
+ ldrb r3, [r4,#6]
+ lsrs r3, r3, #8
+ bcc BC_poll_check_transfer_end
+ ldr r5, [r6]
+
+BC_poll_check_transfer_end:
+ ldrb r3, [r4,#7]
+ lsrs r3, r3, #8
+ bcs BC_poll_transfer_loop
+
+ cmp r5, #0
+ bne BC_poll_loop
+
+ adds r0, #1
+ subs r2, #1
+ bne sector_loop
+
+ pop {r4-r7,pc}
+
+.balign 4
+
+.pool
+
+.end
\ No newline at end of file