1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 10:16:39 +00:00

Gamecard rewrite.

This commit is contained in:
Pablo Curiel 2020-04-15 01:59:12 -04:00
parent e5a4532a63
commit 65e40e7600
14 changed files with 1244 additions and 1331 deletions

View file

@ -1,4 +1,19 @@
# nxdumptool
todo:
hfs0 methods
tik gamecard
# nxdumptool
<img width="200" src="icon.jpg">
Nintendo Switch Dump Tool

File diff suppressed because it is too large Load diff

View file

@ -1,65 +0,0 @@
#pragma once
#ifndef __KEYS_H__
#define __KEYS_H__
#include <switch.h>
#include "nca.h"
#define FS_TID (u64)0x0100000000000000
#define SEG_TEXT BIT(0)
#define SEG_RODATA BIT(1)
#define SEG_DATA BIT(2)
#define ETICKET_DEVKEY_RSA_CTR_SIZE 0x10
#define ETICKET_DEVKEY_RSA_OFFSET ETICKET_DEVKEY_RSA_CTR_SIZE
#define ETICKET_DEVKEY_RSA_SIZE 0x230
#define SIGTYPE_RSA2048_SHA1 (u32)0x10001
#define SIGTYPE_RSA2048_SHA256 (u32)0x10004
typedef struct {
u64 titleID;
u8 mask;
u8 *data;
u64 dataSize;
} keyLocation;
typedef struct {
char name[128];
u8 hash[SHA256_HASH_SIZE];
u64 size;
} keyInfo;
typedef struct {
u16 memory_key_cnt; /* Key counter for keys retrieved from memory. */
u16 ext_key_cnt; /* Key counter for keys retrieved from keysfile. */
u32 total_key_cnt; /* Total key counter. */
// Needed to decrypt the NCA header using AES-128-XTS
u8 header_kek_source[0x10]; /* Seed for header kek. Retrieved from the .rodata section in the FS sysmodule. */
u8 header_key_source[0x20]; /* Seed for NCA header key. Retrieved from the .data section in the FS sysmodule. */
u8 header_kek[0x10]; /* NCA header kek. Generated from header_kek_source. */
u8 header_key[0x20]; /* NCA header key. Generated from header_kek and header_key_source. */
// Needed to derive the KAEK used to decrypt the NCA key area
u8 key_area_key_application_source[0x10]; /* Seed for kaek 0. Retrieved from the .rodata section in the FS sysmodule. */
u8 key_area_key_ocean_source[0x10]; /* Seed for kaek 1. Retrieved from the .rodata section in the FS sysmodule. */
u8 key_area_key_system_source[0x10]; /* Seed for kaek 2. Retrieved from the .rodata section in the FS sysmodule. */
// Needed to decrypt the title key block from an eTicket. Retrieved from the Lockpick_RCM keys file.
u8 eticket_rsa_kek[0x10]; /* eTicket RSA kek. */
u8 titlekeks[0x20][0x10]; /* Title key encryption keys. */
// Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file.
u8 key_area_keys[0x20][3][0x10]; /* Key area encryption keys. */
} nca_keyset_t;
bool loadMemoryKeys();
bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out);
bool loadExternalKeys();
int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key);
bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys);
#endif

View file

@ -6,18 +6,10 @@
#include "es.h"
#include "service_guard.h"
static Service g_esSrv;
static Service g_esSrv = {0};
NX_GENERATE_SERVICE_GUARD(es);
Result _esInitialize() {
return smGetService(&g_esSrv, "es");
}
void _esCleanup() {
serviceClose(&g_esSrv);
}
Result esCountCommonTicket(s32 *out_count)
{
struct {
@ -50,7 +42,7 @@ Result esListCommonTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 cou
Result rc = serviceDispatchInOut(&g_esSrv, 11, *out_entries_written, out,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out_ids, count * sizeof(FsRightsId) } },
.buffers = { { out_ids, count * sizeof(FsRightsId) } }
);
if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written;
@ -66,10 +58,20 @@ Result esListPersonalizedTicket(s32 *out_entries_written, FsRightsId *out_ids, s
Result rc = serviceDispatchInOut(&g_esSrv, 12, *out_entries_written, out,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out_ids, count * sizeof(FsRightsId) } },
.buffers = { { out_ids, count * sizeof(FsRightsId) } }
);
if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written;
return rc;
}
NX_INLINE Result _esInitialize(void)
{
return smGetService(&g_esSrv, "es");
}
static void _esCleanup(void)
{
serviceClose(&g_esSrv);
}

View file

@ -5,8 +5,8 @@
#include <switch.h>
Result esInitialize();
void esExit();
Result esInitialize(void);
void esExit(void);
Result esCountCommonTicket(s32 *out_count);
Result esCountPersonalizedTicket(s32 *out_count);

View file

@ -4,13 +4,13 @@
#include "fs_ext.h"
// IFileSystemProxy
/* IFileSystemProxy */
Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32 partition)
{
struct {
u32 handle;
FsGameCardHandle handle;
u32 partition;
} in = { handle->value, partition };
} in = { *handle, partition };
return serviceDispatchIn(fsGetServiceSession(), 30, in,
.out_num_objects = 1,
@ -26,22 +26,37 @@ Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out)
);
}
// IDeviceOperator
/* IDeviceOperator */
Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id)
{
struct {
u32 handle;
} in = { handle->value };
FsGameCardHandle handle;
} in = { *handle };
struct {
u32 title_ver;
u32 title_version;
u64 title_id;
} out;
Result rc = serviceDispatchInOut(&d->s, 203, in, out);
if (R_SUCCEEDED(rc) && out_title_version) *out_title_version = out.title_ver;
if (R_SUCCEEDED(rc) && out_title_version) *out_title_version = out.title_version;
if (R_SUCCEEDED(rc) && out_title_id) *out_title_id = out.title_id;
return rc;
}
Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const FsGameCardHandle *handle, FsGameCardCertificate *out)
{
struct {
FsGameCardHandle handle;
u64 buf_size;
} in = { *handle, sizeof(FsGameCardCertificate) };
Result rc = serviceDispatchIn(&d->s, 206, in,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out, sizeof(FsGameCardCertificate) } }
);
return rc;
}

View file

@ -6,11 +6,24 @@
#include <switch/types.h>
#include <switch/services/fs.h>
// IFileSystemProxy
/// Located at offset 0x7000 in the gamecard image.
typedef struct {
u8 signature[0x100]; ///< RSA-2048 PKCS #1 signature over the rest of the data.
u32 magic; ///< "CERT"
u8 reserved_1[0x4];
u8 kek_index;
u8 reserved_2[0x7];
u8 device_id[0x10];
u8 reserved_3[0x10];
u8 encrypted_data[0xD0];
} FsGameCardCertificate;
/* IFileSystemProxy */
Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32 partition);
Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out);
// IDeviceOperator
/* IDeviceOperator */
Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id);
Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const FsGameCardHandle *handle, FsGameCardCertificate *out);
#endif /* __FS_EXT_H__ */

