mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 13:42:47 +00:00
363 lines
14 KiB
C
363 lines
14 KiB
C
#include "gamecart.h"
|
|
#include "protocol.h"
|
|
#include "command_ctr.h"
|
|
#include "command_ntr.h"
|
|
#include "card_spi.h"
|
|
#include "nds.h"
|
|
#include "ncch.h"
|
|
#include "ncsd.h"
|
|
#include "rtc.h"
|
|
|
|
#define CART_INSERTED (!(REG_CARDSTATUS & 0x1))
|
|
|
|
typedef struct {
|
|
NcsdHeader ncsd;
|
|
u32 card2_offset;
|
|
u8 cinfo0[0x312 - (0x200 + sizeof(u32))];
|
|
u32 rom_version;
|
|
u8 cinfo1[0x1000 - (0x312 + sizeof(u32))];
|
|
NcchHeader ncch;
|
|
u8 padding[0x3000 - 0x200];
|
|
u8 private[PRIV_HDR_SIZE];
|
|
u8 unused[0x4000 + 0x8000 - PRIV_HDR_SIZE]; // 0xFF
|
|
u32 cart_type;
|
|
u32 cart_id;
|
|
u64 cart_size;
|
|
u64 data_size;
|
|
u32 save_size;
|
|
CardSPIType save_type;
|
|
u32 unused_offset;
|
|
} PACKED_ALIGN(16) CartDataCtr;
|
|
|
|
typedef struct {
|
|
TwlHeader ntr_header;
|
|
u8 ntr_padding[0x3000]; // 0x00
|
|
u8 secure_area[0x4000];
|
|
u8 secure_area_enc[0x4000];
|
|
u8 modcrypt_area[0x4000];
|
|
u32 cart_type;
|
|
u32 cart_id;
|
|
u64 cart_size;
|
|
u64 data_size;
|
|
u32 save_size;
|
|
CardSPIType save_type;
|
|
u32 arm9i_rom_offset;
|
|
} PACKED_ALIGN(16) CartDataNtrTwl;
|
|
|
|
static DsTime init_time;
|
|
static bool encrypted_sa = false;
|
|
|
|
u32 GetCartName(char* name, CartData* cdata) {
|
|
if (cdata->cart_type & CART_CTR) {
|
|
CartDataCtr* cdata_i = (CartDataCtr*)cdata;
|
|
NcsdHeader* ncsd = &(cdata_i->ncsd);
|
|
snprintf(name, 24, "%016llX_v%02lu", ncsd->mediaId, cdata_i->rom_version);
|
|
} else if (cdata->cart_type & CART_NTR) {
|
|
CartDataNtrTwl* cdata_i = (CartDataNtrTwl*)cdata;
|
|
TwlHeader* nds = &(cdata_i->ntr_header);
|
|
snprintf(name, 24, "%.12s_%.6s_%02u", nds->game_title, nds->game_code, nds->rom_version);
|
|
} else return 1;
|
|
for (char* c = name; *c != '\0'; c++)
|
|
if ((*c == ':') || (*c == '*') || (*c == '?') || (*c == '/') || (*c == '\\') || (*c == ' ')) *c = '_';
|
|
return 0;
|
|
}
|
|
|
|
u32 GetCartInfoString(char* info, CartData* cdata) {
|
|
if (cdata->cart_type & CART_CTR) {
|
|
CartDataCtr* cdata_i = (CartDataCtr*)cdata;
|
|
NcsdHeader* ncsd = &(cdata_i->ncsd);
|
|
NcchHeader* ncch = &(cdata_i->ncch);
|
|
snprintf(info, 256, "Title ID : %016llX\nProduct Code : %.10s\nRevision : %lu\nCart ID : %08lX\nPlatform : %s\nTimestamp : 20%02X-%02X-%02X %02X:%02X:%02X\nGM9 Version : %s\n",
|
|
ncsd->mediaId, ncch->productcode, cdata_i->rom_version, cdata_i->cart_id,
|
|
(ncch->flags[4] == 0x2) ? "N3DS" : "O3DS",
|
|
init_time.bcd_Y, init_time.bcd_M, init_time.bcd_D,
|
|
init_time.bcd_h, init_time.bcd_m, init_time.bcd_s,
|
|
VERSION);
|
|
} else if (cdata->cart_type & CART_NTR) {
|
|
CartDataNtrTwl* cdata_i = (CartDataNtrTwl*)cdata;
|
|
TwlHeader* nds = &(cdata_i->ntr_header);
|
|
snprintf(info, 256, "Title String : %.12s\nProduct Code : %.6s\nRevision : %u\nCart ID : %08lX\nPlatform : %s\nTimestamp : 20%02X-%02X-%02X %02X:%02X:%02X\nGM9 Version : %s\n",
|
|
nds->game_title, nds->game_code, nds->rom_version, cdata_i->cart_id,
|
|
(nds->unit_code == 0x2) ? "DSi Enhanced" : (nds->unit_code == 0x3) ? "DSi Exclusive" : "DS",
|
|
init_time.bcd_Y, init_time.bcd_M, init_time.bcd_D,
|
|
init_time.bcd_h, init_time.bcd_m, init_time.bcd_s,
|
|
VERSION);
|
|
} else return 1;
|
|
return 0;
|
|
}
|
|
|
|
u32 SetSecureAreaEncryption(bool encrypted) {
|
|
encrypted_sa = encrypted;
|
|
return 0;
|
|
}
|
|
|
|
u32 InitCartRead(CartData* cdata) {
|
|
get_dstime(&init_time);
|
|
encrypted_sa = false;
|
|
memset(cdata, 0x00, sizeof(CartData));
|
|
cdata->cart_type = CART_NONE;
|
|
if (!CART_INSERTED) return 1;
|
|
Cart_Init();
|
|
cdata->cart_id = Cart_GetID();
|
|
cdata->cart_type = (cdata->cart_id & 0x10000000) ? CART_CTR : CART_NTR;
|
|
|
|
// Use the cart ID to determine the ROM size.
|
|
// (ROM header might be incorrect on dev carts.)
|
|
switch ((cdata->cart_id >> 16) & 0xFF) {
|
|
case 0x07: cdata->cart_size = 8ULL*1024*1024; break;
|
|
case 0x0F: cdata->cart_size = 16ULL*1024*1024; break;
|
|
case 0x1F: cdata->cart_size = 32ULL*1024*1024; break;
|
|
case 0x3F: cdata->cart_size = 64ULL*1024*1024; break;
|
|
case 0x7F: cdata->cart_size = 128ULL*1024*1024; break;
|
|
case 0xFF: cdata->cart_size = 256ULL*1024*1024; break;
|
|
case 0xFE: cdata->cart_size = 512ULL*1024*1024; break;
|
|
case 0xFA: cdata->cart_size = 1024ULL*1024*1024; break;
|
|
case 0xF8: cdata->cart_size = 2048ULL*1024*1024; break;
|
|
case 0xF0: cdata->cart_size = 4096ULL*1024*1024; break;
|
|
default: cdata->cart_size = 0; break;
|
|
}
|
|
|
|
if (cdata->cart_type & CART_CTR) { // CTR cartridges
|
|
memset(cdata, 0xFF, 0x4000 + PRIV_HDR_SIZE); // switch the padding to 0xFF
|
|
|
|
// init, NCCH header
|
|
static u32 sec_keys[4];
|
|
u8* ncch_header = cdata->header + 0x1000;
|
|
CTR_CmdReadHeader(ncch_header);
|
|
Cart_Secure_Init((u32*) (void*) ncch_header, sec_keys);
|
|
|
|
// NCSD header and CINFO
|
|
// Cart_Dummy();
|
|
// Cart_Dummy();
|
|
CTR_CmdReadData(0, 0x200, 8, cdata->header);
|
|
|
|
// safety checks, cart size
|
|
NcsdHeader* ncsd = (NcsdHeader*) (void*) cdata->header;
|
|
NcchHeader* ncch = (NcchHeader*) (void*) ncch_header;
|
|
if ((ValidateNcsdHeader(ncsd) != 0) || (ValidateNcchHeader(ncch) != 0))
|
|
return 1;
|
|
if (cdata->cart_size == 0)
|
|
cdata->cart_size = (u64) ncsd->size * NCSD_MEDIA_UNIT;
|
|
cdata->data_size = GetNcsdTrimmedSize(ncsd);
|
|
if (cdata->cart_size > 0x100000000) return 1; // carts > 4GB don't exist
|
|
// else if (cdata->cart_size == 0x100000000) cdata->cart_size -= 0x200; // silent 4GB fix
|
|
if (cdata->data_size > cdata->cart_size) return 1;
|
|
|
|
// private header
|
|
u8* priv_header = cdata->header + 0x4000;
|
|
CTR_CmdReadUniqueID(priv_header);
|
|
memcpy(priv_header + 0x40, &(cdata->cart_id), 4);
|
|
memset(priv_header + 0x44, 0x00, 4);
|
|
memset(priv_header + 0x48, 0xFF, 8);
|
|
|
|
// save data
|
|
u32 card2_offset = getle32(cdata->header + 0x200);
|
|
if ((card2_offset != 0xFFFFFFFF) || (CardSPIGetCardSPIType(&(cdata->save_type), 0) != 0)) {
|
|
cdata->save_type = (CardSPIType) { NO_CHIP, false };
|
|
}
|
|
cdata->save_size = CardSPIGetCapacity(cdata->save_type);
|
|
} else { // NTR/TWL cartridges
|
|
// NTR header
|
|
TwlHeader* nds_header = (void*)cdata->header;
|
|
u8 secure_area_enc[0x4000];
|
|
NTR_CmdReadHeader(cdata->header);
|
|
if (!(*(cdata->header))) return 1; // error reading the header
|
|
if (!NTR_Secure_Init(cdata->header, secure_area_enc, Cart_GetID(), 0)) return 1;
|
|
|
|
|
|
// cartridge size, trimmed size, twl presets
|
|
if (nds_header->device_capacity >= 15) return 1; // too big, not valid
|
|
if (cdata->cart_size == 0)
|
|
cdata->cart_size = (128 * 1024) << nds_header->device_capacity;
|
|
cdata->data_size = nds_header->ntr_rom_size;
|
|
cdata->arm9i_rom_offset = 0;
|
|
|
|
// TWL header
|
|
if (nds_header->unit_code != 0x00) { // DSi or NDS+DSi
|
|
cdata->cart_type |= CART_TWL;
|
|
cdata->data_size = nds_header->ntr_twl_rom_size;
|
|
cdata->arm9i_rom_offset = nds_header->arm9i_rom_offset;
|
|
if ((cdata->arm9i_rom_offset < nds_header->ntr_rom_size) ||
|
|
(cdata->arm9i_rom_offset + MODC_AREA_SIZE > cdata->data_size))
|
|
return 1; // safety first
|
|
|
|
// Some NTR dev carts have TWL ROMs flashed to them.
|
|
// We'll only want to use TWL secure init if this is a TWL cartridge.
|
|
if (cdata->cart_id & 0x40000000U) { // TWL cartridge
|
|
Cart_Init();
|
|
NTR_CmdReadHeader(cdata->storage);
|
|
if (!NTR_Secure_Init(cdata->storage, NULL, Cart_GetID(), 1)) return 1;
|
|
}
|
|
}
|
|
|
|
// store encrypted secure area
|
|
memcpy(cdata->storage, secure_area_enc, 0x4000);
|
|
|
|
// last safety check
|
|
if (cdata->data_size > cdata->cart_size) return 1;
|
|
|
|
// save data
|
|
bool infrared = *(nds_header->game_code) == 'I';
|
|
if (CardSPIGetCardSPIType(&(cdata->save_type), infrared) != 0) {
|
|
cdata->save_type = (CardSPIType) { NO_CHIP, false };
|
|
}
|
|
cdata->save_size = CardSPIGetCapacity(cdata->save_type);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
u32 ReadCartSectors(void* buffer, u32 sector, u32 count, CartData* cdata) {
|
|
u8* buffer8 = (u8*) buffer;
|
|
if (!CART_INSERTED) return 1;
|
|
// header
|
|
const u32 header_sectors = 0x4000/0x200;
|
|
if (sector < header_sectors) {
|
|
u32 header_count = (sector + count > header_sectors) ? header_sectors - sector : count;
|
|
memcpy(buffer8, cdata->header + (sector * 0x200), header_count * 0x200);
|
|
buffer8 += header_count * 0x200;
|
|
sector += header_count;
|
|
count -= header_count;
|
|
}
|
|
if (!count) return 0;
|
|
// actual cart reads
|
|
if (cdata->cart_type & CART_CTR) {
|
|
// don't read more than 1MB at once
|
|
const u32 max_read = 0x800;
|
|
u8* buff = buffer8;
|
|
for (u32 i = 0; i < count; i += max_read) {
|
|
// Cart_Dummy();
|
|
// Cart_Dummy();
|
|
CTR_CmdReadData(sector + i, 0x200, min(max_read, count - i), buff);
|
|
buff += max_read * 0x200;
|
|
}
|
|
|
|
// overwrite the card2 savegame with 0xFF
|
|
u32 card2_offset = getle32(cdata->header + 0x200);
|
|
if ((card2_offset != 0xFFFFFFFF) &&
|
|
((card2_offset * 0x200) >= cdata->data_size) &&
|
|
(sector + count > card2_offset)) {
|
|
if (sector > card2_offset)
|
|
memset(buffer8, 0xFF, (count * 0x200));
|
|
else memset(buffer8 + (card2_offset - sector) * 0x200, 0xFF,
|
|
(count - (card2_offset - sector)) * 0x200);
|
|
}
|
|
} else if (cdata->cart_type & CART_NTR) {
|
|
u8* buff = buffer8;
|
|
|
|
// secure area handling
|
|
const u32 sa_sector_end = 0x8000/0x200;
|
|
if (sector < sa_sector_end) {
|
|
CartDataNtrTwl* cdata_twl = (CartDataNtrTwl*) cdata;
|
|
u8* sa = encrypted_sa ? cdata_twl->secure_area_enc : cdata_twl->secure_area;
|
|
u32 count_sa = ((sector + count) > sa_sector_end) ? sa_sector_end - sector : count;
|
|
memcpy(buff, sa + ((sector - header_sectors) * 0x200), count_sa * 0x200);
|
|
buff += count_sa * 0x200;
|
|
sector += count_sa;
|
|
count -= count_sa;
|
|
}
|
|
|
|
// regular cart data
|
|
u32 off = sector * 0x200;
|
|
for (u32 i = 0; i < count; i++, off += 0x200, buff += 0x200)
|
|
NTR_CmdReadData(off, buff);
|
|
|
|
// modcrypt area handling
|
|
if ((cdata->cart_type & CART_TWL) &&
|
|
((sector+count) * 0x200 > cdata->arm9i_rom_offset) &&
|
|
(sector * 0x200 < cdata->arm9i_rom_offset + MODC_AREA_SIZE)) {
|
|
u32 arm9i_rom_offset = cdata->arm9i_rom_offset;
|
|
u8* buffer_arm9i = buffer8;
|
|
u32 offset_i = 0;
|
|
u32 size_i = MODC_AREA_SIZE;
|
|
if (arm9i_rom_offset < (sector * 0x200))
|
|
offset_i = (sector * 0x200) - arm9i_rom_offset;
|
|
else buffer_arm9i = buffer8 + (arm9i_rom_offset - (sector * 0x200));
|
|
size_i = MODC_AREA_SIZE - offset_i;
|
|
if (size_i > (count * 0x200) - (buffer_arm9i - buffer8))
|
|
size_i = (count * 0x200) - (buffer_arm9i - buffer8);
|
|
if (size_i) memcpy(buffer_arm9i, cdata->storage + 0x4000 + offset_i, size_i);
|
|
}
|
|
} else return 1;
|
|
return 0;
|
|
}
|
|
|
|
u32 ReadCartBytes(void* buffer, u64 offset, u64 count, CartData* cdata) {
|
|
if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
|
|
// simple wrapper function for ReadCartSectors(...)
|
|
return ReadCartSectors(buffer, offset / 0x200, count / 0x200, cdata);
|
|
} else { // misaligned data -> -___-
|
|
u8* buffer8 = (u8*) buffer;
|
|
u8 l_buffer[0x200];
|
|
if (offset % 0x200) { // handle misaligned offset
|
|
u32 offset_fix = 0x200 - (offset % 0x200);
|
|
if (ReadCartSectors(l_buffer, offset / 0x200, 1, cdata) != 0) return 1;
|
|
memcpy(buffer8, l_buffer + 0x200 - offset_fix, min(offset_fix, count));
|
|
if (count <= offset_fix) return 0;
|
|
offset += offset_fix;
|
|
buffer8 += offset_fix;
|
|
count -= offset_fix;
|
|
} // offset is now aligned and part of the data is read
|
|
if (count >= 0x200) { // otherwise this is misaligned and will be handled below
|
|
if (ReadCartSectors(buffer8, offset / 0x200, count / 0x200, cdata) != 0) return 1;
|
|
}
|
|
if (count % 0x200) { // handle misaligned count
|
|
u32 count_fix = count % 0x200;
|
|
if (ReadCartSectors(l_buffer, (offset + count) / 0x200, 1, cdata) != 0) return 1;
|
|
memcpy(buffer8 + count - count_fix, l_buffer, count_fix);
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
u32 ReadCartPrivateHeader(void* buffer, u64 offset, u64 count, CartData* cdata) {
|
|
if (!(cdata->cart_type & CART_CTR)) return 1;
|
|
if (offset < PRIV_HDR_SIZE) {
|
|
u8* priv_hdr = cdata->header + 0x4000;
|
|
if (offset + count > PRIV_HDR_SIZE) count = PRIV_HDR_SIZE - offset;
|
|
memcpy(buffer, priv_hdr + offset, count);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
u32 ReadCartInfo(u8* buffer, u64 offset, u64 count, CartData* cdata) {
|
|
char info[256];
|
|
u32 len;
|
|
|
|
GetCartInfoString(info, cdata);
|
|
len = strnlen(info, 255);
|
|
|
|
if (offset >= len) return 0;
|
|
if (offset + count > len) count = len - offset;
|
|
memcpy(buffer, info + offset, count);
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 ReadCartSave(u8* buffer, u64 offset, u64 count, CartData* cdata) {
|
|
if (offset >= cdata->save_size) return 1;
|
|
if (offset + count > cdata->save_size) count = cdata->save_size - offset;
|
|
return (CardSPIReadSaveData(cdata->save_type, offset, buffer, count) == 0) ? 0 : 1;
|
|
}
|
|
|
|
u32 WriteCartSave(const u8* buffer, u64 offset, u64 count, CartData* cdata) {
|
|
if (offset >= cdata->save_size) return 1;
|
|
if (offset + count > cdata->save_size) count = cdata->save_size - offset;
|
|
return (CardSPIWriteSaveData(cdata->save_type, offset, buffer, count) == 0) ? 0 : 1;
|
|
}
|
|
|
|
u32 ReadCartSaveJedecId(u8* buffer, u64 offset, u64 count, CartData* cdata) {
|
|
u8 ownBuf[JEDECID_AND_SREG_SIZE] = { 0 };
|
|
u32 id;
|
|
u8 sReg;
|
|
if (offset >= JEDECID_AND_SREG_SIZE) return 1;
|
|
if (offset + count > JEDECID_AND_SREG_SIZE) count = JEDECID_AND_SREG_SIZE - offset;
|
|
int res = CardSPIReadJEDECIDAndStatusReg(cdata->save_type, &id, &sReg);
|
|
if (res) return res;
|
|
ownBuf[0] = (id >> 16) & 0xff;
|
|
ownBuf[1] = (id >> 8) & 0xff;
|
|
ownBuf[2] = id & 0xff;
|
|
ownBuf[JEDECID_AND_SREG_SIZE - 1] = sReg;
|
|
memcpy(buffer, ownBuf + offset, count);
|
|
return 0;
|
|
}
|