mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 05:32:47 +00:00
241 lines
8.6 KiB
C
241 lines
8.6 KiB
C
|
#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;
|
||
|
}
|