763
source/new/gamecard.c Normal file
View file

@ -0,0 +1,763 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <threads.h>
#include "gamecard.h"
#include "service_guard.h"
#include "utils.h"
#define GAMECARD_ACCESS_WAIT_TIME 3 /* Seconds */
#define GAMECARD_UPDATE_TID (u64)0x0100000000000816
#define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB */
#define GAMECARD_ECC_BLOCK_SIZE 0x200
#define GAMECARD_ECC_DATA_SIZE 0x24
typedef struct {
u64 offset; ///< Relative to the start of the gamecard header.
u64 size; ///< Whole partition size.
u8 *header; ///< GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table.
} GameCardHashFileSystemPartitionInfo;
static FsDeviceOperator g_deviceOperator = {0};
static FsEventNotifier g_gameCardEventNotifier = {0};
static Event g_gameCardKernelEvent = {0};
static bool g_openDeviceOperator = false, g_openEventNotifier = false, g_loadKernelEvent = false;
static thrd_t g_gameCardDetectionThread;
static UEvent g_gameCardDetectionThreadExitEvent = {0};
static mtx_t g_gameCardSharedDataMutex;
static bool g_gameCardDetectionThreadCreated = false, g_gameCardInserted = false, g_gameCardInfoLoaded = false;
static FsGameCardHandle g_gameCardHandle = {0};
static FsStorage g_gameCardStorageNormal = {0}, g_gameCardStorageSecure = {0};
static u8 *g_gameCardReadBuf = NULL;
static GameCardHeader g_gameCardHeader = {0};
static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0;
static u8 *g_gameCardHfsRootHeader = NULL; /* GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table */
static GameCardHashFileSystemPartitionInfo *g_gameCardHfsPartitions = NULL;
static bool gamecardCreateDetectionThread(void);
static void gamecardDestroyDetectionThread(void);
static int gamecardDetectionThreadFunc(void *arg);
static inline bool gamecardCheckIfInserted(void);
static void gamecardLoadInfo(void);
static void gamecardFreeInfo(void);
static bool gamecardGetHandle(void);
static inline void gamecardCloseHandle(void);
static bool gamecardOpenStorageAreas(void);
static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock);
static void gamecardCloseStorageAreas(void);
static bool gamecardGetSizesFromStorageAreas(void);
/* Service guard used to generate thread-safe initialize + exit functions */
NX_GENERATE_SERVICE_GUARD(gamecard);
bool gamecardCheckReadyStatus(void)
{
mtx_lock(&g_gameCardSharedDataMutex);
bool status = (g_gameCardInserted && g_gameCardInfoLoaded);
mtx_unlock(&g_gameCardSharedDataMutex);
return status;
}
bool gamecardStorageRead(void *out, u64 out_size, u64 offset)
{
return _gamecardStorageRead(out, out_size, offset, true);
}
bool gamecardGetHeader(GameCardHeader *out)
{
bool ret = false;
mtx_lock(&g_gameCardSharedDataMutex);
if (g_gameCardInserted && g_gameCardInfoLoaded && out)
{
memcpy(out, &g_gameCardHeader, sizeof(GameCardHeader));
ret = true;
}
mtx_unlock(&g_gameCardSharedDataMutex);
return ret;
}
bool gamecardGetTotalRomSize(u64 *out)
{
bool ret = false;
mtx_lock(&g_gameCardSharedDataMutex);
if (g_gameCardInserted && g_gameCardInfoLoaded && out)
{
*out = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize);
ret = true;
}
mtx_unlock(&g_gameCardSharedDataMutex);
return ret;
}
bool gamecardGetTrimmedRomSize(u64 *out)
{
bool ret = false;
mtx_lock(&g_gameCardSharedDataMutex);
if (g_gameCardInserted && g_gameCardInfoLoaded && out)
{
*out = (sizeof(GameCardHeader) + ((u64)g_gameCardHeader.valid_data_end_address * GAMECARD_MEDIA_UNIT_SIZE));
ret = true;
}
mtx_unlock(&g_gameCardSharedDataMutex);
return ret;
}
bool gamecardGetCertificate(FsGameCardCertificate *out)
{
Result rc = 0;
bool ret = false;
mtx_lock(&g_gameCardSharedDataMutex);
if (g_gameCardInserted && g_gameCardHandle.value && out)
{
rc = fsDeviceOperatorGetGameCardDeviceCertificate(&g_deviceOperator, &g_gameCardHandle, out);
if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X)", rc);
ret = R_SUCCEEDED(rc);
}
mtx_unlock(&g_gameCardSharedDataMutex);
return ret;
}
bool gamecardGetBundledFirmwareUpdateVersion(u32 *out)
{
Result rc = 0;
u64 update_id = 0;
u32 update_version = 0;
bool ret = false;
mtx_lock(&g_gameCardSharedDataMutex);
if (g_gameCardInserted && g_gameCardHandle.value && out)
{
rc = fsDeviceOperatorUpdatePartitionInfo(&g_deviceOperator, &g_gameCardHandle, &update_version, &update_id);
if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorUpdatePartitionInfo failed! (0x%08X)", rc);
ret = (R_SUCCEEDED(rc) && update_id == GAMECARD_UPDATE_TID);
if (ret) *out = update_version;
}
mtx_unlock(&g_gameCardSharedDataMutex);
return ret;
}
NX_INLINE Result _gamecardInitialize(void)
{
Result rc = 0;
/* Allocate memory for the gamecard read buffer */
g_gameCardReadBuf = malloc(GAMECARD_READ_BUFFER_SIZE);
if (!g_gameCardReadBuf)
{
LOGFILE("Unable to allocate memory for the gamecard read buffer!");
rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed);
goto out;
}
/* Open device operator */
rc = fsOpenDeviceOperator(&g_deviceOperator);
if (R_FAILED(rc))
{
LOGFILE("fsOpenDeviceOperator failed! (0x%08X)", rc);
goto out;
}
g_openDeviceOperator = true;
/* Open gamecard detection event notifier */
rc = fsOpenGameCardDetectionEventNotifier(&g_gameCardEventNotifier);
if (R_FAILED(rc))
{
LOGFILE("fsOpenGameCardDetectionEventNotifier failed! (0x%08X)", rc);
goto out;
}
g_openEventNotifier = true;
/* Retrieve gamecard detection kernel event */
rc = fsEventNotifierGetEventHandle(&g_gameCardEventNotifier, &g_gameCardKernelEvent, true);
if (R_FAILED(rc))
{
LOGFILE("fsEventNotifierGetEventHandle failed! (0x%08X)", rc);
goto out;
}
g_loadKernelEvent = true;
/* Create usermode exit event */
ueventCreate(&g_gameCardDetectionThreadExitEvent, false);
/* Create gamecard detection thread */
g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread();
if (!g_gameCardDetectionThreadCreated)
{
LOGFILE("Failed to create gamecard detection thread!");
rc = MAKERESULT(Module_Libnx, LibnxError_IoError);
}
out:
return rc;
}
static void _gamecardCleanup(void)
{
/* Destroy gamecard detection thread */
if (g_gameCardDetectionThreadCreated)
{
gamecardDestroyDetectionThread();
g_gameCardDetectionThreadCreated = false;
}
/* Close gamecard detection kernel event */
if (g_loadKernelEvent)
{
eventClose(&g_gameCardKernelEvent);
g_loadKernelEvent = false;
}
/* Close gamecard detection event notifier */
if (g_openEventNotifier)
{
fsEventNotifierClose(&g_gameCardEventNotifier);
g_openEventNotifier = false;
}
/* Close device operator */
if (g_openDeviceOperator)
{
fsDeviceOperatorClose(&g_deviceOperator);
g_openDeviceOperator = false;
}
/* Free gamecard read buffer */
if (g_gameCardReadBuf)
{
free(g_gameCardReadBuf);
g_gameCardReadBuf = NULL;
}
}
static bool gamecardCreateDetectionThread(void)
{
if (mtx_init(&g_gameCardSharedDataMutex, mtx_plain) != thrd_success)
{
LOGFILE("Failed to initialize gamecard shared data mutex!");
return false;
}
if (thrd_create(&g_gameCardDetectionThread, gamecardDetectionThreadFunc, NULL) != thrd_success)
{
LOGFILE("Failed to create gamecard detection thread!");
mtx_destroy(&g_gameCardSharedDataMutex);
return false;
}
return true;
}
static void gamecardDestroyDetectionThread(void)
{
/* Signal the exit event to terminate the gamecard detection thread */
ueventSignal(&g_gameCardDetectionThreadExitEvent);
/* Wait for the gamecard detection thread to exit */
thrd_join(g_gameCardDetectionThread, NULL);
/* Destroy mutex */
mtx_destroy(&g_gameCardSharedDataMutex);
}
static int gamecardDetectionThreadFunc(void *arg)
{
(void)arg;
Result rc = 0;
int idx = 0;
bool prev_status = false;
Waiter gamecard_event_waiter = waiterForEvent(&g_gameCardKernelEvent);
Waiter exit_event_waiter = waiterForUEvent(&g_gameCardDetectionThreadExitEvent);
mtx_lock(&g_gameCardSharedDataMutex);
/* Retrieve initial gamecard insertion status */
g_gameCardInserted = prev_status = gamecardCheckIfInserted();
/* Load gamecard info right away if a gamecard is inserted and if a handle can be retrieved */
if (g_gameCardInserted && gamecardGetHandle()) gamecardLoadInfo();
mtx_unlock(&g_gameCardSharedDataMutex);
while(true)
{
/* Wait until an event is triggered */
rc = waitMulti(&idx, -1, gamecard_event_waiter, exit_event_waiter);
if (R_FAILED(rc)) continue;
/* Exit event triggered */
if (idx == 1) break;
/* Retrieve current gamecard insertion status */
/* Only proceed if we're dealing with a status change */
mtx_lock(&g_gameCardSharedDataMutex);
g_gameCardInserted = gamecardCheckIfInserted();
if (!prev_status && g_gameCardInserted)
{
/* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules */
SLEEP(GAMECARD_ACCESS_WAIT_TIME);
/* Load gamecard info if a gamecard is inserted and if a handle can be retrieved */
if (gamecardGetHandle()) gamecardLoadInfo();
} else {
/* Free gamecard info and close gamecard handle */
gamecardFreeInfo();
gamecardCloseHandle();
}
prev_status = g_gameCardInserted;
mtx_unlock(&g_gameCardSharedDataMutex);
}
/* Free gamecard info and close gamecard handle */
mtx_lock(&g_gameCardSharedDataMutex);
gamecardFreeInfo();
gamecardCloseHandle();
g_gameCardInserted = false;
mtx_unlock(&g_gameCardSharedDataMutex);
return 0;
}
static inline bool gamecardCheckIfInserted(void)
{
bool inserted = false;
Result rc = fsDeviceOperatorIsGameCardInserted(&g_deviceOperator, &inserted);
if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorIsGameCardInserted failed! (0x%08X)", rc);
return (R_SUCCEEDED(rc) && inserted);
}
static void gamecardLoadInfo(void)
{
if (g_gameCardInfoLoaded) return;
GameCardHashFileSystemHeader *fs_header = NULL;
GameCardHashFileSystemEntry *fs_entry = NULL;
/* Open gamecard storage areas */
if (!gamecardOpenStorageAreas())
{
LOGFILE("Failed to open gamecard storage areas!");
goto out;
}
/* Read gamecard header */
if (!_gamecardStorageRead(&g_gameCardHeader, sizeof(GameCardHeader), 0, false))
{
LOGFILE("Failed to read gamecard header!");
goto out;
}
/* Check magic word from gamecard header */
if (__builtin_bswap32(g_gameCardHeader.magic) != GAMECARD_HEAD_MAGIC)
{
LOGFILE("Invalid gamecard header magic word! (0x%08X)", __builtin_bswap32(g_gameCardHeader.magic));
goto out;
}
if (utilsGetCustomFirmwareType() == UtilsCustomFirmwareType_SXOS)
{
/* Total size for the secure storage area is maxed out under SX OS */
/* Let's try to calculate it manually */
u64 capacity = gamecardGetCapacity(&g_gameCardHeader);
if (!capacity)
{
LOGFILE("Invalid gamecard capacity value! (0x%02X)", g_gameCardHeader.rom_size);
goto out;
}
g_gameCardStorageSecureAreaSize = ((capacity - ((capacity / GAMECARD_ECC_BLOCK_SIZE) * GAMECARD_ECC_DATA_SIZE)) - g_gameCardStorageNormalAreaSize);
}
/* Allocate memory for the root hash FS header */
g_gameCardHfsRootHeader = calloc(g_gameCardHeader.partition_fs_header_size, sizeof(u8));
if (!g_gameCardHfsRootHeader)
{
LOGFILE("Unable to allocate memory for the root hash FS header!");
goto out;
}
/* Read root hash FS header */
if (!_gamecardStorageRead(g_gameCardHfsRootHeader, g_gameCardHeader.partition_fs_header_size, g_gameCardHeader.partition_fs_header_address, false))
{
LOGFILE("Failed to read root hash FS header from offset 0x%lX!", g_gameCardHeader.partition_fs_header_address);
goto out;
}
fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader;
if (__builtin_bswap32(fs_header->magic) != GAMECARD_HFS0_MAGIC)
{
LOGFILE("Invalid magic word in root hash FS header! (0x%08X)", __builtin_bswap32(fs_header->magic));
goto out;
}
if (!fs_header->entry_count || !fs_header->name_table_size || \
(sizeof(GameCardHashFileSystemHeader) + (fs_header->entry_count * sizeof(GameCardHashFileSystemEntry)) + fs_header->name_table_size) > g_gameCardHeader.partition_fs_header_size)
{
LOGFILE("Invalid file count and/or name table size in root hash FS header!");
goto out;
}
/* Allocate memory for the hash FS partitions info */
g_gameCardHfsPartitions = calloc(fs_header->entry_count, sizeof(GameCardHashFileSystemEntry));
if (!g_gameCardHfsPartitions)
{
LOGFILE("Unable to allocate memory for the hash FS partitions info!");
goto out;
}
/* Read hash FS partitions */
for(u32 i = 0; i < fs_header->entry_count; i++)
{
fs_entry = (GameCardHashFileSystemEntry*)(g_gameCardHfsRootHeader + sizeof(GameCardHashFileSystemHeader) + (i * sizeof(GameCardHashFileSystemEntry)));
if (!fs_entry->size)
{
LOGFILE("Invalid size for hash FS partition #%u!", i);
goto out;
}
g_gameCardHfsPartitions[i].offset = (g_gameCardHeader.partition_fs_header_address + g_gameCardHeader.partition_fs_header_size + fs_entry->offset);
g_gameCardHfsPartitions[i].size = fs_entry->size;
/* Partially read the current hash FS partition header */
GameCardHashFileSystemHeader partition_header = {0};
if (!_gamecardStorageRead(&partition_header, sizeof(GameCardHashFileSystemHeader), g_gameCardHfsPartitions[i].offset, false))
{
LOGFILE("Failed to partially read hash FS partition #%u header from offset 0x%lX!", i, g_gameCardHfsPartitions[i].offset);
goto out;
}
if (__builtin_bswap32(partition_header.magic) != GAMECARD_HFS0_MAGIC)
{
LOGFILE("Invalid magic word in hash FS partition #%u header! (0x%08X)", i, __builtin_bswap32(partition_header.magic));
goto out;
}
if (!partition_header.name_table_size)
{
LOGFILE("Invalid name table size in hash FS partition #%u header!", i);
goto out;
}
/* Calculate the full header size for the current hash FS partition */
u64 partition_header_size = (sizeof(GameCardHashFileSystemHeader) + (partition_header.entry_count * sizeof(GameCardHashFileSystemEntry)) + partition_header.name_table_size);
/* Allocate memory for the hash FS partition header */
g_gameCardHfsPartitions[i].header = calloc(partition_header_size, sizeof(u8));
if (!g_gameCardHfsPartitions[i].header)
{
LOGFILE("Unable to allocate memory for the hash FS partition #%u header!", i);
goto out;
}
/* Finally, read the full hash FS partition header */
if (!_gamecardStorageRead(g_gameCardHfsPartitions[i].header, partition_header_size, g_gameCardHfsPartitions[i].offset, false))
{
LOGFILE("Failed to read full hash FS partition #%u header from offset 0x%lX!", i, g_gameCardHfsPartitions[i].offset);
goto out;
}
}
g_gameCardInfoLoaded = true;
out:
if (!g_gameCardInfoLoaded) gamecardFreeInfo();
}
static void gamecardFreeInfo(void)
{
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
if (g_gameCardHfsRootHeader)
{
if (g_gameCardHfsPartitions)
{
GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader;
for(u32 i = 0; i < fs_header->entry_count; i++)
{
if (g_gameCardHfsPartitions[i].header) free(g_gameCardHfsPartitions[i].header);
}
}
free(g_gameCardHfsRootHeader);
g_gameCardHfsRootHeader = NULL;
}
if (g_gameCardHfsPartitions)
{
free(g_gameCardHfsPartitions);
g_gameCardHfsPartitions = NULL;
}
gamecardCloseStorageAreas();
g_gameCardInfoLoaded = false;
}
static bool gamecardGetHandle(void)
{
if (!g_gameCardInserted)
{
LOGFILE("Gamecard not inserted!");
return false;
}
if (g_gameCardInfoLoaded && g_gameCardHandle.value) return true;
Result rc1 = 0, rc2 = 0;
FsStorage tmp_storage = {0};
/* 10 tries */
for(u8 i = 0; i < 10; i++)
{
/* First try to open a gamecard storage area using the current gamecard handle */
rc1 = fsOpenGameCardStorage(&tmp_storage, &g_gameCardHandle, 0);
if (R_SUCCEEDED(rc1))
{
fsStorageClose(&tmp_storage);
break;
}
/* If the previous call failed, we may have an invalid handle, so let's close the current one and try to retrieve a new one */
gamecardCloseHandle();
rc2 = fsDeviceOperatorGetGameCardHandle(&g_deviceOperator, &g_gameCardHandle);
}
if (R_FAILED(rc1) || R_FAILED(rc2))
{
/* Close leftover gamecard handle */
gamecardCloseHandle();
if (R_FAILED(rc1)) LOGFILE("fsOpenGameCardStorage failed! (0x%08X)", rc1);
if (R_FAILED(rc2)) LOGFILE("fsDeviceOperatorGetGameCardHandle failed! (0x%08X)", rc2);
return false;
}
return true;
}
static inline void gamecardCloseHandle(void)
{
svcCloseHandle(g_gameCardHandle.value);
g_gameCardHandle.value = 0;
}
static bool gamecardOpenStorageAreas(void)
{
if (!g_gameCardInserted || !g_gameCardHandle.value)
{
LOGFILE("Invalid parameters!");
return false;
}
if (g_gamecardInfoLoaded && serviceIsActive(&(g_gameCardStorageNormal.s)) && serviceIsActive(&(g_gameCardStorageSecure.s))) return true;
gamecardCloseStorageAreas();
Result rc = 0;
bool success = false;
rc = fsOpenGameCardStorage(&g_gameCardStorageNormal, &g_gameCardHandle, 0);
if (R_FAILED(rc))
{
LOGFILE("fsOpenGameCardStorage failed! (0x%08X) (normal)", rc);
goto out;
}
rc = fsOpenGameCardStorage(&g_gameCardStorageSecure, &g_gameCardHandle, 1);
if (R_FAILED(rc))
{
LOGFILE("fsOpenGameCardStorage failed! (0x%08X) (secure)", rc);
goto out;
}
if (!gamecardGetSizesFromStorageAreas())
{
LOGFILE("Failed to retrieve sizes from storage areas!");
goto out;
}
success = true;
out:
if (!success) gamecardCloseStorageAreas();
return success;
}
static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock)
{
if (lock) mtx_lock(&g_gameCardSharedDataMutex);
bool success = false;
if (!g_gameCardInserted || !serviceIsActive(&(g_gameCardStorageNormal.s)) || !g_gameCardStorageNormalAreaSize || !serviceIsActive(&(g_gameCardStorageSecure.s)) || \
!g_gameCardStorageSecureAreaSize || !out || !out_size || offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || \
(offset + out_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize))
{
LOGFILE("Invalid parameters!");
goto out;
}
Result rc = 0;
u8 *out_u8 = (u8*)out;
/* Handle reads between the end of the normal storage area and the start of the secure storage area */
if (offset < g_gameCardStorageNormalAreaSize && (offset + out_size) > g_gameCardStorageNormalAreaSize)
{
/* Calculate normal storage area size difference */
u64 diff_size = (g_gameCardStorageNormalAreaSize - offset);
if (!_gamecardStorageRead(out_u8, diff_size, offset, false)) goto out;
/* Adjust variables to start reading right from the start of the secure storage area */
out_u8 += diff_size;
offset = g_gameCardStorageNormalAreaSize;
out_size -= diff_size;
}
/* Calculate appropiate storage area offset and retrieve the right storage area pointer */
const char *area = (offset < g_gameCardStorageNormalAreaSize ? "normal" : "secure");
u64 base_offset = (offset < g_gameCardStorageNormalAreaSize ? offset : (offset - g_gameCardStorageNormalAreaSize));
FsStorage *storage = (offset < g_gameCardStorageNormalAreaSize ? &g_gameCardStorageNormal : &g_gameCardStorageSecure);
if (!(base_offset % GAMECARD_MEDIA_UNIT_SIZE) && !(out_size % GAMECARD_MEDIA_UNIT_SIZE))
{
/* Optimization for reads that are already aligned to GAMECARD_MEDIA_UNIT_SIZE bytes */
rc = fsStorageRead(storage, base_offset, out_u8, out_size);
if (R_FAILED(rc))
{
LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned)", out_size, base_offset, area, rc);
goto out;
}
success = true;
} else {
/* Fix offset and/or size to avoid unaligned reads */
u64 block_start_offset = (base_offset - (base_offset % GAMECARD_MEDIA_UNIT_SIZE));
u64 block_end_offset = round_up(base_offset + out_size, GAMECARD_MEDIA_UNIT_SIZE);
u64 block_size = (block_end_offset - block_start_offset);
u64 chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? GAMECARD_READ_BUFFER_SIZE : block_size);
u64 out_chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? (GAMECARD_READ_BUFFER_SIZE - (base_offset - block_start_offset)) : out_size);
rc = fsStorageRead(storage, block_start_offset, g_gameCardReadBuf, chunk_size);
if (!R_FAILED(rc))
{
LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (unaligned)", chunk_size, block_start_offset, area, rc);
goto out;
}
memcpy(out_u8, g_gameCardReadBuf + (base_offset - block_start_offset), out_chunk_size);
success = (block_size > GAMECARD_READ_BUFFER_SIZE ? _gamecardStorageRead(out_u8 + out_chunk_size, out_size - out_chunk_size, base_offset + out_chunk_size, false) : true);
}
out:
if (lock) mtx_unlock(&g_gameCardSharedDataMutex);
return success;
}
static void gamecardCloseStorageAreas(void)
{
if (serviceIsActive(&(g_gameCardStorageNormal.s)))
{
fsStorageClose(&g_gameCardStorageNormal);
memset(&g_gameCardStorageNormal, 0, sizeof(FsStorage));
}
g_gameCardStorageNormalAreaSize = 0;
if (serviceIsActive(&(g_gameCardStorageSecure.s)))
{
fsStorageClose(&g_gameCardStorageSecure);
memset(&g_gameCardStorageSecure, 0, sizeof(FsStorage));
}
g_gameCardStorageSecureAreaSize = 0;
}
static bool gamecardGetSizesFromStorageAreas(void)
{
if (!g_gameCardInserted || !serviceIsActive(&(g_gameCardStorageNormal.s)) || !serviceIsActive(&(g_gameCardStorageSecure.s)))
{
LOGFILE("Invalid parameters!");
return false;
}
Result rc = 0;
rc = fsStorageGetSize(&g_gameCardStorageNormal, (s64*)&g_gameCardStorageNormalAreaSize);
if (R_FAILED(rc))
{
LOGFILE("fsStorageGetSize failed! (0x%08X) (normal)", rc);
return false;
}
rc = fsStorageGetSize(&g_gameCardStorageSecure, (s64*)&g_gameCardStorageSecureAreaSize);
if (R_FAILED(rc))
{
LOGFILE("fsStorageGetSize failed! (0x%08X) (secure)", rc);
g_gameCardStorageNormalAreaSize = 0;
return false;
}
return true;
}

174
source/new/gamecard.h Normal file
View file

@ -0,0 +1,174 @@
#pragma once
#ifndef __GAMECARD_H__
#define __GAMECARD_H__
#include "fs_ext.h"
#define GAMECARD_HEAD_MAGIC 0x48454144 /* "HEAD" */
#define GAMECARD_CERT_MAGIC 0x43455254 /* "CERT" */
#define GAMECARD_HFS0_MAGIC 0x48465330 /* "HFS0" */
#define GAMECARD_MEDIA_UNIT_SIZE 0x200
typedef enum {
GameCardKekIndex_Version0 = 0,
GameCardKekIndex_VersionForDev = 1
} GameCardKekIndex;
typedef struct {
u8 kek_index : 4; ///< GameCardKekIndex.
u8 titlekey_dec_index : 4;
} GameCardKeyFlags;
typedef enum {
GameCardRomSize_1GB = 0xFA,
GameCardRomSize_2GB = 0xF8,
GameCardRomSize_4GB = 0xF0,
GameCardRomSize_8GB = 0xE0,
GameCardRomSize_16GB = 0xE1,
GameCardRomSize_32GB = 0xE2
} GameCardRomSize;
typedef struct {
u8 autoboot : 1;
u8 history_erase : 1;
u8 repair_tool : 1;
u8 different_region_cup_to_terra_device : 1;
u8 different_region_cup_to_global_device : 1;
} GameCardFlags;
typedef enum {
GameCardSelSec_ForT1 = 0,
GameCardSelSec_ForT2 = 1
} GameCardSelSec;
typedef enum {
GameCardFwVersion_Dev = 0,
GameCardFwVersion_Prod = 1,
GameCardFwVersion_Since400NUP = 2
} GameCardFwVersion;
typedef enum {
GameCardAccCtrl_25MHz = 0xA10011,
GameCardAccCtrl_50MHz = 0xA10010
} GameCardAccCtrl;
typedef enum {
GameCardCompatibilityType_Normal = 0,
GameCardCompatibilityType_Terra = 1
} GameCardCompatibilityType;
typedef struct {
u64 fw_version; ///< GameCardFwVersion.
u32 acc_ctrl; ///< GameCardAccCtrl.
u32 wait_1_time_read; ///< Always 0x1388.
u32 wait_2_time_read; ///< Always 0.
u32 wait_1_time_write; ///< Always 0.
u32 wait_2_time_write; ///< Always 0.
u32 fw_mode;
u32 upp_version;
u8 compatibility_type; ///< GameCardCompatibilityType.
u8 reserved_1[0x3];
u64 upp_hash;
u64 upp_id; ///< Must match GAMECARD_UPDATE_TID.
u8 reserved_2[0x38];
} GameCardExtendedHeader;
typedef struct {
u8 signature[0x100]; ///< RSA-2048 PKCS #1 signature over the rest of the header.
u32 magic; ///< "HEAD".
u32 secure_area_start_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks.
u32 backup_area_start_address; ///< Always 0xFFFFFFFF.
GameCardKeyFlags key_flags;
u8 rom_size; ///< GameCardRomSize.
u8 header_version;
GameCardFlags flags;
u64 package_id;
u32 valid_data_end_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks.
u8 reserved[0x4];
u8 iv[0x10];
u64 partition_fs_header_address; ///< Root HFS0 header offset.
u64 partition_fs_header_size; ///< Root HFS0 header size.
u8 partition_fs_header_hash[SHA256_HASH_SIZE];
u8 initial_data_hash[SHA256_HASH_SIZE];
u32 sel_sec; ///< GameCardSelSec.
u32 sel_t1_key_index;
u32 sel_key_index;
u32 normal_area_end_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks.
GameCardExtendedHeader extended_header; ///< Encrypted using AES-128-CBC with 'xci_header_key', which can't dumped through current methods.
} GameCardHeader;
typedef struct {
u32 magic; ///< "HFS0".
u32 entry_count;
u32 name_table_size;
u8 reserved[0x4];
} GameCardHashFileSystemHeader;
typedef struct {
u64 offset;
u64 size;
u32 name_offset;
u32 hash_target_size;
u64 hash_target_offset;
u8 hash[SHA256_HASH_SIZE];
} GameCardHashFileSystemEntry;
/// Initializes data needed to access raw gamecard storage areas.
/// Also spans a background thread to automatically detect gamecard status changes and to cache data from the inserted gamecard.
Result gamecardInitialize(void);
/// Deinitializes data generated by gamecardInitialize().
/// This includes destroying the background gamecard detection thread and freeing all cached gamecard data.
void gamecardExit(void);
/// Used to check if a gamecard has been inserted and if info could be loaded from it.
bool gamecardCheckReadyStatus(void);
/// Used to read data from the inserted gamecard.
/// All required handles are managed internally.
/// offset + out_size should never exceed the value returned by gamecardGetTotalRomSize().
bool gamecardStorageRead(void *out, u64 out_size, u64 offset);
/// Miscellaneous functions.
bool gamecardGetHeader(GameCardHeader *out);
bool gamecardGetTotalRomSize(u64 *out);
bool gamecardGetTrimmedRomSize(u64 *out);
bool gamecardGetCertificate(FsGameCardCertificate *out);
bool gamecardGetBundledFirmwareUpdateVersion(u32 *out);
static inline u64 gamecardGetCapacity(GameCardHeader *header)
{
if (!header) return 0;
u64 capacity = 0;
switch(header->rom_size)
{
case GameCardRomSize_1GB:
capacity = (u64)0x40000000;
break;
case GameCardRomSize_2GB:
capacity = (u64)0x80000000;
break;
case GameCardRomSize_4GB:
capacity = (u64)0x100000000;
break;
case GameCardRomSize_8GB:
capacity = (u64)0x200000000;
break;
case GameCardRomSize_16GB:
capacity = (u64)0x400000000;
break;
case GameCardRomSize_32GB:
capacity = (u64)0x800000000;
break;
default:
break;
}
return out;
}
#endif /* __GAMECARD_H__ */

