mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +00:00
Gamecard rewrite.
This commit is contained in:
parent
e5a4532a63
commit
65e40e7600
14 changed files with 1244 additions and 1331 deletions
17
README.md
17
README.md
|
@ -1,4 +1,19 @@
|
|||
# nxdumptool
|
||||
todo:
|
||||
|
||||
hfs0 methods
|
||||
tik gamecard
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# nxdumptool
|
||||
<img width="200" src="icon.jpg">
|
||||
Nintendo Switch Dump Tool
|
||||
|
||||
|
|
1163
source/keys.c
1163
source/keys.c
File diff suppressed because it is too large
Load diff
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
763
source/new/gamecard.c
Normal 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
174
source/new/gamecard.h
Normal 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__ */
|
205
source/new/nca.c
205
source/new/nca.c
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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); \
|
||||
\
|
||||
|
|
|
@ -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]))
|
||||
{
|
||||
|
|
|
@ -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"))
|
||||
|
|
Loading…
Reference in a new issue