1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-10-31 16:01:44 +00:00
nxdumptool/source/gamecard.c

945 lines
31 KiB
C
Raw Normal View History

2020-04-16 01:06:41 +01:00
/*
* Copyright (c) 2020 DarkMatterCore
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
2020-04-15 06:59:12 +01:00
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <threads.h>
#include "gamecard.h"
#include "service_guard.h"
#include "utils.h"
2020-04-16 05:37:16 +01:00
#define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB */
2020-04-15 06:59:12 +01:00
#define GAMECARD_ACCESS_WAIT_TIME 3 /* Seconds */
#define GAMECARD_UPDATE_TID (u64)0x0100000000000816
#define GAMECARD_ECC_BLOCK_SIZE 0x200
#define GAMECARD_ECC_DATA_SIZE 0x24
2020-04-15 21:50:07 +01:00
#define GAMECARD_STORAGE_AREA_NAME(x) ((x) == GameCardStorageArea_Normal ? "normal" : ((x) == GameCardStorageArea_Secure ? "secure" : "none"))
2020-04-16 05:37:16 +01:00
#define GAMECARD_CAPACITY_1GiB (u64)0x40000000
#define GAMECARD_CAPACITY_2GiB (u64)0x80000000
#define GAMECARD_CAPACITY_4GiB (u64)0x100000000
#define GAMECARD_CAPACITY_8GiB (u64)0x200000000
#define GAMECARD_CAPACITY_16GiB (u64)0x400000000
#define GAMECARD_CAPACITY_32GiB (u64)0x800000000
2020-04-15 21:50:07 +01:00
/* Type definitions. */
typedef enum {
GameCardStorageArea_None = 0,
GameCardStorageArea_Normal = 1,
GameCardStorageArea_Secure = 2
} GameCardStorageArea;
2020-04-15 06:59:12 +01:00
typedef struct {
2020-04-16 11:13:11 +01:00
u64 offset; ///< Relative to the start of the gamecard header.
u64 size; ///< Whole partition size.
u64 header_size; ///< Full header size.
u8 *header; ///< GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table.
2020-04-15 06:59:12 +01:00
} GameCardHashFileSystemPartitionInfo;
2020-04-15 21:50:07 +01:00
/* Global variables. */
2020-04-15 06:59:12 +01:00
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};
2020-04-15 21:50:07 +01:00
static FsStorage g_gameCardStorage = {0};
static u8 g_gameCardStorageCurrentArea = GameCardStorageArea_None;
2020-04-15 06:59:12 +01:00
static u8 *g_gameCardReadBuf = NULL;
static GameCardHeader g_gameCardHeader = {0};
static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0;
2020-04-16 05:37:16 +01:00
static u64 g_gameCardCapacity = 0;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table.
2020-04-15 06:59:12 +01:00
static GameCardHashFileSystemPartitionInfo *g_gameCardHfsPartitions = NULL;
2020-04-15 21:50:07 +01:00
/* Function prototypes. */
2020-04-15 06:59:12 +01:00
static bool gamecardCreateDetectionThread(void);
static void gamecardDestroyDetectionThread(void);
static int gamecardDetectionThreadFunc(void *arg);
2020-04-15 21:50:07 +01:00
static inline bool gamecardIsInserted(void);
2020-04-15 06:59:12 +01:00
static void gamecardLoadInfo(void);
static void gamecardFreeInfo(void);
static bool gamecardGetHandle(void);
static inline void gamecardCloseHandle(void);
2020-04-15 21:50:07 +01:00
static bool gamecardOpenStorageArea(u8 area);
static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool lock);
2020-04-15 21:50:07 +01:00
static void gamecardCloseStorageArea(void);
2020-04-15 06:59:12 +01:00
2020-04-16 05:37:16 +01:00
static bool gamecardGetStorageAreasSizes(void);
static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size);
2020-04-15 06:59:12 +01:00
static bool gamecardGetHashFileSystemPartitionIndexByType(u8 type, u32 *out);
2020-04-16 11:13:11 +01:00
static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *hfs_header, u32 idx);
static inline char *gamecardGetHashFileSystemEntryName(void *hfs_header, u32 name_offset);
2020-04-16 01:06:41 +01:00
/* Service guard used to generate thread-safe initialize + exit functions. */
2020-04-16 11:13:11 +01:00
/* I'm using this here even though this actually isn't a real service but who cares, it gets the job done. */
2020-04-15 06:59:12 +01:00
NX_GENERATE_SERVICE_GUARD(gamecard);
2020-04-15 21:50:07 +01:00
bool gamecardIsReady(void)
2020-04-15 06:59:12 +01:00
{
2020-04-16 05:37:16 +01:00
bool ret = false;
2020-04-15 06:59:12 +01:00
mtx_lock(&g_gameCardSharedDataMutex);
2020-04-16 05:37:16 +01:00
ret = (g_gameCardInserted && g_gameCardInfoLoaded);
2020-04-15 06:59:12 +01:00
mtx_unlock(&g_gameCardSharedDataMutex);
2020-04-16 05:37:16 +01:00
return ret;
2020-04-15 06:59:12 +01:00
}
bool gamecardRead(void *out, u64 read_size, u64 offset)
2020-04-15 06:59:12 +01:00
{
return gamecardReadStorageArea(out, read_size, offset, true);
2020-04-15 06:59:12 +01:00
}
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;
}
2020-04-16 05:37:16 +01:00
bool gamecardGetTotalSize(u64 *out)
2020-04-15 06:59:12 +01:00
{
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;
}
2020-04-16 05:37:16 +01:00
bool gamecardGetTrimmedSize(u64 *out)
2020-04-15 06:59:12 +01:00
{
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;
}
2020-04-16 05:37:16 +01:00
bool gamecardGetRomCapacity(u64 *out)
{
bool ret = false;
mtx_lock(&g_gameCardSharedDataMutex);
if (g_gameCardInserted && g_gameCardInfoLoaded && out)
{
*out = g_gameCardCapacity;
ret = true;
}
mtx_unlock(&g_gameCardSharedDataMutex);
return ret;
}
2020-04-15 06:59:12 +01:00
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;
}
bool gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(u8 hfs_partition_type, const char *name, u64 *out_offset, u64 *out_size)
2020-04-16 11:13:11 +01:00
{
bool ret = false;
char *entry_name = NULL;
size_t name_len = 0;
u32 hfs_partition_idx = 0;
2020-04-16 11:13:11 +01:00
GameCardHashFileSystemHeader *fs_header = NULL;
GameCardHashFileSystemEntry *fs_entry = NULL;
mtx_lock(&g_gameCardSharedDataMutex);
if (!g_gameCardInserted || !g_gameCardInfoLoaded || !name || !*name || !out_offset || !out_size || !gamecardGetHashFileSystemPartitionIndexByType(hfs_partition_type, &hfs_partition_idx))
2020-04-16 11:13:11 +01:00
{
LOGFILE("Invalid parameters!");
goto out;
}
name_len = strlen(name);
fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsPartitions[hfs_partition_idx].header;
2020-04-16 11:13:11 +01:00
for(u32 i = 0; i < fs_header->entry_count; i++)
{
fs_entry = gamecardGetHashFileSystemEntryByIndex(fs_header, i);
if (!fs_entry) continue;
entry_name = gamecardGetHashFileSystemEntryName(fs_header, fs_entry->name_offset);
if (!entry_name) continue;
if (!strncasecmp(entry_name, name, name_len))
{
*out_offset = (g_gameCardHfsPartitions[hfs_partition_idx].offset + g_gameCardHfsPartitions[hfs_partition_idx].header_size + fs_entry->offset);
*out_size = fs_entry->size;
2020-04-16 11:13:11 +01:00
ret = true;
break;
}
}
out:
mtx_unlock(&g_gameCardSharedDataMutex);
return ret;
}
2020-04-15 06:59:12 +01:00
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 */
2020-04-15 21:50:07 +01:00
g_gameCardInserted = prev_status = gamecardIsInserted();
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
/* Load gamecard info right away if a gamecard is inserted */
if (g_gameCardInserted) gamecardLoadInfo();
2020-04-15 06:59:12 +01:00
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);
2020-04-15 21:50:07 +01:00
g_gameCardInserted = gamecardIsInserted();
2020-04-15 06:59:12 +01:00
if (!prev_status && g_gameCardInserted)
{
/* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules */
utilsSleep(GAMECARD_ACCESS_WAIT_TIME);
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
/* Load gamecard info */
gamecardLoadInfo();
2020-04-15 06:59:12 +01:00
} else {
2020-04-15 21:50:07 +01:00
/* Free gamecard info */
2020-04-15 06:59:12 +01:00
gamecardFreeInfo();
}
prev_status = g_gameCardInserted;
mtx_unlock(&g_gameCardSharedDataMutex);
}
/* Free gamecard info and close gamecard handle */
mtx_lock(&g_gameCardSharedDataMutex);
gamecardFreeInfo();
g_gameCardInserted = false;
mtx_unlock(&g_gameCardSharedDataMutex);
return 0;
}
2020-04-15 21:50:07 +01:00
static inline bool gamecardIsInserted(void)
2020-04-15 06:59:12 +01:00
{
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;
2020-04-15 21:50:07 +01:00
/* Retrieve gamecard storage area sizes */
/* gamecardReadStorageArea() actually checks if the storage area sizes are greater than zero, so we must first perform this step */
2020-04-16 05:37:16 +01:00
if (!gamecardGetStorageAreasSizes())
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
LOGFILE("Failed to retrieve gamecard storage area sizes!");
2020-04-15 06:59:12 +01:00
goto out;
}
/* Read gamecard header */
2020-04-15 21:50:07 +01:00
if (!gamecardReadStorageArea(&g_gameCardHeader, sizeof(GameCardHeader), 0, false))
2020-04-15 06:59:12 +01:00
{
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;
}
2020-04-16 05:37:16 +01:00
/* Get gamecard capacity */
g_gameCardCapacity = gamecardGetCapacityFromRomSizeValue(g_gameCardHeader.rom_size);
if (!g_gameCardCapacity)
{
LOGFILE("Invalid gamecard capacity value! (0x%02X)", g_gameCardHeader.rom_size);
goto out;
}
2020-04-15 06:59:12 +01:00
if (utilsGetCustomFirmwareType() == UtilsCustomFirmwareType_SXOS)
{
2020-04-15 21:50:07 +01:00
/* The total size for the secure storage area is maxed out under SX OS */
2020-04-15 06:59:12 +01:00
/* Let's try to calculate it manually */
2020-04-16 05:37:16 +01:00
g_gameCardStorageSecureAreaSize = ((g_gameCardCapacity - ((g_gameCardCapacity / GAMECARD_ECC_BLOCK_SIZE) * GAMECARD_ECC_DATA_SIZE)) - g_gameCardStorageNormalAreaSize);
2020-04-15 06:59:12 +01:00
}
/* 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 */
2020-04-15 21:50:07 +01:00
if (!gamecardReadStorageArea(g_gameCardHfsRootHeader, g_gameCardHeader.partition_fs_header_size, g_gameCardHeader.partition_fs_header_address, false))
2020-04-15 06:59:12 +01:00
{
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++)
{
2020-04-16 11:13:11 +01:00
fs_entry = gamecardGetHashFileSystemEntryByIndex(g_gameCardHfsRootHeader, i);
if (!fs_entry || !fs_entry->size)
2020-04-15 06:59:12 +01:00
{
2020-04-16 11:13:11 +01:00
LOGFILE("Invalid hash FS partition entry!");
2020-04-15 06:59:12 +01:00
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};
2020-04-15 21:50:07 +01:00
if (!gamecardReadStorageArea(&partition_header, sizeof(GameCardHashFileSystemHeader), g_gameCardHfsPartitions[i].offset, false))
2020-04-15 06:59:12 +01:00
{
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;
}
2020-04-16 11:13:11 +01:00
/* Calculate the full header size for the current hash FS partition and round it to a GAMECARD_MEDIA_UNIT_SIZE bytes boundary */
g_gameCardHfsPartitions[i].header_size = (sizeof(GameCardHashFileSystemHeader) + (partition_header.entry_count * sizeof(GameCardHashFileSystemEntry)) + partition_header.name_table_size);
g_gameCardHfsPartitions[i].header_size = ROUND_UP(g_gameCardHfsPartitions[i].header_size, GAMECARD_MEDIA_UNIT_SIZE);
2020-04-15 06:59:12 +01:00
/* Allocate memory for the hash FS partition header */
2020-04-16 11:13:11 +01:00
g_gameCardHfsPartitions[i].header = calloc(g_gameCardHfsPartitions[i].header_size, sizeof(u8));
2020-04-15 06:59:12 +01:00
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 */
2020-04-16 11:13:11 +01:00
if (!gamecardReadStorageArea(g_gameCardHfsPartitions[i].header, g_gameCardHfsPartitions[i].header_size, g_gameCardHfsPartitions[i].offset, false))
2020-04-15 06:59:12 +01:00
{
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));
2020-04-16 05:37:16 +01:00
2020-04-15 21:50:07 +01:00
g_gameCardStorageNormalAreaSize = 0;
g_gameCardStorageSecureAreaSize = 0;
2020-04-15 06:59:12 +01:00
2020-04-16 05:37:16 +01:00
g_gameCardCapacity = 0;
2020-04-15 06:59:12 +01:00
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;
}
2020-04-15 21:50:07 +01:00
gamecardCloseStorageArea();
2020-04-15 06:59:12 +01:00
g_gameCardInfoLoaded = false;
}
static bool gamecardGetHandle(void)
{
if (!g_gameCardInserted)
{
LOGFILE("Gamecard not inserted!");
return false;
}
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;
}
2020-04-15 21:50:07 +01:00
static bool gamecardOpenStorageArea(u8 area)
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
if (!g_gameCardInserted || (area != GameCardStorageArea_Normal && area != GameCardStorageArea_Secure))
2020-04-15 06:59:12 +01:00
{
LOGFILE("Invalid parameters!");
return false;
}
2020-04-15 21:50:07 +01:00
if (g_gameCardHandle.value && serviceIsActive(&(g_gameCardStorage.s)) && g_gameCardStorageCurrentArea == area) return true;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
gamecardCloseStorageArea();
2020-04-15 06:59:12 +01:00
Result rc = 0;
2020-04-15 21:50:07 +01:00
u32 partition = (area - 1); /* Zero-based index */
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
/* Retrieve a new gamecard handle */
if (!gamecardGetHandle())
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
LOGFILE("Failed to retrieve gamecard handle!");
return false;
2020-04-15 06:59:12 +01:00
}
2020-04-15 21:50:07 +01:00
/* Open storage area */
rc = fsOpenGameCardStorage(&g_gameCardStorage, &g_gameCardHandle, partition);
2020-04-15 06:59:12 +01:00
if (R_FAILED(rc))
{
2020-04-15 21:50:07 +01:00
LOGFILE("fsOpenGameCardStorage failed to open %s storage area! (0x%08X)", GAMECARD_STORAGE_AREA_NAME(area), rc);
gamecardCloseHandle();
return false;
2020-04-15 06:59:12 +01:00
}
2020-04-15 21:50:07 +01:00
g_gameCardStorageCurrentArea = area;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
return true;
2020-04-15 06:59:12 +01:00
}
static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool lock)
2020-04-15 06:59:12 +01:00
{
if (lock) mtx_lock(&g_gameCardSharedDataMutex);
bool success = false;
if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || \
offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || (offset + read_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize))
2020-04-15 06:59:12 +01:00
{
LOGFILE("Invalid parameters!");
goto out;
}
Result rc = 0;
u8 *out_u8 = (u8*)out;
2020-04-15 21:50:07 +01:00
u8 area = (offset < g_gameCardStorageNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure);
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
/* Handle reads that span both the normal and secure gamecard storage areas */
if (area == GameCardStorageArea_Normal && (offset + read_size) > g_gameCardStorageNormalAreaSize)
2020-04-15 06:59:12 +01:00
{
/* Calculate normal storage area size difference */
u64 diff_size = (g_gameCardStorageNormalAreaSize - offset);
2020-04-15 21:50:07 +01:00
if (!gamecardReadStorageArea(out_u8, diff_size, offset, false)) goto out;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
/* Adjust variables to read right from the start of the secure storage area */
read_size -= diff_size;
2020-04-15 21:50:07 +01:00
offset = g_gameCardStorageNormalAreaSize;
out_u8 += diff_size;
area = GameCardStorageArea_Secure;
}
/* Open a storage area if needed */
/* If the right storage area has already been opened, this will return true */
if (!gamecardOpenStorageArea(area))
{
LOGFILE("Failed to open %s storage area!", GAMECARD_STORAGE_AREA_NAME(area));
goto out;
2020-04-15 06:59:12 +01:00
}
/* Calculate appropiate storage area offset and retrieve the right storage area pointer */
2020-04-15 21:50:07 +01:00
u64 base_offset = (area == GameCardStorageArea_Normal ? offset : (offset - g_gameCardStorageNormalAreaSize));
2020-04-15 06:59:12 +01:00
if (!(base_offset % GAMECARD_MEDIA_UNIT_SIZE) && !(read_size % GAMECARD_MEDIA_UNIT_SIZE))
2020-04-15 06:59:12 +01:00
{
/* Optimization for reads that are already aligned to GAMECARD_MEDIA_UNIT_SIZE bytes */
rc = fsStorageRead(&g_gameCardStorage, base_offset, out_u8, read_size);
2020-04-15 06:59:12 +01:00
if (R_FAILED(rc))
{
LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned)", read_size, base_offset, GAMECARD_STORAGE_AREA_NAME(area), rc);
2020-04-15 06:59:12 +01:00
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 + read_size, GAMECARD_MEDIA_UNIT_SIZE);
2020-04-15 06:59:12 +01:00
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)) : read_size);
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
rc = fsStorageRead(&g_gameCardStorage, block_start_offset, g_gameCardReadBuf, chunk_size);
if (R_FAILED(rc))
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (unaligned)", chunk_size, block_start_offset, GAMECARD_STORAGE_AREA_NAME(area), rc);
2020-04-15 06:59:12 +01:00
goto out;
}
memcpy(out_u8, g_gameCardReadBuf + (base_offset - block_start_offset), out_chunk_size);
success = (block_size > GAMECARD_READ_BUFFER_SIZE ? gamecardReadStorageArea(out_u8 + out_chunk_size, read_size - out_chunk_size, base_offset + out_chunk_size, false) : true);
2020-04-15 06:59:12 +01:00
}
out:
if (lock) mtx_unlock(&g_gameCardSharedDataMutex);
return success;
}
2020-04-15 21:50:07 +01:00
static void gamecardCloseStorageArea(void)
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
if (serviceIsActive(&(g_gameCardStorage.s)))
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
fsStorageClose(&g_gameCardStorage);
memset(&g_gameCardStorage, 0, sizeof(FsStorage));
2020-04-15 06:59:12 +01:00
}
2020-04-15 21:50:07 +01:00
gamecardCloseHandle();
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
g_gameCardStorageCurrentArea = GameCardStorageArea_None;
2020-04-15 06:59:12 +01:00
}
2020-04-16 05:37:16 +01:00
static bool gamecardGetStorageAreasSizes(void)
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
if (!g_gameCardInserted)
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
LOGFILE("Gamecard not inserted!");
2020-04-15 06:59:12 +01:00
return false;
}
2020-04-15 21:50:07 +01:00
for(u8 i = 0; i < 2; i++)
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
Result rc = 0;
u64 area_size = 0;
u8 area = (i == 0 ? GameCardStorageArea_Normal : GameCardStorageArea_Secure);
if (!gamecardOpenStorageArea(area))
{
LOGFILE("Failed to open %s storage area!", GAMECARD_STORAGE_AREA_NAME(area));
return false;
}
rc = fsStorageGetSize(&g_gameCardStorage, (s64*)&area_size);
gamecardCloseStorageArea();
2020-04-16 01:06:41 +01:00
if (R_FAILED(rc) || !area_size)
2020-04-15 21:50:07 +01:00
{
LOGFILE("fsStorageGetSize failed to retrieve %s storage area size! (0x%08X)", GAMECARD_STORAGE_AREA_NAME(area), rc);
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = 0;
return false;
}
if (area == GameCardStorageArea_Normal)
{
g_gameCardStorageNormalAreaSize = area_size;
} else {
g_gameCardStorageSecureAreaSize = area_size;
}
2020-04-15 06:59:12 +01:00
}
return true;
}
2020-04-16 05:37:16 +01:00
static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size)
{
u64 capacity = 0;
switch(rom_size)
{
case GameCardRomSize_1GiB:
capacity = GAMECARD_CAPACITY_1GiB;
break;
case GameCardRomSize_2GiB:
capacity = GAMECARD_CAPACITY_2GiB;
break;
case GameCardRomSize_4GiB:
capacity = GAMECARD_CAPACITY_4GiB;
break;
case GameCardRomSize_8GiB:
capacity = GAMECARD_CAPACITY_8GiB;
break;
case GameCardRomSize_16GiB:
capacity = GAMECARD_CAPACITY_16GiB;
break;
case GameCardRomSize_32GiB:
capacity = GAMECARD_CAPACITY_32GiB;
break;
default:
break;
}
return capacity;
}
2020-04-16 11:13:11 +01:00
static bool gamecardGetHashFileSystemPartitionIndexByType(u8 type, u32 *out)
2020-04-16 11:13:11 +01:00
{
if (type > GameCardHashFileSystemPartitionType_Secure || !out) return false;
char *entry_name = NULL;
GameCardHashFileSystemEntry *fs_entry = NULL;
GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader;
for(u32 i = 0; i < fs_header->entry_count; i++)
{
fs_entry = gamecardGetHashFileSystemEntryByIndex(fs_header, i);
if (!fs_entry) continue;
entry_name = gamecardGetHashFileSystemEntryName(fs_header, fs_entry->name_offset);
if (!entry_name) continue;
if (!strcasecmp(entry_name, GAMECARD_HFS_PARTITION_NAME(type)))
{
*out = i;
return true;
}
}
return false;
2020-04-16 11:13:11 +01:00
}
static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *hfs_header, u32 idx)
{
if (!hfs_header || idx >= ((GameCardHashFileSystemHeader*)hfs_header)->entry_count) return NULL;
return (GameCardHashFileSystemEntry*)((u8*)hfs_header + sizeof(GameCardHashFileSystemHeader) + (idx * sizeof(GameCardHashFileSystemEntry)));
}
static inline char *gamecardGetHashFileSystemEntryName(void *hfs_header, u32 name_offset)
{
if (!hfs_header) return NULL;
GameCardHashFileSystemHeader *header = (GameCardHashFileSystemHeader*)hfs_header;
if (!header->entry_count || name_offset >= header->name_table_size) return NULL;
return ((char*)hfs_header + sizeof(GameCardHashFileSystemHeader) + (header->entry_count * sizeof(GameCardHashFileSystemEntry)) + name_offset);
}