forked from Mirror/GodMode9
Handle seed crypto when installing game images
This commit is contained in:
parent
1f59f6c3ad
commit
8e3a5b496a
@ -1,10 +1,7 @@
|
|||||||
#include "ncch.h"
|
#include "ncch.h"
|
||||||
#include "support.h"
|
|
||||||
#include "disadiff.h"
|
|
||||||
#include "keydb.h"
|
#include "keydb.h"
|
||||||
#include "aes.h"
|
#include "aes.h"
|
||||||
#include "sha.h"
|
#include "sha.h"
|
||||||
#include "ff.h"
|
|
||||||
|
|
||||||
#define EXEFS_KEYID(name) (((strncmp(name, "banner", 8) == 0) || (strncmp(name, "icon", 8) == 0)) ? 0 : 1)
|
#define EXEFS_KEYID(name) (((strncmp(name, "banner", 8) == 0) || (strncmp(name, "icon", 8) == 0)) ? 0 : 1)
|
||||||
|
|
||||||
@ -51,100 +48,6 @@ u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 section) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
|
|
||||||
static u8 lseed[16+8] __attribute__((aligned(4))) = { 0 }; // seed plus title ID for easy validation
|
|
||||||
u64 titleId = ncch->programId;
|
|
||||||
u32 hash_seed = ncch->hash_seed;
|
|
||||||
u32 sha256sum[8];
|
|
||||||
|
|
||||||
memcpy(lseed+16, &(ncch->programId), 8);
|
|
||||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
|
||||||
if (hash_seed == sha256sum[0]) {
|
|
||||||
memcpy(seed, lseed, 16);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup a large enough buffer
|
|
||||||
u8* buffer = (u8*) malloc(max(STD_BUFFER_SIZE, SEEDSAVE_AREA_SIZE));
|
|
||||||
if (!buffer) return 1;
|
|
||||||
|
|
||||||
// try to grab the seed from NAND database
|
|
||||||
const char* nand_drv[] = {"1:", "4:"}; // SysNAND and EmuNAND
|
|
||||||
for (u32 i = 0; i < countof(nand_drv); i++) {
|
|
||||||
UINT btr = 0;
|
|
||||||
FIL file;
|
|
||||||
char path[128];
|
|
||||||
|
|
||||||
// grab the key Y from movable.sed
|
|
||||||
u8 movable_keyy[16];
|
|
||||||
snprintf(path, 128, "%s/private/movable.sed", nand_drv[i]);
|
|
||||||
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
|
||||||
continue;
|
|
||||||
f_lseek(&file, 0x110);
|
|
||||||
f_read(&file, movable_keyy, 0x10, &btr);
|
|
||||||
f_close(&file);
|
|
||||||
|
|
||||||
// build the seed save path
|
|
||||||
sha_quick(sha256sum, movable_keyy, 0x10, SHA256_MODE);
|
|
||||||
snprintf(path, 128, "%s/data/%08lX%08lX%08lX%08lX/sysdata/0001000F/00000000",
|
|
||||||
nand_drv[i], sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
|
||||||
|
|
||||||
// check seedsave for seed
|
|
||||||
u8* seeddb = buffer;
|
|
||||||
if (ReadDisaDiffIvfcLvl4(path, NULL, SEEDSAVE_AREA_OFFSET, SEEDSAVE_AREA_SIZE, seeddb) != SEEDSAVE_AREA_SIZE)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// search for the seed
|
|
||||||
for (u32 s = 0; s < SEEDSAVE_MAX_ENTRIES; s++) {
|
|
||||||
if (titleId != getle64(seeddb + (s*8))) continue;
|
|
||||||
memcpy(lseed, seeddb + (SEEDSAVE_MAX_ENTRIES*8) + (s*16), 16);
|
|
||||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
|
||||||
if (hash_seed == sha256sum[0]) {
|
|
||||||
memcpy(seed, lseed, 16);
|
|
||||||
free(buffer);
|
|
||||||
return 0; // found!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// not found -> try seeddb.bin
|
|
||||||
SeedInfo* seeddb = (SeedInfo*) (void*) buffer;
|
|
||||||
size_t len = LoadSupportFile(SEEDDB_NAME, seeddb, STD_BUFFER_SIZE);
|
|
||||||
if (len && (seeddb->n_entries <= (len - 16) / 32)) { // check filesize / seeddb size
|
|
||||||
for (u32 s = 0; s < seeddb->n_entries; s++) {
|
|
||||||
if (titleId != seeddb->entries[s].titleId)
|
|
||||||
continue;
|
|
||||||
memcpy(lseed, seeddb->entries[s].seed, 16);
|
|
||||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
|
||||||
if (hash_seed == sha256sum[0]) {
|
|
||||||
memcpy(seed, lseed, 16);
|
|
||||||
free(buffer);
|
|
||||||
return 0; // found!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// out of options -> failed!
|
|
||||||
free(buffer);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry) {
|
|
||||||
if (!seed_entry) { // no seed entry -> reset database
|
|
||||||
memset(seed_info, 0, 16);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// check if entry already in DB
|
|
||||||
u32 n_entries = seed_info->n_entries;
|
|
||||||
SeedInfoEntry* seed = seed_info->entries;
|
|
||||||
for (u32 i = 0; i < n_entries; i++, seed++)
|
|
||||||
if (seed->titleId == seed_entry->titleId) return 0;
|
|
||||||
// actually a new seed entry
|
|
||||||
memcpy(seed, seed_entry, sizeof(SeedInfoEntry));
|
|
||||||
seed_info->n_entries++;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid) {
|
u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid) {
|
||||||
u8 flags3 = (crypto >> 8) & 0xFF;
|
u8 flags3 = (crypto >> 8) & 0xFF;
|
||||||
u8 flags7 = crypto & 0xFF;
|
u8 flags7 = crypto & 0xFF;
|
||||||
@ -177,7 +80,7 @@ u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid) {
|
|||||||
if ((memcmp(lsignature, ncch->signature, 16) != 0) || (ltitleId != ncch->programId)) {
|
if ((memcmp(lsignature, ncch->signature, 16) != 0) || (ltitleId != ncch->programId)) {
|
||||||
u8 keydata[16+16] __attribute__((aligned(4)));
|
u8 keydata[16+16] __attribute__((aligned(4)));
|
||||||
memcpy(keydata, ncch->signature, 16);
|
memcpy(keydata, ncch->signature, 16);
|
||||||
if (GetNcchSeed(keydata + 16, ncch) != 0)
|
if (FindSeed(keydata + 16, ncch->programId, ncch->hash_seed) != 0)
|
||||||
return 1;
|
return 1;
|
||||||
sha_quick(seedkeyY, keydata, 32, SHA256_MODE);
|
sha_quick(seedkeyY, keydata, 32, SHA256_MODE);
|
||||||
memcpy(lsignature, ncch->signature, 16);
|
memcpy(lsignature, ncch->signature, 16);
|
||||||
@ -357,3 +260,12 @@ u32 SetNcchSdFlag(void* data) { // data must be at least 0x600 byte and start wi
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 SetupSystemForNcch(NcchHeader* ncch, bool to_emunand) {
|
||||||
|
u16 crypto = NCCH_GET_CRYPTO(ncch);
|
||||||
|
if ((crypto & 0x20) && // seed crypto
|
||||||
|
(SetupSeedSystemCrypto(ncch->programId, ncch->hash_seed, to_emunand) != 0) &&
|
||||||
|
(SetupSeedPrePurchase(ncch->programId, to_emunand) != 0))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "exefs.h"
|
#include "exefs.h"
|
||||||
|
#include "seedsave.h"
|
||||||
|
|
||||||
#define NCCH_MEDIA_UNIT 0x200
|
#define NCCH_MEDIA_UNIT 0x200
|
||||||
|
|
||||||
@ -17,13 +18,6 @@
|
|||||||
#define NCCH_STDCRYPTO 0x0000
|
#define NCCH_STDCRYPTO 0x0000
|
||||||
#define NCCH_GET_CRYPTO(ncch) (!NCCH_ENCRYPTED(ncch) ? NCCH_NOCRYPTO : (((ncch)->flags[3] << 8) | ((ncch)->flags[7]&(0x01|0x20))))
|
#define NCCH_GET_CRYPTO(ncch) (!NCCH_ENCRYPTED(ncch) ? NCCH_NOCRYPTO : (((ncch)->flags[3] << 8) | ((ncch)->flags[7]&(0x01|0x20))))
|
||||||
|
|
||||||
#define SEEDDB_NAME "seeddb.bin"
|
|
||||||
#define SEEDDB_SIZE(sdb) (16 + ((sdb)->n_entries * sizeof(SeedInfoEntry)))
|
|
||||||
|
|
||||||
#define SEEDSAVE_MAX_ENTRIES 2000
|
|
||||||
#define SEEDSAVE_AREA_OFFSET 0x4000
|
|
||||||
#define SEEDSAVE_AREA_SIZE (SEEDSAVE_MAX_ENTRIES * (8+16))
|
|
||||||
|
|
||||||
// wrapper defines
|
// wrapper defines
|
||||||
#define DecryptNcch(data, offset, size, ncch, exefs) CryptNcch(data, offset, size, ncch, exefs, NCCH_NOCRYPTO)
|
#define DecryptNcch(data, offset, size, ncch, exefs) CryptNcch(data, offset, size, ncch, exefs, NCCH_NOCRYPTO)
|
||||||
#define EncryptNcch(data, offset, size, ncch, exefs, crypto) CryptNcch(data, offset, size, ncch, exefs, crypto)
|
#define EncryptNcch(data, offset, size, ncch, exefs, crypto) CryptNcch(data, offset, size, ncch, exefs, crypto)
|
||||||
@ -89,23 +83,10 @@ typedef struct {
|
|||||||
u8 hash_romfs[0x20];
|
u8 hash_romfs[0x20];
|
||||||
} __attribute__((packed, aligned(16))) NcchHeader;
|
} __attribute__((packed, aligned(16))) NcchHeader;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
u64 titleId;
|
|
||||||
u8 seed[16];
|
|
||||||
u8 reserved[8];
|
|
||||||
} PACKED_STRUCT SeedInfoEntry;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
u32 n_entries;
|
|
||||||
u8 padding[12];
|
|
||||||
SeedInfoEntry entries[256]; // this number is only a placeholder
|
|
||||||
} PACKED_STRUCT SeedInfo;
|
|
||||||
|
|
||||||
u32 ValidateNcchHeader(NcchHeader* header);
|
u32 ValidateNcchHeader(NcchHeader* header);
|
||||||
u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid);
|
u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid);
|
||||||
u32 SetupNcchCrypto(NcchHeader* ncch, u16 crypt_to);
|
u32 SetupNcchCrypto(NcchHeader* ncch, u16 crypt_to);
|
||||||
u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs, u16 crypto);
|
u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs, u16 crypto);
|
||||||
u32 CryptNcchSequential(void* data, u32 offset, u32 size, u16 crypto);
|
u32 CryptNcchSequential(void* data, u32 offset, u32 size, u16 crypto);
|
||||||
u32 SetNcchSdFlag(void* data);
|
u32 SetNcchSdFlag(void* data);
|
||||||
|
u32 SetupSystemForNcch(NcchHeader* ncch, bool to_emunand);
|
||||||
u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry);
|
|
||||||
|
240
arm9/source/game/seedsave.c
Normal file
240
arm9/source/game/seedsave.c
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
#include "seedsave.h"
|
||||||
|
#include "support.h"
|
||||||
|
#include "nandcmac.h"
|
||||||
|
#include "sha.h"
|
||||||
|
#include "ff.h"
|
||||||
|
|
||||||
|
#define TITLETAG_MAX_ENTRIES 2000 // same as SEEDSAVE_MAX_ENTRIES
|
||||||
|
#define TITLETAG_AREA_OFFSET 0x10000 // thanks @luigoalma
|
||||||
|
|
||||||
|
// this structure is 0x80 bytes, thanks @luigoalma
|
||||||
|
typedef struct {
|
||||||
|
char magic[4]; // "PREP" for prepurchase install. NIM excepts "PREP" to do seed downloads on the background.
|
||||||
|
// playable date parameters
|
||||||
|
// 2000-01-01 is a safe bet for a stub entry
|
||||||
|
s32 year;
|
||||||
|
u8 month;
|
||||||
|
u8 day;
|
||||||
|
u16 country_code; // enum list of values, this will affect seed downloading, just requires at least one valid enum value. 1 == Japan, it's enough.
|
||||||
|
// everything after this point can be 0 padded
|
||||||
|
u32 seed_status; // 0 == not tried, 1 == last attempt failed, 2 == seed downloaded successfully
|
||||||
|
s32 seed_result; // result related to last download attempt
|
||||||
|
s32 seed_support_error_code; // support code derived from the result code
|
||||||
|
// after this point, all is unused or padding. NIM wont use or access this at all.
|
||||||
|
// It's memset to 0 by NIM
|
||||||
|
u8 unknown[0x68];
|
||||||
|
} PACKED_STRUCT TitleTagEntry;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 unknown0;
|
||||||
|
u32 n_entries;
|
||||||
|
u8 unknown1[0x1000 - 0x8];
|
||||||
|
u64 titleId[TITLETAG_MAX_ENTRIES];
|
||||||
|
TitleTagEntry tag[TITLETAG_MAX_ENTRIES];
|
||||||
|
} PACKED_STRUCT TitleTag;
|
||||||
|
|
||||||
|
u32 GetSeedPath(char* path, const char* drv) {
|
||||||
|
u8 movable_keyy[16] = { 0 };
|
||||||
|
u32 sha256sum[8];
|
||||||
|
UINT btr = 0;
|
||||||
|
FIL file;
|
||||||
|
|
||||||
|
// grab the key Y from movable.sed
|
||||||
|
// wrong result if movable.sed does not have it
|
||||||
|
snprintf(path, 128, "%2.2s/private/movable.sed", drv);
|
||||||
|
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||||
|
return 1;
|
||||||
|
f_lseek(&file, 0x110);
|
||||||
|
f_read(&file, movable_keyy, 0x10, &btr);
|
||||||
|
f_close(&file);
|
||||||
|
if (btr != 0x10)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// build the seed save path
|
||||||
|
sha_quick(sha256sum, movable_keyy, 0x10, SHA256_MODE);
|
||||||
|
snprintf(path, 128, "%2.2s/data/%08lX%08lX%08lX%08lX/sysdata/0001000F/00000000",
|
||||||
|
drv, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 FindSeed(u8* seed, u64 titleId, u32 hash_seed) {
|
||||||
|
static u8 lseed[16+8] __attribute__((aligned(4))) = { 0 }; // seed plus title ID for easy validation
|
||||||
|
u32 sha256sum[8];
|
||||||
|
|
||||||
|
memcpy(lseed+16, &titleId, 8);
|
||||||
|
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||||
|
if (hash_seed == sha256sum[0]) {
|
||||||
|
memcpy(seed, lseed, 16);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup a large enough buffer
|
||||||
|
u8* buffer = (u8*) malloc(max(STD_BUFFER_SIZE, sizeof(SeedDb)));
|
||||||
|
if (!buffer) return 1;
|
||||||
|
|
||||||
|
// try to grab the seed from NAND database
|
||||||
|
const char* nand_drv[] = {"1:", "4:"}; // SysNAND and EmuNAND
|
||||||
|
for (u32 i = 0; i < countof(nand_drv); i++) {
|
||||||
|
char path[128];
|
||||||
|
SeedDb* seeddb = (SeedDb*) (void*) buffer;
|
||||||
|
|
||||||
|
// read SEEDDB from file
|
||||||
|
if (GetSeedPath(path, nand_drv[i]) != 0) continue;
|
||||||
|
if ((ReadDisaDiffIvfcLvl4(path, NULL, SEEDSAVE_AREA_OFFSET, sizeof(SeedDb), seeddb) != sizeof(SeedDb)) ||
|
||||||
|
(seeddb->n_entries > SEEDSAVE_MAX_ENTRIES))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// search for the seed
|
||||||
|
for (u32 s = 0; s < seeddb->n_entries; s++) {
|
||||||
|
if (titleId != seeddb->titleId[s]) continue;
|
||||||
|
memcpy(lseed, &(seeddb->seed[s]), sizeof(Seed));
|
||||||
|
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||||
|
if (hash_seed == sha256sum[0]) {
|
||||||
|
memcpy(seed, lseed, 16);
|
||||||
|
free(buffer);
|
||||||
|
return 0; // found!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found -> try seeddb.bin
|
||||||
|
SeedInfo* seeddb = (SeedInfo*) (void*) buffer;
|
||||||
|
size_t len = LoadSupportFile(SEEDINFO_NAME, seeddb, STD_BUFFER_SIZE);
|
||||||
|
if (len && (seeddb->n_entries <= (len - 16) / 32)) { // check filesize / seeddb size
|
||||||
|
for (u32 s = 0; s < seeddb->n_entries; s++) {
|
||||||
|
if (titleId != seeddb->entries[s].titleId)
|
||||||
|
continue;
|
||||||
|
memcpy(lseed, &(seeddb->entries[s].seed), sizeof(Seed));
|
||||||
|
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||||
|
if (hash_seed == sha256sum[0]) {
|
||||||
|
memcpy(seed, lseed, 16);
|
||||||
|
free(buffer);
|
||||||
|
return 0; // found!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// out of options -> failed!
|
||||||
|
free(buffer);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry) {
|
||||||
|
if (!seed_entry) { // no seed entry -> reset database
|
||||||
|
memset(seed_info, 0, 16);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// check if entry already in DB
|
||||||
|
u32 n_entries = seed_info->n_entries;
|
||||||
|
SeedInfoEntry* seed = seed_info->entries;
|
||||||
|
for (u32 i = 0; i < n_entries; i++, seed++)
|
||||||
|
if (seed->titleId == seed_entry->titleId) return 0;
|
||||||
|
// actually a new seed entry
|
||||||
|
memcpy(seed, seed_entry, sizeof(SeedInfoEntry));
|
||||||
|
seed_info->n_entries++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 InstallSeedDbToSystem(SeedInfo* seed_info, bool to_emunand) {
|
||||||
|
char path[128];
|
||||||
|
SeedDb* seeddb = (SeedDb*) malloc(sizeof(SeedDb));
|
||||||
|
if (!seeddb) return 1;
|
||||||
|
|
||||||
|
// read the current SEEDDB database
|
||||||
|
if ((GetSeedPath(path, to_emunand ? "4:" : "1:") != 0) ||
|
||||||
|
(ReadDisaDiffIvfcLvl4(path, NULL, SEEDSAVE_AREA_OFFSET, sizeof(SeedDb), seeddb) != sizeof(SeedDb)) ||
|
||||||
|
(seeddb->n_entries >= SEEDSAVE_MAX_ENTRIES)) {
|
||||||
|
free (seeddb);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find free slots, insert seeds from SeedInfo
|
||||||
|
for (u32 slot = 0, s = 0; s < seed_info->n_entries; s++) {
|
||||||
|
SeedInfoEntry* entry = &(seed_info->entries[s]);
|
||||||
|
for (slot = 0; slot < seeddb->n_entries; slot++)
|
||||||
|
if (seeddb->titleId[slot] == entry->titleId) break;
|
||||||
|
if (slot >= SEEDSAVE_MAX_ENTRIES) break;
|
||||||
|
if (slot >= seeddb->n_entries) seeddb->n_entries = slot + 1;
|
||||||
|
seeddb->titleId[slot] = entry->titleId;
|
||||||
|
memcpy(&(seeddb->seed[slot]), &(entry->seed), sizeof(Seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
// write back to system (warning: no write protection checks here)
|
||||||
|
u32 size = WriteDisaDiffIvfcLvl4(path, NULL, SEEDSAVE_AREA_OFFSET, sizeof(SeedDb), seeddb);
|
||||||
|
FixFileCmac(path, false);
|
||||||
|
|
||||||
|
free (seeddb);
|
||||||
|
return (size == sizeof(SeedDb)) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 SetupSeedPrePurchase(u64 titleId, bool to_emunand) {
|
||||||
|
// here, we ask the system to install the seed for us
|
||||||
|
TitleTag* titletag = (TitleTag*) malloc(sizeof(TitleTag));
|
||||||
|
if (!titletag) return 1;
|
||||||
|
|
||||||
|
char path[128];
|
||||||
|
if ((GetSeedPath(path, to_emunand ? "4:" : "1:") != 0) ||
|
||||||
|
(ReadDisaDiffIvfcLvl4(path, NULL, TITLETAG_AREA_OFFSET, sizeof(TitleTag), titletag) != sizeof(TitleTag)) ||
|
||||||
|
(titletag->n_entries >= TITLETAG_MAX_ENTRIES)) {
|
||||||
|
free (titletag);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pointers for TITLETAG title IDs and seeds
|
||||||
|
// find a free slot, insert titletag
|
||||||
|
u32 slot = 0;
|
||||||
|
for (; slot < titletag->n_entries; slot++)
|
||||||
|
if (titletag->titleId[slot] == titleId) break;
|
||||||
|
if (slot >= titletag->n_entries)
|
||||||
|
titletag->n_entries = slot + 1;
|
||||||
|
|
||||||
|
TitleTagEntry* ttag = &(titletag->tag[slot]);
|
||||||
|
titletag->titleId[slot] = titleId;
|
||||||
|
memset(ttag, 0, sizeof(TitleTagEntry));
|
||||||
|
memcpy(ttag->magic, "PREP", 4);
|
||||||
|
ttag->year = 2000;
|
||||||
|
ttag->month = 1;
|
||||||
|
ttag->day = 1;
|
||||||
|
ttag->country_code = 1;
|
||||||
|
|
||||||
|
// write back to system (warning: no write protection checks here)
|
||||||
|
u32 size = WriteDisaDiffIvfcLvl4(path, NULL, TITLETAG_AREA_OFFSET, sizeof(TitleTag), titletag);
|
||||||
|
FixFileCmac(path, false);
|
||||||
|
|
||||||
|
free(titletag);
|
||||||
|
return (size == sizeof(TitleTag)) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 SetupSeedSystemCrypto(u64 titleId, u32 hash_seed, bool to_emunand) {
|
||||||
|
// attempt to find the seed inside the seeddb.bin support file
|
||||||
|
SeedInfo* seeddb = (SeedInfo*) malloc(STD_BUFFER_SIZE);
|
||||||
|
if (!seeddb) return 1;
|
||||||
|
|
||||||
|
size_t len = LoadSupportFile(SEEDINFO_NAME, seeddb, STD_BUFFER_SIZE);
|
||||||
|
if (len && (seeddb->n_entries <= (len - 16) / 32)) { // check filesize / seeddb size
|
||||||
|
for (u32 s = 0; s < seeddb->n_entries; s++) {
|
||||||
|
if (titleId != seeddb->entries[s].titleId)
|
||||||
|
continue;
|
||||||
|
// found a candidate, hash and verify it
|
||||||
|
u8 lseed[16+8] __attribute__((aligned(4))) = { 0 }; // seed plus title ID for easy validation
|
||||||
|
u32 sha256sum[8];
|
||||||
|
memcpy(lseed+16, &titleId, 8);
|
||||||
|
memcpy(lseed, &(seeddb->entries[s].seed), sizeof(Seed));
|
||||||
|
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||||
|
u32 res = 0; // assuming the installed seed to be correct
|
||||||
|
if (hash_seed == sha256sum[0]) {
|
||||||
|
// found, install it
|
||||||
|
seeddb->n_entries = 1;
|
||||||
|
seeddb->entries[0].titleId = titleId;
|
||||||
|
memcpy(&(seeddb->entries[0].seed), lseed, sizeof(Seed));
|
||||||
|
res = InstallSeedDbToSystem(seeddb, to_emunand);
|
||||||
|
}
|
||||||
|
free(seeddb);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(seeddb);
|
||||||
|
return 1;
|
||||||
|
}
|
41
arm9/source/game/seedsave.h
Normal file
41
arm9/source/game/seedsave.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "disadiff.h"
|
||||||
|
|
||||||
|
#define SEEDINFO_NAME "seeddb.bin"
|
||||||
|
#define SEEDINFO_SIZE(sdb) (16 + ((sdb)->n_entries * sizeof(SeedInfoEntry)))
|
||||||
|
|
||||||
|
#define SEEDSAVE_MAX_ENTRIES 2000
|
||||||
|
#define SEEDSAVE_AREA_OFFSET 0x3000
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u8 byte[16];
|
||||||
|
} PACKED_STRUCT Seed;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 titleId;
|
||||||
|
Seed seed;
|
||||||
|
u8 reserved[8];
|
||||||
|
} PACKED_STRUCT SeedInfoEntry;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 n_entries;
|
||||||
|
u8 padding[12];
|
||||||
|
SeedInfoEntry entries[SEEDSAVE_MAX_ENTRIES]; // this number is only a placeholder
|
||||||
|
} PACKED_STRUCT SeedInfo;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 unknown0;
|
||||||
|
u32 n_entries;
|
||||||
|
u8 unknown1[0x1000 - 0x8];
|
||||||
|
u64 titleId[SEEDSAVE_MAX_ENTRIES];
|
||||||
|
Seed seed[SEEDSAVE_MAX_ENTRIES];
|
||||||
|
} PACKED_STRUCT SeedDb;
|
||||||
|
|
||||||
|
u32 GetSeedPath(char* path, const char* drv);
|
||||||
|
u32 FindSeed(u8* seed, u64 titleId, u32 hash_seed);
|
||||||
|
u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry);
|
||||||
|
u32 InstallSeedDbToSystem(SeedInfo* seed_info, bool to_emunand);
|
||||||
|
u32 SetupSeedPrePurchase(u64 titleId, bool to_emunand);
|
||||||
|
u32 SetupSeedSystemCrypto(u64 titleId, u32 hash_seed, bool to_emunand);
|
@ -2112,7 +2112,7 @@ u32 HomeMoreMenu(char* current_path) {
|
|||||||
bool seed_sys = false;
|
bool seed_sys = false;
|
||||||
bool seed_emu = false;
|
bool seed_emu = false;
|
||||||
if (BuildSeedInfo(NULL, false) == 0) {
|
if (BuildSeedInfo(NULL, false) == 0) {
|
||||||
ShowString("Building " SEEDDB_NAME "...");
|
ShowString("Building " SEEDINFO_NAME "...");
|
||||||
seed_sys = (BuildSeedInfo("1:", false) == 0);
|
seed_sys = (BuildSeedInfo("1:", false) == 0);
|
||||||
seed_emu = (BuildSeedInfo("4:", false) == 0);
|
seed_emu = (BuildSeedInfo("4:", false) == 0);
|
||||||
if (!seed_sys || BuildSeedInfo(NULL, true) != 0)
|
if (!seed_sys || BuildSeedInfo(NULL, true) != 0)
|
||||||
@ -2121,7 +2121,7 @@ u32 HomeMoreMenu(char* current_path) {
|
|||||||
ShowPrompt(false, "Built in " OUTPUT_PATH ":\n \n%18.18-s %s\n%18.18-s %s\n%18.18-s %s",
|
ShowPrompt(false, "Built in " OUTPUT_PATH ":\n \n%18.18-s %s\n%18.18-s %s\n%18.18-s %s",
|
||||||
TIKDB_NAME_ENC, tik_enc_sys ? tik_enc_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed",
|
TIKDB_NAME_ENC, tik_enc_sys ? tik_enc_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed",
|
||||||
TIKDB_NAME_DEC, tik_dec_sys ? tik_dec_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed",
|
TIKDB_NAME_DEC, tik_dec_sys ? tik_dec_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed",
|
||||||
SEEDDB_NAME, seed_sys ? seed_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed");
|
SEEDINFO_NAME, seed_sys ? seed_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed");
|
||||||
GetDirContents(current_dir, current_path);
|
GetDirContents(current_dir, current_path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1589,6 +1589,7 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
|||||||
bool sdtie = ((*drv == 'A') || (*drv == 'B'));
|
bool sdtie = ((*drv == 'A') || (*drv == 'B'));
|
||||||
bool syscmd = (((*drv == '1') || (*drv == '4')) ||
|
bool syscmd = (((*drv == '1') || (*drv == '4')) ||
|
||||||
(((*drv == '2') || (*drv == '5')) && (title_id[3] != 0x04)));
|
(((*drv == '2') || (*drv == '5')) && (title_id[3] != 0x04)));
|
||||||
|
bool to_emunand = ((*drv == 'B') || (*drv == '4') || (*drv == '5'));
|
||||||
|
|
||||||
char path_titledb[32];
|
char path_titledb[32];
|
||||||
char path_ticketdb[32];
|
char path_ticketdb[32];
|
||||||
@ -1662,6 +1663,10 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
|||||||
(CreateSaveData(drv, tid64, "banner.sav", sizeof(TwlIconData), false) != 0))
|
(CreateSaveData(drv, tid64, "banner.sav", sizeof(TwlIconData), false) != 0))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
// install seed to system (if available)
|
||||||
|
if (ncch && (SetupSystemForNcch(ncch, to_emunand) != 0))
|
||||||
|
return 1;
|
||||||
|
|
||||||
// write ticket and title databases
|
// write ticket and title databases
|
||||||
// ensure remounting the old mount path
|
// ensure remounting the old mount path
|
||||||
char path_store[256] = { 0 };
|
char path_store[256] = { 0 };
|
||||||
@ -3081,7 +3086,7 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) {
|
|||||||
|
|
||||||
u32 BuildSeedInfo(const char* path, bool dump) {
|
u32 BuildSeedInfo(const char* path, bool dump) {
|
||||||
static SeedInfo* seed_info = NULL;
|
static SeedInfo* seed_info = NULL;
|
||||||
const char* path_out = OUTPUT_PATH "/" SEEDDB_NAME;
|
const char* path_out = OUTPUT_PATH "/" SEEDINFO_NAME;
|
||||||
const char* path_in = path;
|
const char* path_in = path;
|
||||||
u32 inputtype = 0; // 0 -> none, 1 -> seeddb.bin, 2 -> seed system save
|
u32 inputtype = 0; // 0 -> none, 1 -> seeddb.bin, 2 -> seed system save
|
||||||
|
|
||||||
@ -3106,16 +3111,7 @@ u32 BuildSeedInfo(const char* path, bool dump) {
|
|||||||
|
|
||||||
char path_str[128];
|
char path_str[128];
|
||||||
if (path_in && (strnlen(path_in, 16) == 2)) { // when only a drive is given...
|
if (path_in && (strnlen(path_in, 16) == 2)) { // when only a drive is given...
|
||||||
// grab the key Y from movable.sed
|
if (GetSeedPath(path_str, path_in) != 0) return 1;
|
||||||
u8 movable_keyy[16] __attribute__((aligned(4)));
|
|
||||||
snprintf(path_str, 128, "%s/private/movable.sed", path_in);
|
|
||||||
if (fvx_qread(path_str, movable_keyy, 0x110, 0x10, NULL) != FR_OK)
|
|
||||||
return 1;
|
|
||||||
// build the seed save path
|
|
||||||
u32 sha256sum[8];
|
|
||||||
sha_quick(sha256sum, movable_keyy, 0x10, SHA256_MODE);
|
|
||||||
snprintf(path_str, 128, "%s/data/%08lX%08lX%08lX%08lX/sysdata/0001000F/00000000",
|
|
||||||
path_in, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
|
||||||
path_in = path_str;
|
path_in = path_str;
|
||||||
inputtype = 2;
|
inputtype = 2;
|
||||||
}
|
}
|
||||||
@ -3126,7 +3122,7 @@ u32 BuildSeedInfo(const char* path, bool dump) {
|
|||||||
|
|
||||||
UINT br;
|
UINT br;
|
||||||
if ((fvx_qread(path_in, seed_info_merge, 0, STD_BUFFER_SIZE, &br) != FR_OK) ||
|
if ((fvx_qread(path_in, seed_info_merge, 0, STD_BUFFER_SIZE, &br) != FR_OK) ||
|
||||||
(SEEDDB_SIZE(seed_info_merge) != br)) {
|
(SEEDINFO_SIZE(seed_info_merge) != br)) {
|
||||||
free(seed_info_merge);
|
free(seed_info_merge);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -3135,27 +3131,27 @@ u32 BuildSeedInfo(const char* path, bool dump) {
|
|||||||
u32 n_entries = seed_info_merge->n_entries;
|
u32 n_entries = seed_info_merge->n_entries;
|
||||||
SeedInfoEntry* seed = seed_info_merge->entries;
|
SeedInfoEntry* seed = seed_info_merge->entries;
|
||||||
for (u32 i = 0; i < n_entries; i++, seed++) {
|
for (u32 i = 0; i < n_entries; i++, seed++) {
|
||||||
if (SEEDDB_SIZE(seed_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
if (SEEDINFO_SIZE(seed_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
||||||
AddSeedToDb(seed_info, seed); // ignore result
|
AddSeedToDb(seed_info, seed); // ignore result
|
||||||
}
|
}
|
||||||
|
|
||||||
free(seed_info_merge);
|
free(seed_info_merge);
|
||||||
} else if (inputtype == 2) { // seed system save input
|
} else if (inputtype == 2) { // seed system save input
|
||||||
u8* seedsave = (u8*) malloc(SEEDSAVE_AREA_SIZE);
|
SeedDb* seedsave = (SeedDb*) malloc(sizeof(SeedDb));
|
||||||
if (!seedsave) return 1;
|
if (!seedsave) return 1;
|
||||||
|
|
||||||
if (ReadDisaDiffIvfcLvl4(path_in, NULL, SEEDSAVE_AREA_OFFSET, SEEDSAVE_AREA_SIZE, seedsave) != SEEDSAVE_AREA_SIZE) {
|
if ((ReadDisaDiffIvfcLvl4(path_in, NULL, SEEDSAVE_AREA_OFFSET, sizeof(SeedDb), seedsave) != sizeof(SeedDb)) ||
|
||||||
|
(seedsave->n_entries >= SEEDSAVE_MAX_ENTRIES)) {
|
||||||
free(seedsave);
|
free(seedsave);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SeedInfoEntry seed = { 0 };
|
SeedInfoEntry seed = { 0 };
|
||||||
for (u32 s = 0; s < SEEDSAVE_MAX_ENTRIES; s++) {
|
for (u32 s = 0; s < seedsave->n_entries; s++) {
|
||||||
seed.titleId = getle64(seedsave + (s*8));
|
seed.titleId = seedsave->titleId[s];
|
||||||
memcpy(seed.seed, seedsave + (SEEDSAVE_MAX_ENTRIES*8) + (s*16), 16);
|
memcpy(&(seed.seed), &(seedsave->seed[s]), sizeof(Seed));
|
||||||
if (((seed.titleId >> 32) != 0x00040000) ||
|
if ((seed.titleId >> 32) != 0x00040000) continue;
|
||||||
(!getle64(seed.seed) && !getle64(seed.seed + 8))) continue;
|
if (SEEDINFO_SIZE(seed_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
||||||
if (SEEDDB_SIZE(seed_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
|
||||||
AddSeedToDb(seed_info, &seed); // ignore result
|
AddSeedToDb(seed_info, &seed); // ignore result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3163,7 +3159,7 @@ u32 BuildSeedInfo(const char* path, bool dump) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dump) {
|
if (dump) {
|
||||||
u32 dump_size = SEEDDB_SIZE(seed_info);
|
u32 dump_size = SEEDINFO_SIZE(seed_info);
|
||||||
u32 ret = 0;
|
u32 ret = 0;
|
||||||
|
|
||||||
if (dump_size > 16) {
|
if (dump_size > 16) {
|
||||||
|
@ -1363,8 +1363,8 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
|||||||
(BuildTitleKeyInfo(NULL, tik_dec, true) == 0))
|
(BuildTitleKeyInfo(NULL, tik_dec, true) == 0))
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
} else if (strncasecmp(argv[0], SEEDDB_NAME, _ARG_MAX_LEN) == 0) {
|
} else if (strncasecmp(argv[0], SEEDINFO_NAME, _ARG_MAX_LEN) == 0) {
|
||||||
if (flags & _FLG('w')) fvx_unlink(OUTPUT_PATH "/" SEEDDB_NAME);
|
if (flags & _FLG('w')) fvx_unlink(OUTPUT_PATH "/" SEEDINFO_NAME);
|
||||||
if (BuildSeedInfo(NULL, false) == 0) {
|
if (BuildSeedInfo(NULL, false) == 0) {
|
||||||
ShowString("Building to " OUTPUT_PATH ":\n%s ...", argv[0]);
|
ShowString("Building to " OUTPUT_PATH ":\n%s ...", argv[0]);
|
||||||
if (((BuildSeedInfo("1:", false) == 0) ||
|
if (((BuildSeedInfo("1:", false) == 0) ||
|
||||||
|
Loading…
x
Reference in New Issue
Block a user