View file

@ -4,17 +4,21 @@
#include "nca.h"
#include "keys.h"
#include "rsa.h"
#include "utils.h"
static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD,
0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB
};
static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx);
static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset);
static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset);
size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, bool encrypt)
size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt)
{
if (!ctx || !dst || !src || !size || (size % NCA_AES_XTS_SECTOR_SIZE) != 0)
if (!ctx || !dst || !src || !size || !sector_size || (size % sector_size) != 0)
{
LOGFILE("Invalid parameters!");
return 0;
@ -26,19 +30,19 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src,
u8 *dst_u8 = (u8*)dst;
const u8 *src_u8 = (const u8*)src;
for(i = 0; i < size; i += NCA_AES_XTS_SECTOR_SIZE, cur_sector++)
for(i = 0; i < size; i += sector_size, cur_sector++)
{
/* We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak */
aes128XtsContextResetSector(ctx, cur_sector, true);
if (encrypt)
{
crypt_res = aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, NCA_AES_XTS_SECTOR_SIZE);
crypt_res = aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, sector_size);
} else {
crypt_res = aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, NCA_AES_XTS_SECTOR_SIZE);
crypt_res = aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, sector_size);
}
if (crypt_res != NCA_AES_XTS_SECTOR_SIZE) break;
if (crypt_res != sector_size) break;
}
return i;
@ -47,6 +51,19 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src,
bool ncaDecryptKeyArea(NcaContext *ctx)
{
if (!ctx)
@ -56,8 +73,15 @@ bool ncaDecryptKeyArea(NcaContext *ctx)
}
Result rc = 0;
u8 tmp_kek[0x10] = {0};
const u8 *kek_src = NULL;
u8 key_count, tmp_kek[0x10] = {0};
/* Check if we're dealing with a NCA0 with a plain text key area */
if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx))
{
memcpy(ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40);
return true;
}
kek_src = keysGetKeyAreaEncryptionKeySource(ctx->header.kaek_index);
if (!kek_src)
@ -73,7 +97,9 @@ bool ncaDecryptKeyArea(NcaContext *ctx)
return false;
}
for(u8 i = 0; i < 4; i++)
key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4);
for(u8 i = 0; i < key_count; i++)
{
rc = splCryptoGenerateAesKey(tmp_kek, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]);
if (R_FAILED(rc))
@ -86,7 +112,7 @@ bool ncaDecryptKeyArea(NcaContext *ctx)
return true;
}
bool ncaEncryptNcaKeyArea(NcaContext *ctx)
bool ncaEncryptKeyArea(NcaContext *ctx)
{
if (!ctx)
{
@ -94,8 +120,16 @@ bool ncaEncryptNcaKeyArea(NcaContext *ctx)
return false;
}
Aes128Context key_area_ctx = {0};
u8 key_count;
const u8 *kaek = NULL;
Aes128Context key_area_ctx = {0};
/* Check if we're dealing with a NCA0 with a plain text key area */
if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx))
{
memcpy(ctx->header.encrypted_keys, ctx->decrypted_keys, 0x40);
return true;
}
kaek = keysGetKeyAreaEncryptionKey(ctx->key_generation, ctx->header.kaek_index);
if (!kaek)
@ -104,20 +138,14 @@ bool ncaEncryptNcaKeyArea(NcaContext *ctx)
return false;
}
key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4);
aes128ContextCreate(&key_area_ctx, kaek, true);
for(u8 i = 0; i < 4; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]);
for(u8 i = 0; i < key_count; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]);
return true;
}
bool ncaDecryptHeader(NcaContext *ctx)
{
if (!ctx)
@ -129,13 +157,13 @@ bool ncaDecryptHeader(NcaContext *ctx)
u32 i, magic = 0;
size_t crypt_res = 0;
const u8 *header_key = NULL;
Aes128XtsContext hdr_aes_ctx = {0};
Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0};
header_key = keysGetNcaHeaderKey();
aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false);
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, false);
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
if (crypt_res != NCA_HEADER_LENGTH)
{
LOGFILE("Invalid output length for decrypted NCA header! (0x%X != 0x%lX)", NCA_HEADER_LENGTH, crypt_res);
@ -147,21 +175,55 @@ bool ncaDecryptHeader(NcaContext *ctx)
switch(magic)
{
case NCA_NCA3_MAGIC:
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, false);
ctx->format_version = NcaVersion_Nca3;
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
if (crypt_res != NCA_FULL_HEADER_LENGTH)
{
LOGFILE("Error decrypting full NCA3 header!");
return false;
}
break;
case NCA_NCA2_MAGIC:
for(i = 0; i < 4; i++)
ctx->format_version = NcaVersion_Nca2;
for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
{
if (ctx->header.fs_entries[i].enable_entry)
if (!ctx->header.fs_entries[i].enable_entry) continue;
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
if (crypt_res != NCA_FS_HEADER_LENGTH)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), sizeof(NcaFsHeader), 0, false);
if (crypt_res != sizeof(NcaFsHeader)) break;
} else {
memset(&(ctx->header.fs_headers[i]), 0, sizeof(NcaFsHeader));
LOGFILE("Error decrypting NCA2 FS section header #%u!", i);
return false;
}
}
break;
case NCA_NCA0_MAGIC:
ctx->format_version = NcaVersion_Nca0;
/* We first need to decrypt the key area from the NCA0 header in order to access its FS section headers */
if (!ncaDecryptKeyArea(ctx))
{
LOGFILE("Error decrypting key area from NCA0 header!");
return false;
}
aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_keys[0], ctx->decrypted_keys[1], false);
for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
{
if (!ctx->header.fs_entries[i].enable_entry) continue;
}
break;
default:
@ -169,10 +231,71 @@ bool ncaDecryptHeader(NcaContext *ctx)
return false;
}
/* Fill additional context info */
ctx->key_generation = ncaGetKeyGenerationValue(ctx);
ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx);
return true;
}
bool ncaEncryptHeader(NcaContext *ctx)
{
if (!ctx)
{
LOGFILE("Invalid NCA context!");
return false;
}
u32 i;
size_t crypt_res = 0;
const u8 *header_key = NULL;
Aes128XtsContext hdr_aes_ctx = {0};
header_key = keysGetNcaHeaderKey();
aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, true);
switch(ctx->format_version)
{
case NcaVersion_Nca3:
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true);
if (crypt_res != NCA_FULL_HEADER_LENGTH)
{
LOGFILE("Error encrypting full NCA3 header!");
return false;
}
break;
case NcaVersion_Nca2:
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true);
if (crypt_res != NCA_HEADER_LENGTH)
{
LOGFILE("Error encrypting partial NCA2 header!");
return false;
}
for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
{
if (!ctx->header.fs_entries[i].enable_entry) continue;
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true);
if (crypt_res != NCA_FS_HEADER_LENGTH)
{
LOGFILE("Error encrypting NCA2 FS section header #%u!", i);
return false;
}
}
break;
case NcaVersion_Nca0:
/* There's nothing else to do */
break;
default:
LOGFILE("Invalid NCA format version! (0x%02X)", ctx->format_version);
return false;
}
return true;
}
@ -183,6 +306,24 @@ bool ncaDecryptHeader(NcaContext *ctx)
static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx)
{
if (!ctx || ctx->format_version != NcaVersion_Nca0) return false;
u8 nca0_key_area_hash[SHA256_HASH_SIZE] = {0};
sha256CalculateHash(nca0_key_area_hash, ctx->header.encrypted_keys, 0x40);
if (!memcmp(nca0_key_area_hash, g_nca0KeyAreaHash, SHA256_HASH_SIZE)) return false;
return true;
}
static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
{
if (!ctr) return;
@ -213,4 +354,4 @@ static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset)
ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF);
ctr_val >>= 8;
}
}
}

