Skip to content

Instantly share code, notes, and snippets.

@profi200
Created July 16, 2022 15:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save profi200/4c3a3f2882f0c64b421187c0b22fa2bf to your computer and use it in GitHub Desktop.
Save profi200/4c3a3f2882f0c64b421187c0b22fa2bf to your computer and use it in GitHub Desktop.
#include <string.h>
#include "mem_map.h"
#include "types.h"
#include "util.h"
#include "arm9/hardware/gamecard.h"
#include "arm9/ncch.h"
#include "arm9/hardware/crypto.h"
#include "arm9/hardware/timer.h"
// TODO: This belongs in cfg9.h.
#define CFG_REGS_BASE (IO_MEM_ARM9_ONLY)
#define REG_CFG9_CARDCTL *((vu16*)(CFG_REGS_BASE + 0x0000C))
#define REG_CFG9_CARDSTATUS *((vu8* )(CFG_REGS_BASE + 0x00010))
#define REG_CFG9_CARDCYCLES0 *((vu16*)(CFG_REGS_BASE + 0x00012))
#define REG_CFG9_CARDCYCLES1 *((vu16*)(CFG_REGS_BASE + 0x00014))
#define CTRCARD_REGS_BASE (IO_MEM_ARM9_ONLY + 0x4000)
#define REG_CTRCARDCNT *((vu32*)(CTRCARD_REGS_BASE + 0x00))
#define REG_CTRCARDBLKCNT *((vu32*)(CTRCARD_REGS_BASE + 0x04))
#define REG_CTRCARDSECCNT *((vu32*)(CTRCARD_REGS_BASE + 0x08))
#define REG_CTRCARDSECSEED *((vu32*)(CTRCARD_REGS_BASE + 0x10))
#define REGs_CTRCARDCMD ((vu32*)(CTRCARD_REGS_BASE + 0x20))
#define REG_CTRCARDFIFO *((vu32*)(CTRCARD_REGS_BASE + 0x30))
#define NTRCARD_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x64000)
#define REG_NTRCARDMCNT *((vu16*)(NTRCARD_REGS_BASE + 0x00))
#define REG_NTRCARDMDATA *((vu16*)(NTRCARD_REGS_BASE + 0x02))
#define REG_NTRCARDROMCNT *((vu32*)(NTRCARD_REGS_BASE + 0x04))
#define REGs_NTRCARDCMD ((vu32*)(NTRCARD_REGS_BASE + 0x08))
#define REG_NTRCARDSEEDX_L *((vu32*)(NTRCARD_REGS_BASE + 0x10))
#define REG_NTRCARDSEEDY_L *((vu32*)(NTRCARD_REGS_BASE + 0x14))
#define REG_NTRCARDSEEDX_H *((vu16*)(NTRCARD_REGS_BASE + 0x18))
#define REG_NTRCARDSEEDY_H *((vu16*)(NTRCARD_REGS_BASE + 0x1A))
#define REG_NTRCARDFIFO *((vu32*)(NTRCARD_REGS_BASE + 0x1C))
static u32 chipId, cardType;
static NCCH_header ctrcardHeader;
static u32 cmdRand1, cmdRand2;
static u32 readCount;
static bool ctrcardSecureInit(u32 cmdBuf[4]);
void resetCardslot(void)
{
REG_CFG9_CARDCYCLES0 = 0x1988;
REG_CFG9_CARDCYCLES1 = 0x264C;
// boot9 waits here. Unnecessary?
REG_CFG9_CARDSTATUS = 3u<<2; // Request power off
while(REG_CFG9_CARDSTATUS != 0); // Aotomatically changes to 0 (off)
TIMER_sleep(1);
REG_CFG9_CARDSTATUS = 1u<<2; // Prepare power on
TIMER_sleep(10);
REG_CFG9_CARDSTATUS = 2u<<2; // Power on
TIMER_sleep(27);
// Switch to NTRCARD controller.
REG_CFG9_CARDCTL = 0; // Select NTRCARD controller, eject IRQ off?
REG_NTRCARDMCNT = NTRCARD_CR1_ENABLE | NTRCARD_CR1_IRQ;
REG_NTRCARDROMCNT = 0x20000000;
TIMER_sleep(120);
}
bool gamecardInit(void)
{
// No gamecard inserted.
if(REG_CFG9_CARDSTATUS & 1) return false;
readCount = 0;
resetCardslot();
u32 cmdBuf[4] = {0};
cmdBuf[0] = 0x9F000000; // Reset cmd
ntrcardCommand(cmdBuf, 0x2000, NTRCARD_CLK_SLOW | NTRCARD_DELAY1(0x1FFF) | NTRCARD_DELAY2(0x18), NULL);
// No idea what this is. Hardcoded in Process9.
static const u32 unkGarbageCmd[2] = {0x71C93FE9, 0xBB0A3B18};
ntrcardCommand(unkGarbageCmd, 0, NTRCARD_CLK_SLOW | NTRCARD_DELAY1(0x1FFF) | NTRCARD_DELAY2(0x18), NULL);
// Send the get chip ID cmd twice like Process9.
cmdBuf[0] = 0x90000000; // Get chip ID cmd
ntrcardCommand(cmdBuf, 4, NTRCARD_CLK_SLOW | NTRCARD_DELAY1(0x1FFF) | NTRCARD_DELAY2(0x18), &chipId);
ntrcardCommand(cmdBuf, 4, NTRCARD_CLK_SLOW | NTRCARD_DELAY1(0x1FFF) | NTRCARD_DELAY2(0x18), NULL);
if(chipId & 0x10000000)
{
cmdBuf[0] = 0xA0000000; // Get card type cmd
ntrcardCommand(cmdBuf, 4, 0, &cardType);
cmdBuf[0] = 0x3E000000; // Enter 16 byte mode cmd
ntrcardCommand(cmdBuf, 0, 0, NULL);
// Switch to CTRCARD controller.
REG_CTRCARDCNT = CTRCARD_nRESET;
REG_CFG9_CARDCTL = 2;
cmdBuf[0] = 0x82000000; // Read header cmd
ctrcardCommand(cmdBuf, 0x200, 1, 0x704802C, &ctrcardHeader); // P9: 0x9004802C 24-26 unknown
// Check if the header is ok.
if(memcmp(&ctrcardHeader.magic, "NCCH", 4) != 0) return false;
// The secure init function sets the cmdRand* words in cmdBuf for us.
if(!ctrcardSecureInit(cmdBuf)) return false;
u32 chipIdTest, cardTypeTest;
cmdBuf[0] = 0xA2000000; // Get secure chip ID cmd
ctrcardCommand(cmdBuf, 4, 1, 0x701002C, &chipIdTest); // P9: 0x9001002C 24-26 unknown
cmdBuf[0] = 0xA3000000; // Get secure card type cmd
ctrcardCommand(cmdBuf, 4, 1, 0x701002C, &cardTypeTest); // P9: 0x9001002C 24-26 unknown
if(chipIdTest == chipId && cardTypeTest == cardType)
{
cmdBuf[0] = 0xC5000000; // Check status cmd
ctrcardCommand(cmdBuf, 0, 1, 0x100002C, NULL); // P9: 0x9000002C, 24-26 unknown
}
cmdBuf[0] = 0xA2000000; // Get secure chip ID cmd
for(u32 i = 0; i < 5; i++)
{
ctrcardCommand(cmdBuf, 4, 1, 0x701002C, NULL); // P9: 0x9001002C 24-26 unknown
}
}
return true;
}
u32 getChipId(void)
{
return chipId;
}
u32 getCardType(void)
{
return cardType;
}
//////////////////////////////////
// NTRCARD //
//////////////////////////////////
void ntrcardCommand(const u32 command[2], u32 pageSize, u32 latency, void* buffer)
{
REG_NTRCARDMCNT = NTRCARD_CR1_ENABLE;
REGs_NTRCARDCMD[0] = swap32(command[0]);
REGs_NTRCARDCMD[1] = swap32(command[1]);
pageSize -= pageSize & 3; // align to 4 byte
u32 pageParam = NTRCARD_PAGESIZE_4K;
u32 transferLength = 4096;
// make zero read and 4 byte read a little special for timing optimization(and 512 too)
switch (pageSize) {
case 0:
transferLength = 0;
pageParam = NTRCARD_PAGESIZE_0;
break;
case 4:
transferLength = 4;
pageParam = NTRCARD_PAGESIZE_4;
break;
case 512:
transferLength = 512;
pageParam = NTRCARD_PAGESIZE_512;
break;
case 8192:
transferLength = 8192;
pageParam = NTRCARD_PAGESIZE_8K;
break;
default:
break; //Using 4K pagesize and transfer length by default
}
// go
REG_NTRCARDROMCNT = 0x10000000;
REG_NTRCARDROMCNT = NTRKEY_PARAM | NTRCARD_ACTIVATE | NTRCARD_nRESET | pageParam | latency;
u8 * pbuf = (u8 *)buffer;
u32 * pbuf32 = (u32 * )buffer;
bool useBuf = ( NULL != pbuf );
bool useBuf32 = (useBuf && (0 == (3 & ((u32)buffer))));
u32 count = 0;
u32 cardCtrl = REG_NTRCARDROMCNT;
if(useBuf32)
{
while( (cardCtrl & NTRCARD_BUSY) && count < pageSize)
{
cardCtrl = REG_NTRCARDROMCNT;
if( cardCtrl & NTRCARD_DATA_READY ) {
u32 data = REG_NTRCARDFIFO;
*pbuf32++ = data;
count += 4;
}
}
}
else if(useBuf)
{
while( (cardCtrl & NTRCARD_BUSY) && count < pageSize)
{
cardCtrl = REG_NTRCARDROMCNT;
if( cardCtrl & NTRCARD_DATA_READY ) {
u32 data = REG_NTRCARDFIFO;
pbuf[0] = (unsigned char) (data >> 0);
pbuf[1] = (unsigned char) (data >> 8);
pbuf[2] = (unsigned char) (data >> 16);
pbuf[3] = (unsigned char) (data >> 24);
pbuf += sizeof (unsigned int);
count += 4;
}
}
}
else
{
while( (cardCtrl & NTRCARD_BUSY) && count < pageSize)
{
cardCtrl = REG_NTRCARDROMCNT;
if( cardCtrl & NTRCARD_DATA_READY ) {
u32 data = REG_NTRCARDFIFO;
(void)data;
count += 4;
}
}
}
// if read is not finished, ds will not pull ROM CS to high, we pull it high manually
if( count != transferLength ) {
// MUST wait for next data ready,
// if ds pull ROM CS to high during 4 byte data transfer, something will mess up
// so we have to wait next data ready
do { cardCtrl = REG_NTRCARDROMCNT; } while(!(cardCtrl & NTRCARD_DATA_READY));
// and this tiny delay is necessary
//ioAK2Delay(33);
// pull ROM CS high
REG_NTRCARDROMCNT = 0x10000000;
REG_NTRCARDROMCNT = NTRKEY_PARAM | NTRCARD_ACTIVATE | NTRCARD_nRESET; // | 0 | 0x0000;
}
// wait rom cs high
do { cardCtrl = REG_NTRCARDROMCNT; } while( cardCtrl & NTRCARD_BUSY );
//lastCmd[0] = command[0];lastCmd[1] = command[1];
}
//////////////////////////////////
// CTRCARD //
//////////////////////////////////
void ctrcardCommand(const u32 command[4], u32 pageSize, u32 blocks, u32 latency, void* buffer)
{
REGs_CTRCARDCMD[0] = command[3];
REGs_CTRCARDCMD[1] = command[2];
REGs_CTRCARDCMD[2] = command[1];
REGs_CTRCARDCMD[3] = command[0];
//Make sure this never happens
if(blocks == 0) blocks = 1;
pageSize -= pageSize & 3; // align to 4 byte
u32 pageParam = CTRCARD_PAGESIZE_4K;
u32 transferLength = 4096;
// make zero read and 4 byte read a little special for timing optimization(and 512 too)
switch(pageSize) {
case 0:
transferLength = 0;
pageParam = CTRCARD_PAGESIZE_0;
break;
case 4:
transferLength = 4;
pageParam = CTRCARD_PAGESIZE_4;
break;
case 64:
transferLength = 64;
pageParam = CTRCARD_PAGESIZE_64;
break;
case 512:
transferLength = 512;
pageParam = CTRCARD_PAGESIZE_512;
break;
case 1024:
transferLength = 1024;
pageParam = CTRCARD_PAGESIZE_1K;
break;
case 2048:
transferLength = 2048;
pageParam = CTRCARD_PAGESIZE_2K;
break;
case 4096:
transferLength = 4096;
pageParam = CTRCARD_PAGESIZE_4K;
break;
default:
break; //Defaults already set
}
REG_CTRCARDBLKCNT = blocks - 1;
transferLength *= blocks;
// go
REG_CTRCARDCNT = 0x10000000;
REG_CTRCARDCNT = CTRCARD_ACTIVATE | CTRCARD_nRESET | pageParam | latency;
u8 * pbuf = (u8 *)buffer;
u32 * pbuf32 = (u32 * )buffer;
bool useBuf = ( NULL != pbuf );
bool useBuf32 = (useBuf && (0 == (3 & ((u32)buffer))));
u32 count = 0;
u32 cardCtrl = REG_CTRCARDCNT;
if(useBuf32)
{
while( (cardCtrl & CTRCARD_BUSY) && count < transferLength)
{
cardCtrl = REG_CTRCARDCNT;
if( cardCtrl & CTRCARD_DATA_READY ) {
u32 data = REG_CTRCARDFIFO;
*pbuf32++ = data;
count += 4;
}
}
}
else if(useBuf)
{
while( (cardCtrl & CTRCARD_BUSY) && count < transferLength)
{
cardCtrl = REG_CTRCARDCNT;
if( cardCtrl & CTRCARD_DATA_READY ) {
u32 data = REG_CTRCARDFIFO;
pbuf[0] = (unsigned char) (data >> 0);
pbuf[1] = (unsigned char) (data >> 8);
pbuf[2] = (unsigned char) (data >> 16);
pbuf[3] = (unsigned char) (data >> 24);
pbuf += sizeof (unsigned int);
count += 4;
}
}
}
else
{
while( (cardCtrl & CTRCARD_BUSY) && count < transferLength)
{
cardCtrl = REG_CTRCARDCNT;
if( cardCtrl & CTRCARD_DATA_READY ) {
u32 data = REG_CTRCARDFIFO;
(void)data;
count += 4;
}
}
}
// if read is not finished, ds will not pull ROM CS to high, we pull it high manually
if( count != transferLength ) {
// MUST wait for next data ready,
// if ds pull ROM CS to high during 4 byte data transfer, something will mess up
// so we have to wait next data ready
do { cardCtrl = REG_CTRCARDCNT; } while(!(cardCtrl & CTRCARD_DATA_READY));
// and this tiny delay is necessary
wait(66);
// pull ROM CS high
REG_CTRCARDCNT = 0x10000000;
REG_CTRCARDCNT = CTRKEY_PARAM | CTRCARD_ACTIVATE | CTRCARD_nRESET;
}
// wait rom cs high
do { cardCtrl = REG_CTRCARDCNT; } while( cardCtrl & CTRCARD_BUSY );
//lastCmd[0] = command[0];lastCmd[1] = command[1];
}
void ctrcardReadData(u32 sector, u32 length, u32 blocks, void* buffer)
{
u32 cmdBuf[4] = {0};
if(readCount++ >= 10000)
{
readCount = 0;
cmdBuf[0] = 0xC5000000; // Check status cmd
cmdBuf[2] = cmdRand1;
cmdBuf[3] = cmdRand2;
ctrcardCommand(cmdBuf, 0, 1, 0x100002C, NULL); // P9: 0x9000002C, 24-26 unknown
cmdBuf[2] = 0;
cmdBuf[3] = 0;
}
// This may not be needed if gamecards >4 GiB don't exist. P9 does it anyway.
cmdBuf[0] = 0xBF000000 | sector>>23; // Read ROM cmd
cmdBuf[1] = sector<<9;
ctrcardCommand(cmdBuf, length, blocks, 0x704822C, buffer); // P9: 0xD104822C, 24-26 hardcoded
}
NCCH_header* ctrcardGetHeader(void)
{
return &ctrcardHeader;
}
void ctrcardGetUniqueId(u32 *buf)
{
// TODO: Do we need cmdRand* here? Doesn't look like it's needed.
const u32 uniqueIdCmd[4] = {0xC6000000, 0x00000000, 0x00000000, 0x00000000};
ctrcardCommand(uniqueIdCmd, 0x40, 1, 0x701002C, buf); // P9: 0x9003002C, 24-26 unknown
}
static void ctrcardSetSecSeed(const u32 seed[4], bool flag)
{
// Select secure crypto key?
if(flag) REG_CTRCARDSECCNT = ((cardType & 3u) << 8) | 4;
REG_CTRCARDSECSEED = seed[0];
REG_CTRCARDSECSEED = seed[1];
REG_CTRCARDSECSEED = seed[2];
REG_CTRCARDSECSEED = seed[3];
REG_CTRCARDSECCNT |= 0x8000;
while(!(REG_CTRCARDSECCNT & 0x4000));
if(flag) (*(vu32*)0x1000400C) = 0x00000001; // Enable card command encryption?
}
static bool ctrcardSecureInit(u32 cmdBuf[4])
{
if((cardType & 3u) == 3) // Dev card
{
static const u32 devCardKey[4] = {0};
AES_setKey(0x11, AES_KEY_NORMAL, AES_INPUT_BIG | AES_INPUT_NORMAL, false, devCardKey);
AES_selectKeyslot(0x11);
}
else // Retail card
{
AES_setKey(0x3B, AES_KEY_Y, AES_INPUT_BIG | AES_INPUT_NORMAL, false, ctrcardHeader.seedKeyY);
AES_selectKeyslot(0x3B);
}
u32 seed[4];
AES_ctx ctx;
AES_setNonce(&ctx, AES_INPUT_BIG | AES_INPUT_NORMAL, ctrcardHeader.seedNonce);
AES_setCryptParams(&ctx, AES_INPUT_BIG | AES_INPUT_NORMAL, AES_OUTPUT_LITTLE | AES_OUTPUT_REVERSED);
if(!AES_ccm(&ctx, ctrcardHeader.encryptedSeed, seed, 16, ctrcardHeader.seedAesMac, 1, false)) return false;
ctrcardSetSecSeed(seed, true);
cmdRand1 = REG_PRNG[0];
cmdRand2 = REG_PRNG[4];
cmdBuf[0] = 0x83000000; // Seed cmd
cmdBuf[2] = cmdRand1;
cmdBuf[3] = cmdRand2;
ctrcardCommand(cmdBuf, 0, 1, 0x700822C, NULL); // P9: 0x9000002C, 24-26 unknown
seed[0] = cmdRand2;
seed[1] = cmdRand1;
ctrcardSetSecSeed(seed, false);
return true;
}
#pragma once
#include "types.h"
#include "arm9/ncch.h"
#define CARD_ENABLE (1u<<15)
#define CARD_SPI_ENABLE (1u<<13)
#define CARD_SPI_BUSY (1u<<7)
#define CARD_SPI_HOLD (1u<<6)
void resetCardslot(void);
bool gamecardInit(void);
u32 getChipId(void);
u32 getCardType(void);
//////////////////////////////////
// NTRCARD //
//////////////////////////////////
#define NTRCARD_PAGESIZE_0 (0u<<24)
#define NTRCARD_PAGESIZE_4 (7u<<24)
#define NTRCARD_PAGESIZE_512 (1u<<24)
#define NTRCARD_PAGESIZE_1K (2u<<24)
#define NTRCARD_PAGESIZE_2K (3u<<24)
#define NTRCARD_PAGESIZE_4K (4u<<24)
#define NTRCARD_PAGESIZE_8K (5u<<24)
#define NTRCARD_PAGESIZE_16K (6u<<24)
#define NTRCARD_ACTIVATE (1u<<31) // when writing, get the ball rolling
#define NTRCARD_WR (1u<<30) // Card write enable
#define NTRCARD_nRESET (1u<<29) // value on the /reset pin (1 = high out, not a reset state, 0 = low out = in reset)
#define NTRCARD_SEC_LARGE (1u<<28) // Use "other" secure area mode, which tranfers blocks of 0x1000 bytes at a time
#define NTRCARD_CLK_SLOW (1u<<27) // Transfer clock rate (0 = 6.7MHz, 1 = 4.2MHz)
#define NTRCARD_BLK_SIZE(n) ((n & 0x7u)<<24) // Transfer block size, (0 = None, 1..6 = (0x100 << n) bytes, 7 = 4 bytes)
#define NTRCARD_SEC_CMD (1u<<22) // The command transfer will be hardware encrypted (KEY2)
#define NTRCARD_DELAY2(n) ((n & 0x3Fu)<<16) // Transfer delay length part 2
#define NTRCARD_SEC_SEED (1u<<15) // Apply encryption (KEY2) seed to hardware registers
#define NTRCARD_SEC_EN (1u<<14) // Security enable
#define NTRCARD_SEC_DAT (1u<<13) // The data transfer will be hardware encrypted (KEY2)
#define NTRCARD_DELAY1(n) (n & 0x1FFFu) // Transfer delay length part 1
// 3 bits in b10..b8 indicate something
// read bits
#define NTRCARD_BUSY (1u<<31) // when reading, still expecting incomming data?
#define NTRCARD_DATA_READY (1u<<23) // when reading, REG_NTRCARDFIFO has another word of data and is good to go
// Card commands
#define NTRCARD_CMD_DUMMY (0x9Fu)
#define NTRCARD_CMD_HEADER_READ (0x00u)
#define NTRCARD_CMD_HEADER_CHIPID (0x90u)
#define NTRCARD_CMD_ACTIVATE_BF (0x3Cu) // Go into blowfish (KEY1) encryption mode
#define NTRCARD_CMD_ACTIVATE_SEC (0x40u) // Go into hardware (KEY2) encryption mode
#define NTRCARD_CMD_SECURE_CHIPID (0x10u)
#define NTRCARD_CMD_SECURE_READ (0x20u)
#define NTRCARD_CMD_DISABLE_SEC (0x60u) // Leave hardware (KEY2) encryption mode
#define NTRCARD_CMD_DATA_MODE (0xA0u)
#define NTRCARD_CMD_DATA_READ (0xB7u)
#define NTRCARD_CMD_DATA_CHIPID (0xB8u)
#define NTRCARD_CR1_ENABLE (0x8000u)
#define NTRCARD_CR1_IRQ (0x4000u)
#define NTRKEY_PARAM (0x3F1FFFu)
void ntrcardCommand(const u32 command[2], u32 pageSize, u32 latency, void* buffer);
//////////////////////////////////
// CTRCARD //
//////////////////////////////////
#define CTRCARD_PAGESIZE_0 (0u<<16)
#define CTRCARD_PAGESIZE_4 (1u<<16)
#define CTRCARD_PAGESIZE_16 (2u<<16)
#define CTRCARD_PAGESIZE_64 (3u<<16)
#define CTRCARD_PAGESIZE_512 (4u<<16)
#define CTRCARD_PAGESIZE_1K (5u<<16)
#define CTRCARD_PAGESIZE_2K (6u<<16)
#define CTRCARD_PAGESIZE_4K (7u<<16)
#define CTRCARD_PAGESIZE_16K (8u<<16)
#define CTRCARD_PAGESIZE_64K (9u<<16)
#define CTRCARD_CRC_ERROR (1u<<4)
#define CTRCARD_ACTIVATE (1u<<31) // when writing, get the ball rolling
#define CTRCARD_IE (1u<<30) // Interrupt enable
#define CTRCARD_WR (1u<<29) // Card write enable
#define CTRCARD_nRESET (1u<<28) // value on the /reset pin (1 = high out, not a reset state, 0 = low out = in reset)
#define CTRCARD_BLK_SIZE(n) ((n & 0xFu)<<16) // Transfer block size
#define CTRCARD_BUSY (1u<<31) // when reading, still expecting incomming data?
#define CTRCARD_DATA_READY (1u<<27) // when reading, REG_CTRCARDFIFO has another word of data and is good to go
#define CTRKEY_PARAM (0x1000000u)
void ctrcardCommand(const u32 command[4], u32 pageSize, u32 blocks, u32 latency, void* buffer);
void ctrcardReadData(u32 sector, u32 length, u32 blocks, void* buffer);
NCCH_header* ctrcardGetHeader(void);
void ctrcardGetUniqueId(u32 *buf);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment