mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-10-30 15:31:46 +00:00
764 lines
24 KiB
C
764 lines
24 KiB
C
|
#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;
|
||
|
}
|