View file

@ -22,19 +22,12 @@
#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x))
typedef enum {
NcaVersion_Nca0Beta = 0,
NcaVersion_Nca0 = 1,
NcaVersion_Nca2 = 2,
NcaVersion_Nca3 = 3
NcaVersion_Nca0 = 0,
NcaVersion_Nca2 = 1,
NcaVersion_Nca3 = 2
} NcaVersion;
typedef enum {
NcaDistributionType_Download = 0,
NcaDistributionType_GameCard = 1
@ -51,7 +44,7 @@ typedef enum {
typedef enum {
NcaKeyGenerationOld_100_230 = 0,
NcaKeyGenerationOld_300 = 1
NcaKeyGenerationOld_300 = 2
} NcaKeyGenerationOld;
typedef enum {
@ -60,7 +53,7 @@ typedef enum {
NcaKeyAreaEncryptionKeyIndex_System = 2
} NcaKeyAreaEncryptionKeyIndex;
/// 'NcaKeyGeneration_Latest' will always point to the last known key generation value
/// 'NcaKeyGeneration_Latest' will always point to the last known key generation value.
typedef enum {
NcaKeyGeneration_301_302 = 3,
NcaKeyGeneration_400_410 = 4,
@ -75,8 +68,8 @@ typedef enum {
} NcaKeyGeneration;
typedef struct {
u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks
u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks
u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks.
u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks.
u8 enable_entry;
u8 reserved[0x7];
} NcaFsEntry;
@ -97,8 +90,8 @@ typedef enum {
typedef enum {
NcaHashType_Auto = 0,
NcaHashType_None = 1,
NcaHashType_HierarchicalSha256 = 2, ///< Used by NcaFsType_PartitionFs
NcaHashType_HierarchicalIntegrity = 3 ///< Used by NcaFsType_RomFs
NcaHashType_HierarchicalSha256 = 2, ///< Used by NcaFsType_PartitionFs.
NcaHashType_HierarchicalIntegrity = 3 ///< Used by NcaFsType_RomFs.
} NcaHashType;
typedef enum {
@ -114,7 +107,7 @@ typedef struct {
u64 size;
} NcaHierarchicalSha256LayerInfo;
/// Used for NcaFsType_PartitionFs and NCA0 RomFS
/// Used for NcaFsType_PartitionFs and NCA0 RomFS.
typedef struct {
u8 master_hash[SHA256_HASH_SIZE];
u32 hash_block_size;
@ -130,9 +123,9 @@ typedef struct {
u8 reserved[0x4];
} NcaHierarchicalIntegrityLayerInfo;
/// Used for NcaFsType_RomFs
/// Used for NcaFsType_RomFs.
typedef struct {
u32 magic; ///< "IVFC"
u32 magic; ///< "IVFC".
u32 version;
u32 master_hash_size;
u32 layer_count;
@ -145,12 +138,12 @@ typedef struct {
typedef struct {
union {
struct {
///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs)
///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs).
NcaHierarchicalSha256 hierarchical_sha256;
u8 reserved_1[0xB0];
};
struct {
///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs)
///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs).
NcaHierarchicalIntegrity hierarchical_integrity;
u8 reserved_2[0x18];
};
@ -158,13 +151,13 @@ typedef struct {
} NcaHashInfo;
typedef struct {
u32 magic; ///< "BKTR"
u32 magic; ///< "BKTR".
u32 bucket_count;
u32 entry_count;
u8 reserved[0x4];
} NcaBucketTreeHeader;
/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs)
/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs).
typedef struct {
u64 indirect_offset;
u64 indirect_size;
@ -174,16 +167,16 @@ typedef struct {
NcaBucketTreeHeader aes_ctr_ex_header;
} NcaPatchInfo;
/// Format unknown
/// Format unknown.
typedef struct {
u8 unknown[0x30];
} NcaSparseInfo;
typedef struct {
u16 version;
u8 fs_type; ///< NcaFsType
u8 hash_type; ///< NcaHashType
u8 encryption_type; ///< NcaEncryptionType
u8 fs_type; ///< NcaFsType.
u8 hash_type; ///< NcaHashType.
u8 encryption_type; ///< NcaEncryptionType.
u8 reserved_1[0x3];
NcaHashInfo hash_info;
NcaPatchInfo patch_info;
@ -201,11 +194,11 @@ typedef struct {
typedef struct {
u8 main_signature[0x100]; ///< RSA-PSS signature over header with fixed key.
u8 acid_signature[0x100]; ///< RSA-PSS signature over header with key in NPDM.
u32 magic; ///< "NCA0" / "NCA2" / "NCA3"
u8 distribution_type; ///< NcaDistributionType
u8 content_type; ///< NcaContentType
u8 key_generation_old; ///< NcaKeyGenerationOld
u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex
u32 magic; ///< "NCA0" / "NCA2" / "NCA3".
u8 distribution_type; ///< NcaDistributionType.
u8 content_type; ///< NcaContentType.
u8 key_generation_old; ///< NcaKeyGenerationOld.
u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex.
u64 content_size;
u64 program_id;
u32 content_index;
@ -218,7 +211,7 @@ typedef struct {
u8 sdk_addon_major;
};
};
u8 key_generation; ///< NcaKeyGeneration
u8 key_generation; ///< NcaKeyGeneration.
u8 main_signature_key_generation;
u8 reserved_1[0xE];
FsRightsId rights_id; ///< Used for titlekey crypto.
@ -252,20 +245,21 @@ typedef struct {
} NcaFsContext;
typedef struct {
u8 storage_id; ///< NcmStorageId
u8 storage_id; ///< NcmStorageId.
NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data.
u64 gamecard_base_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard.
u64 gc_secure_area_base_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard.
NcmContentId id; ///< Also used to read NCA data.
char id_str[0x21];
u8 hash[0x20];
char hash_str[0x41];
u8 format_version; ///< NcaVersion.
u8 type; ///< NcmContentType. Retrieved from NcmContentInfo.
u64 size; ///< Retrieved from NcmContentInfo.
u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header.
u8 id_offset; ///< Retrieved from NcmContentInfo.
bool rights_id_available;
NcaHeader header;
bool dirty_header;
NcaEncryptedKey decrypted_keys[4];
NcaFsContext fs_contexts[4];
} NcaContext;
@ -293,8 +287,9 @@ static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx)
static inline void ncaSetDownloadDistributionType(NcaContext *ctx)
{
if (!ctx) return;
if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return;
ctx->header.distribution_type = NcaDistributionType_Download;
ctx->dirty_header = true;
}
static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx)
@ -319,6 +314,7 @@ static inline void ncaWipeRightsId(NcaContext *ctx)
{
if (!ctx) return;
memset(ctx->header.rights_id, 0, sizeof(FsRightsId));
ctx->dirty_header = true;
}
@ -327,6 +323,9 @@ static inline void ncaWipeRightsId(NcaContext *ctx)
bool ncaDecryptKeyArea(NcaContext *nca_ctx);
bool ncaEncryptKeyArea(NcaContext *nca_ctx);
bool ncaDecryptHeader(NcaContext *ctx);
bool ncaEncryptHeader(NcaContext *ctx);

View file

@ -32,7 +32,7 @@ NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
\
static ServiceGuard g_##name##Guard; \
static ServiceGuard g_##name##Guard = {0}; \
NX_INLINE Result _##name##Initialize _paramdecl; \
static void _##name##Cleanup(void); \
\

View file

@ -22,7 +22,7 @@ typedef struct {
u8 modulus[0x100];
u32 public_exponent; ///< Must match ETICKET_DEVKEY_PUBLIC_EXPONENT. Stored using big endian byte order.
u8 padding[0x14];
u8 device_id[0x8];
u64 device_id;
u8 ghash[0x10];
} tikEticketDeviceKeyData;
@ -272,7 +272,7 @@ bool tikGetTitleKekDecryptedTitleKeyFromTicket(void *dst, Ticket *tik)
return false;
}
/* Even though tickets do have a proper key_generation field, we'll default to retrieve it from the rights_id field */
/* Even though tickets do have a proper key_generation field, we'll just retrieve it from the rights_id field */
/* Old custom tools used to wipe the key_generation field or save it to a different offset */
if (!tikGetTitleKekDecryptedTitleKey(dst, titlekey, tik_common_blk->rights_id.c[0xF]))
{

View file

@ -3,7 +3,6 @@
#ifndef __UTIL_H__
#define __UTIL_H__
#include <time.h>
#include <switch.h>
#include "nca.h"
@ -24,13 +23,20 @@
#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__)
#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
#define SLEEP(x) svcSleepThread((x) * (u64)1000000000)
static Mutex g_logfileMutex = 0;
void utilsWriteLogMessage(const char *func_name, const char *fmt, ...)
{
if (!func_name || !strlen(func_name) || !fmt || !strlen(fmt)) return;
mutexLock(&g_logfileMutex);
va_list args;
FILE *logfile = NULL;
char str[FS_MAX_PATH] = {0};
logfile = fopen(APP_BASE_PATH "log.txt", "a+");
if (!logfile) return;
@ -47,11 +53,25 @@ void utilsWriteLogMessage(const char *func_name, const char *fmt, ...)
fprintf(logfile, "\r\n");
fclose(logfile);
mutexUnlock(&g_logfileMutex);
}
#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__)
typedef enum {
UtilsCustomFirmwareType_Atmosphere = 0,
UtilsCustomFirmwareType_SXOS = 1,
UtilsCustomFirmwareType_ReiNX = 2
} UtilsCustomFirmwareType;
typedef struct {
u16 major : 6;
u16 minor : 6;
u16 micro : 4;
u16 bugfix;
} TitleVersion;
#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
@ -128,7 +148,6 @@ void utilsWriteLogMessage(const char *func_name, const char *fmt, ...)
#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "secure" (2)
#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "secure" (3)
#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown"))
#define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown")))
#define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown"))))
#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown"))