Small code refactor (part 2).

* Rewrote mutex handling throughout the code to use a small, macro-based scoped lock implementation.

* Removed extern variables from common.h - launch path management is now completely handled in utils.c.

* Updated NpdmSystemCallId_Count to reflect changes introduced in 12.0.0.

* Added NcaMainSignatureKeyGeneration enum.

* NCA main signature moduli are now retrieved from FS .rodata at runtime.

* Simplified lock management in usb.c by using a single global mutex with scoped locks instead of three different r/w locks.

* Updated FatFs to R0.14b.

* Enabled 64-bit LBA support in FatFs to potentially support custom eMMC replacements / resized USER partitions in the future.

* Updated LZ4 to v1.9.3.

* Fixed typos.

* USB gamecard dumper PoC now only dumps the Initial Data area.

* Updated to-do list.
This commit is contained in:
Pablo Curiel 2021-05-18 08:32:43 -04:00
parent 85f146f50c
commit f82d7a3db4
36 changed files with 3122 additions and 2845 deletions

View file

@ -47,7 +47,11 @@ VERSION_MICRO := 0
APP_TITLE := nxdumptool APP_TITLE := nxdumptool
APP_AUTHOR := DarkMatterCore APP_AUTHOR := DarkMatterCore
APP_VERSION := ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO} APP_VERSION := ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}
ifneq ($(origin BUILD_TYPE),undefined)
APP_TITLE := ${BUILD_TYPE}
endif
TARGET := ${APP_TITLE} TARGET := ${APP_TITLE}
BUILD := build BUILD := build
@ -65,24 +69,24 @@ BOREALIS_RESOURCES := romfs:/
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# options for code generation # options for code generation
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -Werror -O2 -ffunction-sections $(ARCH) $(DEFINES) $(INCLUDE) -D__SWITCH__ CFLAGS := -g -Wall -Werror -O2 -ffunction-sections $(ARCH) $(DEFINES) $(INCLUDE) -D__SWITCH__
CFLAGS += -DVERSION_MAJOR=${VERSION_MAJOR} -DVERSION_MINOR=${VERSION_MINOR} -DVERSION_MICRO=${VERSION_MICRO} CFLAGS += -DVERSION_MAJOR=${VERSION_MAJOR} -DVERSION_MINOR=${VERSION_MINOR} -DVERSION_MICRO=${VERSION_MICRO}
CFLAGS += -DAPP_TITLE=\"${APP_TITLE}\" -DAPP_AUTHOR=\"${APP_AUTHOR}\" -DAPP_VERSION=\"${APP_VERSION}\" CFLAGS += -DAPP_TITLE=\"${APP_TITLE}\" -DAPP_AUTHOR=\"${APP_AUTHOR}\" -DAPP_VERSION=\"${APP_VERSION}\"
CFLAGS += -DGIT_BRANCH=\"${GIT_BRANCH}\" -DGIT_COMMIT=\"${GIT_COMMIT}\" CFLAGS += -DGIT_BRANCH=\"${GIT_BRANCH}\" -DGIT_COMMIT=\"${GIT_COMMIT}\"
CFLAGS += -DBOREALIS_RESOURCES="\"${BOREALIS_RESOURCES}\"" CFLAGS += -DBOREALIS_RESOURCES="\"${BOREALIS_RESOURCES}\""
CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags` CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags`
CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --cflags` CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --cflags`
CFLAGS += `aarch64-none-elf-pkg-config json-c --cflags` CFLAGS += `aarch64-none-elf-pkg-config json-c --cflags`
CFLAGS += `aarch64-none-elf-pkg-config libturbojpeg --cflags` CFLAGS += `aarch64-none-elf-pkg-config libturbojpeg --cflags`
CXXFLAGS := $(CFLAGS) -std=c++1z -O2 -Wno-volatile -Wno-unused-parameter CXXFLAGS := $(CFLAGS) -std=c++1z -O2 -Wno-volatile -Wno-unused-parameter
ASFLAGS := -g $(ARCH) ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) LDFLAGS := -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -lz -lusbhsfs -lntfs-3g -llwext4 -lnx -ljson-c -lturbojpeg LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -lz -lusbhsfs -lntfs-3g -llwext4 -lnx -ljson-c -lturbojpeg
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing # list of directories containing libraries, this must be the top level containing

View file

@ -25,12 +25,17 @@ for f in ./code_templates/*.c; do
rm -f ./source/main.c rm -f ./source/main.c
cp $f ./source/main.c cp $f ./source/main.c
make clean cp ./romfs/icon/nxdumptool.jpg ./romfs/icon/$filename.jpg
make -j$(nproc)
make BUILD_TYPE="$filename" -j$(nproc)
rm -f ./romfs/icon/$filename.jpg
mkdir ./code_templates/tmp/$filename mkdir ./code_templates/tmp/$filename
cp ./nxdumptool.nro ./code_templates/tmp/$filename/nxdumptool.nro cp ./$filename.nro ./code_templates/tmp/$filename/$filename.nro
#cp ./nxdumptool.elf ./code_templates/tmp/$filename/nxdumptool.elf #cp ./$filename.elf ./code_templates/tmp/$filename/$filename.elf
make BUILD_TYPE="$filename" clean
done done
make clean_all make clean_all

View file

@ -32,10 +32,6 @@
#define BLOCK_SIZE 0x800000 #define BLOCK_SIZE 0x800000
#define OUTPATH "/nsp/" #define OUTPATH "/nsp/"
int g_argc = 0;
char **g_argv = NULL;
const char *g_appLaunchPath = NULL;
static PadState g_padState = {0}; static PadState g_padState = {0};
static const char *dump_type_strings[] = { static const char *dump_type_strings[] = {
@ -783,12 +779,9 @@ end:
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
g_argc = argc;
g_argv = argv;
int ret = 0; int ret = 0;
if (!utilsInitializeResources()) if (!utilsInitializeResources(argc, (const char**)argv))
{ {
ret = -1; ret = -1;
goto out; goto out;

View file

@ -31,10 +31,6 @@
#define BLOCK_SIZE 0x800000 #define BLOCK_SIZE 0x800000
int g_argc = 0;
char **g_argv = NULL;
const char *g_appLaunchPath = NULL;
static PadState g_padState = {0}; static PadState g_padState = {0};
typedef struct typedef struct
@ -941,12 +937,9 @@ static void nspDump(TitleInfo *title_info)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
g_argc = argc;
g_argv = argv;
int ret = 0; int ret = 0;
if (!utilsInitializeResources()) if (!utilsInitializeResources(argc, (const char**)argv))
{ {
ret = -1; ret = -1;
goto out; goto out;

View file

@ -26,10 +26,6 @@
#define BLOCK_SIZE 0x800000 #define BLOCK_SIZE 0x800000
int g_argc = 0;
char **g_argv = NULL;
const char *g_appLaunchPath = NULL;
static PadState g_padState = {0}; static PadState g_padState = {0};
static Mutex g_fileMutex = 0; static Mutex g_fileMutex = 0;
@ -358,12 +354,9 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
g_argc = argc;
g_argv = argv;
int ret = 0; int ret = 0;
if (!utilsInitializeResources()) if (!utilsInitializeResources(argc, (const char**)argv))
{ {
ret = -1; ret = -1;
goto out; goto out;

View file

@ -28,10 +28,6 @@
#define BLOCK_SIZE 0x800000 #define BLOCK_SIZE 0x800000
#define OUTPATH "sdmc:/systitle_dumps" #define OUTPATH "sdmc:/systitle_dumps"
int g_argc = 0;
char **g_argv = NULL;
const char *g_appLaunchPath = NULL;
static PadState g_padState = {0}; static PadState g_padState = {0};
static u8 *buf = NULL; static u8 *buf = NULL;
@ -255,12 +251,9 @@ static void dumpFsSection(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
g_argc = argc;
g_argv = argv;
int ret = 0; int ret = 0;
if (!utilsInitializeResources()) if (!utilsInitializeResources(argc, (const char**)argv))
{ {
ret = -1; ret = -1;
goto out; goto out;

View file

@ -27,10 +27,6 @@
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE #define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
int g_argc = 0;
char **g_argv = NULL;
const char *g_appLaunchPath = NULL;
static PadState g_padState = {0}; static PadState g_padState = {0};
/* Type definitions. */ /* Type definitions. */
@ -153,7 +149,7 @@ static Menu g_xciMenu = {
static MenuElement *g_rootMenuElements[] = { static MenuElement *g_rootMenuElements[] = {
&(MenuElement){ &(MenuElement){
.str = "dump key area", .str = "dump key area (initial data)",
.child_menu = NULL, .child_menu = NULL,
.task_func = &sendGameCardKeyAreaViaUsb, .task_func = &sendGameCardKeyAreaViaUsb,
.element_options = NULL .element_options = NULL
@ -215,15 +211,12 @@ static void utilsWaitForButtonPress(u64 flag)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
g_argc = argc;
g_argv = argv;
int ret = 0; int ret = 0;
Menu *cur_menu = &g_rootMenu; Menu *cur_menu = &g_rootMenu;
u32 element_count = menuGetElementCount(cur_menu), page_size = 30; u32 element_count = menuGetElementCount(cur_menu), page_size = 30;
if (!utilsInitializeResources()) if (!utilsInitializeResources(argc, (const char**)argv))
{ {
ret = -1; ret = -1;
goto out; goto out;
@ -453,10 +446,10 @@ static bool sendGameCardKeyAreaViaUsb(void)
if (!dumpGameCardKeyArea(&gc_key_area) || !filename) goto end; if (!dumpGameCardKeyArea(&gc_key_area) || !filename) goto end;
crc32FastCalculate(&gc_key_area, sizeof(GameCardKeyArea), &crc); crc32FastCalculate(&(gc_key_area.initial_data), sizeof(GameCardInitialData), &crc);
snprintf(path, MAX_ELEMENTS(path), "%s (Key Area) (%08X).bin", filename, crc); snprintf(path, MAX_ELEMENTS(path), "%s (Initial Data) (%08X).bin", filename, crc);
if (!sendFileData(path, &gc_key_area, sizeof(GameCardKeyArea))) goto end; if (!sendFileData(path, &(gc_key_area.initial_data), sizeof(GameCardInitialData))) goto end;
printf("successfully sent key area as \"%s\"\n", path); printf("successfully sent key area as \"%s\"\n", path);
success = true; success = true;

View file

@ -27,10 +27,6 @@
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE #define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
int g_argc = 0;
char **g_argv = NULL;
const char *g_appLaunchPath = NULL;
static PadState g_padState = {0}; static PadState g_padState = {0};
static Mutex g_fileMutex = 0; static Mutex g_fileMutex = 0;
@ -337,12 +333,9 @@ u8 get_program_id_offset(TitleInfo *info, u32 program_count)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
g_argc = argc;
g_argv = argv;
int ret = 0; int ret = 0;
if (!utilsInitializeResources()) if (!utilsInitializeResources(argc, (const char**)argv))
{ {
ret = -1; ret = -1;
goto out; goto out;

View file

@ -27,10 +27,6 @@
#include "nacp.h" #include "nacp.h"
#include "legal_info.h" #include "legal_info.h"
int g_argc = 0;
char **g_argv = NULL;
const char *g_appLaunchPath = NULL;
static PadState g_padState = {0}; static PadState g_padState = {0};
static void utilsScanPads(void) static void utilsScanPads(void)
@ -83,12 +79,9 @@ static void writeFile(void *buf, size_t buf_size, const char *path)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
g_argc = argc;
g_argv = argv;
int ret = 0; int ret = 0;
if (!utilsInitializeResources()) if (!utilsInitializeResources(argc, (const char**)argv))
{ {
ret = -1; ret = -1;
goto out; goto out;

View file

@ -93,11 +93,4 @@ typedef struct {
NXDT_ASSERT(VersionType2, 0x4); NXDT_ASSERT(VersionType2, 0x4);
/// These are set in main().
extern int g_argc;
extern char **g_argv;
/// This is set in utilsInitializeResources().
extern const char *g_appLaunchPath;
#endif /* __COMMON_H__ */ #endif /* __COMMON_H__ */

View file

@ -100,7 +100,7 @@ extern "C" {
/*------ Version ------*/ /*------ Version ------*/
#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ #define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */
#define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ #define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */
#define LZ4_VERSION_RELEASE 2 /* for tweaks, bug-fixes, or development */ #define LZ4_VERSION_RELEASE 3 /* for tweaks, bug-fixes, or development */
#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) #define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE)
@ -186,7 +186,8 @@ LZ4LIB_API int LZ4_compressBound(int inputSize);
The larger the acceleration value, the faster the algorithm, but also the lesser the compression. The larger the acceleration value, the faster the algorithm, but also the lesser the compression.
It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed.
An acceleration value of "1" is the same as regular LZ4_compress_default() An acceleration value of "1" is the same as regular LZ4_compress_default()
Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c). Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c).
Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c).
*/ */
LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
@ -212,7 +213,18 @@ LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* d
* New value is necessarily <= input value. * New value is necessarily <= input value.
* @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize)
* or 0 if compression fails. * or 0 if compression fails.
*/ *
* Note : from v1.8.2 to v1.9.1, this function had a bug (fixed un v1.9.2+):
* the produced compressed content could, in specific circumstances,
* require to be decompressed into a destination buffer larger
* by at least 1 byte than the content to decompress.
* If an application uses `LZ4_compress_destSize()`,
* it's highly recommended to update liblz4 to v1.9.2 or better.
* If this can't be done or ensured,
* the receiving decompression function should provide
* a dstCapacity which is > decompressedSize, by at least 1 byte.
* See https://github.com/lz4/lz4/issues/859 for details
*/
LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize);
@ -220,25 +232,35 @@ LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePt
* Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src',
* into destination buffer 'dst' of size 'dstCapacity'. * into destination buffer 'dst' of size 'dstCapacity'.
* Up to 'targetOutputSize' bytes will be decoded. * Up to 'targetOutputSize' bytes will be decoded.
* The function stops decoding on reaching this objective, * The function stops decoding on reaching this objective.
* which can boost performance when only the beginning of a block is required. * This can be useful to boost performance
* whenever only the beginning of a block is required.
* *
* @return : the number of bytes decoded in `dst` (necessarily <= dstCapacity) * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize)
* If source stream is detected malformed, function returns a negative result. * If source stream is detected malformed, function returns a negative result.
* *
* Note : @return can be < targetOutputSize, if compressed block contains less data. * Note 1 : @return can be < targetOutputSize, if compressed block contains less data.
* *
* Note 2 : this function features 2 parameters, targetOutputSize and dstCapacity, * Note 2 : targetOutputSize must be <= dstCapacity
* and expects targetOutputSize <= dstCapacity. *
* It effectively stops decoding on reaching targetOutputSize, * Note 3 : this function effectively stops decoding on reaching targetOutputSize,
* so dstCapacity is kind of redundant. * so dstCapacity is kind of redundant.
* This is because in a previous version of this function, * This is because in older versions of this function,
* decoding operation would not "break" a sequence in the middle. * decoding operation would still write complete sequences.
* As a consequence, there was no guarantee that decoding would stop at exactly targetOutputSize, * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize,
* it could write more bytes, though only up to dstCapacity. * it could write more bytes, though only up to dstCapacity.
* Some "margin" used to be required for this operation to work properly. * Some "margin" used to be required for this operation to work properly.
* This is no longer necessary. * Thankfully, this is no longer necessary.
* The function nonetheless keeps its signature, in an effort to not break API. * The function nonetheless keeps the same signature, in an effort to preserve API compatibility.
*
* Note 4 : If srcSize is the exact size of the block,
* then targetOutputSize can be any value,
* including larger than the block's decompressed size.
* The function will, at most, generate block's decompressed size.
*
* Note 5 : If srcSize is _larger_ than block's compressed size,
* then targetOutputSize **MUST** be <= block's decompressed size.
* Otherwise, *silent corruption will occur*.
*/ */
LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);
@ -547,74 +569,64 @@ LZ4LIB_STATIC_API void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const
#define LZ4_H_98237428734687 #define LZ4_H_98237428734687
/*-************************************************************ /*-************************************************************
* PRIVATE DEFINITIONS * Private Definitions
************************************************************** **************************************************************
* Do not use these definitions directly. * Do not use these definitions directly.
* They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`.
* Accessing members will expose code to API and/or ABI break in future versions of the library. * Accessing members will expose user code to API and/or ABI break in future versions of the library.
**************************************************************/ **************************************************************/
#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) #define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2)
#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) #define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE)
#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ #define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */
#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) #if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
#include <stdint.h> # include <stdint.h>
typedef int8_t LZ4_i8;
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; typedef uint8_t LZ4_byte;
struct LZ4_stream_t_internal { typedef uint16_t LZ4_u16;
uint32_t hashTable[LZ4_HASH_SIZE_U32]; typedef uint32_t LZ4_u32;
uint32_t currentOffset;
uint16_t dirty;
uint16_t tableType;
const uint8_t* dictionary;
const LZ4_stream_t_internal* dictCtx;
uint32_t dictSize;
};
typedef struct {
const uint8_t* externalDict;
size_t extDictSize;
const uint8_t* prefixEnd;
size_t prefixSize;
} LZ4_streamDecode_t_internal;
#else #else
typedef signed char LZ4_i8;
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; typedef unsigned char LZ4_byte;
struct LZ4_stream_t_internal { typedef unsigned short LZ4_u16;
unsigned int hashTable[LZ4_HASH_SIZE_U32]; typedef unsigned int LZ4_u32;
unsigned int currentOffset;
unsigned short dirty;
unsigned short tableType;
const unsigned char* dictionary;
const LZ4_stream_t_internal* dictCtx;
unsigned int dictSize;
};
typedef struct {
const unsigned char* externalDict;
const unsigned char* prefixEnd;
size_t extDictSize;
size_t prefixSize;
} LZ4_streamDecode_t_internal;
#endif #endif
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
struct LZ4_stream_t_internal {
LZ4_u32 hashTable[LZ4_HASH_SIZE_U32];
LZ4_u32 currentOffset;
LZ4_u32 tableType;
const LZ4_byte* dictionary;
const LZ4_stream_t_internal* dictCtx;
LZ4_u32 dictSize;
};
typedef struct {
const LZ4_byte* externalDict;
size_t extDictSize;
const LZ4_byte* prefixEnd;
size_t prefixSize;
} LZ4_streamDecode_t_internal;
/*! LZ4_stream_t : /*! LZ4_stream_t :
* information structure to track an LZ4 stream. * Do not use below internal definitions directly !
* Declare or allocate an LZ4_stream_t instead.
* LZ4_stream_t can also be created using LZ4_createStream(), which is recommended. * LZ4_stream_t can also be created using LZ4_createStream(), which is recommended.
* The structure definition can be convenient for static allocation * The structure definition can be convenient for static allocation
* (on stack, or as part of larger structure). * (on stack, or as part of larger structure).
* Init this structure with LZ4_initStream() before first use. * Init this structure with LZ4_initStream() before first use.
* note : only use this definition in association with static linking ! * note : only use this definition in association with static linking !
* this definition is not API/ABI safe, and may change in a future version. * this definition is not API/ABI safe, and may change in future versions.
*/ */
#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4 + ((sizeof(void*)==16) ? 4 : 0) /*AS-400*/ ) #define LZ4_STREAMSIZE 16416 /* static size, for inter-version compatibility */
#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) #define LZ4_STREAMSIZE_VOIDP (LZ4_STREAMSIZE / sizeof(void*))
union LZ4_stream_u { union LZ4_stream_u {
unsigned long long table[LZ4_STREAMSIZE_U64]; void* table[LZ4_STREAMSIZE_VOIDP];
LZ4_stream_t_internal internal_donotuse; LZ4_stream_t_internal internal_donotuse;
} ; /* previously typedef'd to LZ4_stream_t */ }; /* previously typedef'd to LZ4_stream_t */
/*! LZ4_initStream() : v1.9.0+ /*! LZ4_initStream() : v1.9.0+
* An LZ4_stream_t structure must be initialized at least once. * An LZ4_stream_t structure must be initialized at least once.
@ -667,22 +679,21 @@ union LZ4_streamDecode_u {
#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS #ifdef LZ4_DISABLE_DEPRECATE_WARNINGS
# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ # define LZ4_DEPRECATED(message) /* disable deprecation warnings */
#else #else
# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ # if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
# define LZ4_DEPRECATED(message) [[deprecated(message)]] # define LZ4_DEPRECATED(message) [[deprecated(message)]]
# elif (LZ4_GCC_VERSION >= 405) || defined(__clang__)
# define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
# elif (LZ4_GCC_VERSION >= 301)
# define LZ4_DEPRECATED(message) __attribute__((deprecated))
# elif defined(_MSC_VER) # elif defined(_MSC_VER)
# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) # define LZ4_DEPRECATED(message) __declspec(deprecated(message))
# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45))
# define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31)
# define LZ4_DEPRECATED(message) __attribute__((deprecated))
# else # else
# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") # pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler")
# define LZ4_DEPRECATED(message) # define LZ4_DEPRECATED(message) /* disabled */
# endif # endif
#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ #endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */
/* Obsolete compression functions */ /*! Obsolete compression functions (since v1.7.3) */
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize);
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize);
@ -690,11 +701,12 @@ LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_co
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize);
/* Obsolete decompression functions */ /*! Obsolete decompression functions (since v1.8.0) */
LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize);
LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize);
/* Obsolete streaming functions; degraded functionality; do not use! /* Obsolete streaming functions (since v1.7.0)
* degraded functionality; do not use!
* *
* In order to perform streaming compression, these functions depended on data * In order to perform streaming compression, these functions depended on data
* that is no longer tracked in the state. They have been preserved as well as * that is no longer tracked in the state. They have been preserved as well as
@ -708,23 +720,22 @@ LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStre
LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer);
LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state);
/* Obsolete streaming decoding functions */ /*! Obsolete streaming decoding functions (since v1.7.0) */
LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize);
LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize);
/*! LZ4_decompress_fast() : **unsafe!** /*! Obsolete LZ4_decompress_fast variants (since v1.9.0) :
* These functions used to be faster than LZ4_decompress_safe(), * These functions used to be faster than LZ4_decompress_safe(),
* but it has changed, and they are now slower than LZ4_decompress_safe(). * but this is no longer the case. They are now slower.
* This is because LZ4_decompress_fast() doesn't know the input size, * This is because LZ4_decompress_fast() doesn't know the input size,
* and therefore must progress more cautiously in the input buffer to not read beyond the end of block. * and therefore must progress more cautiously into the input buffer to not read beyond the end of block.
* On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability.
* As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated.
* *
* The last remaining LZ4_decompress_fast() specificity is that * The last remaining LZ4_decompress_fast() specificity is that
* it can decompress a block without knowing its compressed size. * it can decompress a block without knowing its compressed size.
* Such functionality could be achieved in a more secure manner, * Such functionality can be achieved in a more secure manner
* by also providing the maximum size of input buffer, * by employing LZ4_decompress_safe_partial().
* but it would require new prototypes, and adaptation of the implementation to this new use case.
* *
* Parameters: * Parameters:
* originalSize : is the uncompressed size to regenerate. * originalSize : is the uncompressed size to regenerate.
@ -739,7 +750,6 @@ LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4
* But they may happen if input data is invalid (error or intentional tampering). * But they may happen if input data is invalid (error or intentional tampering).
* As a consequence, use these functions in trusted environments with trusted data **only**. * As a consequence, use these functions in trusted environments with trusted data **only**.
*/ */
LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead")
LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead")

View file

@ -91,11 +91,19 @@ typedef enum {
NcaKeyGeneration_700_801 = 8, NcaKeyGeneration_700_801 = 8,
NcaKeyGeneration_810_811 = 9, NcaKeyGeneration_810_811 = 9,
NcaKeyGeneration_900_901 = 10, NcaKeyGeneration_900_901 = 10,
NcaKeyGeneration_910_1201 = 11, NcaKeyGeneration_910_1202 = 11,
NcaKeyGeneration_Current = NcaKeyGeneration_910_1201, NcaKeyGeneration_Current = NcaKeyGeneration_910_1202,
NcaKeyGeneration_Max = 32 NcaKeyGeneration_Max = 32
} NcaKeyGeneration; } NcaKeyGeneration;
/// 'NcaMainSignatureKeyGeneration_Current' will always point to the last known key generation value.
typedef enum {
NcaMainSignatureKeyGeneration_100_811 = 0,
NcaMainSignatureKeyGeneration_900_1202 = 1,
NcaMainSignatureKeyGeneration_Current = NcaMainSignatureKeyGeneration_900_1202,
NcaMainSignatureKeyGeneration_Max = (NcaMainSignatureKeyGeneration_Current + 1)
} NcaMainSignatureKeyGeneration;
typedef struct { typedef struct {
u32 start_sector; ///< Expressed in NCA_FS_SECTOR_SIZE sectors. u32 start_sector; ///< Expressed in NCA_FS_SECTOR_SIZE sectors.
u32 end_sector; ///< Expressed in NCA_FS_SECTOR_SIZE sectors. u32 end_sector; ///< Expressed in NCA_FS_SECTOR_SIZE sectors.
@ -139,7 +147,7 @@ typedef struct {
u32 content_index; u32 content_index;
VersionType2 sdk_addon_version; VersionType2 sdk_addon_version;
u8 key_generation; ///< NcaKeyGeneration. u8 key_generation; ///< NcaKeyGeneration.
u8 main_signature_key_generation; u8 main_signature_key_generation; ///< NcaMainSignatureKeyGeneration.
u8 reserved[0xE]; u8 reserved[0xE];
FsRightsId rights_id; ///< Used for titlekey crypto. FsRightsId rights_id; ///< Used for titlekey crypto.
NcaFsInfo fs_info[NCA_FS_HEADER_COUNT]; ///< Start and end sectors for each NCA FS section. NcaFsInfo fs_info[NCA_FS_HEADER_COUNT]; ///< Start and end sectors for each NCA FS section.

View file

@ -437,7 +437,8 @@ typedef enum {
NpdmSystemCallId_CreateResourceLimit = BIT(5), NpdmSystemCallId_CreateResourceLimit = BIT(5),
NpdmSystemCallId_SetResourceLimitLimitValue = BIT(6), NpdmSystemCallId_SetResourceLimitLimitValue = BIT(6),
NpdmSystemCallId_CallSecureMonitor = BIT(7), NpdmSystemCallId_CallSecureMonitor = BIT(7),
NpdmSystemCallId_Count = 0x80 ///< Total values supported by this enum.
NpdmSystemCallId_Count = 0xC0 ///< Total values supported by this enum.
} NpdmSystemCallId; } NpdmSystemCallId;
/// EnableSystemCalls entry for the KernelCapability descriptor. /// EnableSystemCalls entry for the KernelCapability descriptor.

View file

@ -85,7 +85,7 @@ typedef struct {
NXDT_ASSERT(NsoHeader, 0x100); NXDT_ASSERT(NsoHeader, 0x100);
/// Usually placed right after NsoHeader, but it's actual offset may vary. /// Usually placed right after NsoHeader, but its actual offset may vary.
/// If the 'module_name_size' member from NsoHeader is greater than 1 and the 'name_length' element from NsoModuleName is greater than 0, 'name' will hold the module name. /// If the 'module_name_size' member from NsoHeader is greater than 1 and the 'name_length' element from NsoModuleName is greater than 0, 'name' will hold the module name.
typedef struct { typedef struct {
u8 name_length; u8 name_length;

View file

@ -32,6 +32,8 @@ extern "C" {
#define APP_BASE_PATH "sdmc:/switch/" APP_TITLE "/" #define APP_BASE_PATH "sdmc:/switch/" APP_TITLE "/"
#define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:"
#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member) #define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) #define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0])))
@ -44,7 +46,14 @@ extern "C" {
#define IS_POWER_OF_TWO(x) (((x) & ((x) - 1)) == 0) #define IS_POWER_OF_TWO(x) (((x) & ((x) - 1)) == 0)
#define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:" #define SCOPED_LOCK(mtx) for(UtilsScopedLock scoped_lock __attribute__((__cleanup__(utilsUnlockScope))) = utilsLockScope(mtx); scoped_lock.cond; scoped_lock.cond = 0)
/// Used by scoped locks.
typedef struct {
Mutex *mtx;
bool lock;
int cond;
} UtilsScopedLock;
/// Used to determine which CFW is the application running under. /// Used to determine which CFW is the application running under.
typedef enum { typedef enum {
@ -54,18 +63,46 @@ typedef enum {
UtilsCustomFirmwareType_ReiNX = 3 UtilsCustomFirmwareType_ReiNX = 3
} UtilsCustomFirmwareType; } UtilsCustomFirmwareType;
/// Resource (de)initialization. /// Resource initialization.
/// Called at program startup and exit. /// Called at program startup.
bool utilsInitializeResources(void); bool utilsInitializeResources(const int program_argc, const char **program_argv);
/// Resource deinitialization.
/// Called at program exit.
void utilsCloseResources(void); void utilsCloseResources(void);
/// Returns a pointer to the application launch path.
const char *utilsGetLaunchPath(void);
/// Returns a pointer to the FsFileSystem object for the SD card.
FsFileSystem *utilsGetSdCardFileSystemObject(void);
/// Commits SD card filesystem changes.
/// Must be used after closing a file handle from the SD card.
bool utilsCommitSdCardFileSystemChanges(void);
/// Returns a UtilsCustomFirmwareType value.
u8 utilsGetCustomFirmwareType(void);
/// Returns true if the application is running under a development unit.
bool utilsIsDevelopmentUnit(void);
/// Returns true if the application is running under applet mode.
bool utilsAppletModeCheck(void);
/// Returns a pointer to the FsStorage object for the eMMC BIS System partition.
FsStorage *utilsGetEmmcBisSystemPartitionStorage(void);
/// Enables/disables CPU/MEM overclocking.
void utilsOverclockSystem(bool overclock);
/// (Un)blocks HOME button presses.
void utilsChangeHomeButtonBlockStatus(bool block);
/// Thread management functions. /// Thread management functions.
bool utilsCreateThread(Thread *out_thread, ThreadFunc func, void *arg, int cpu_id); bool utilsCreateThread(Thread *out_thread, ThreadFunc func, void *arg, int cpu_id);
void utilsJoinThread(Thread *thread); void utilsJoinThread(Thread *thread);
/// Returns true if the application is running under a development unit.
bool utilsIsDevelopmentUnit(void);
/// Formats a string and appends it to the provided buffer. /// Formats a string and appends it to the provided buffer.
/// If the buffer isn't big enough to hold both its current contents and the new formatted string, it will be resized. /// If the buffer isn't big enough to hold both its current contents and the new formatted string, it will be resized.
__attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...); __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...);
@ -89,13 +126,6 @@ void utilsGenerateFormattedSizeString(u64 size, char *dst, size_t dst_size);
/// Returns false if there's an error. /// Returns false if there's an error.
bool utilsGetFileSystemStatsByPath(const char *path, u64 *out_total, u64 *out_free); bool utilsGetFileSystemStatsByPath(const char *path, u64 *out_total, u64 *out_free);
/// Returns a pointer to the FsFileSystem object for the SD card.
FsFileSystem *utilsGetSdCardFileSystemObject(void);
/// Commits SD card filesystem changes.
/// Must be used after closing a file handle from the SD card.
bool utilsCommitSdCardFileSystemChanges(void);
/// Returns true if a file exists. /// Returns true if a file exists.
bool utilsCheckIfFileExists(const char *path); bool utilsCheckIfFileExists(const char *path);
@ -112,27 +142,25 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element);
/// Returns a pointer to a dynamically allocated string that holds the full path formed by the provided arguments. /// Returns a pointer to a dynamically allocated string that holds the full path formed by the provided arguments.
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension); char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension);
/// Returns true if the application is running under Applet Mode.
bool utilsAppletModeCheck(void);
/// (Un)blocks HOME button presses.
void utilsChangeHomeButtonBlockStatus(bool block);
/// Returns a UtilsCustomFirmwareType value.
u8 utilsGetCustomFirmwareType(void);
/// Returns a pointer to the FsStorage object for the eMMC BIS System partition.
FsStorage *utilsGetEmmcBisSystemPartitionStorage(void);
/// Enables/disables CPU/MEM overclocking.
void utilsOverclockSystem(bool overclock);
/// Simple wrapper to sleep the current thread for a specific number of full seconds. /// Simple wrapper to sleep the current thread for a specific number of full seconds.
NX_INLINE void utilsSleep(u64 seconds) NX_INLINE void utilsSleep(u64 seconds)
{ {
if (seconds) svcSleepThread(seconds * (u64)1000000000); if (seconds) svcSleepThread(seconds * (u64)1000000000);
} }
/// Wrappers used in scoped locks.
NX_INLINE UtilsScopedLock utilsLockScope(Mutex *mtx)
{
UtilsScopedLock scoped_lock = { mtx, !mutexIsLockedByCurrentThread(mtx), 1 };
if (scoped_lock.lock) mutexLock(scoped_lock.mtx);
return scoped_lock;
}
NX_INLINE void utilsUnlockScope(UtilsScopedLock *scoped_lock)
{
if (scoped_lock->lock) mutexUnlock(scoped_lock->mtx);
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -42,10 +42,10 @@ void usbExit(void);
/// Returns a pointer to a dynamically allocated, page aligned memory buffer that's suitable for USB transfers. /// Returns a pointer to a dynamically allocated, page aligned memory buffer that's suitable for USB transfers.
void *usbAllocatePageAlignedBuffer(size_t size); void *usbAllocatePageAlignedBuffer(size_t size);
/// Used to check if the console has been connected to an USB host device and if a valid USB session has been established. /// Used to check if the console has been connected to a USB host device and if a valid USB session has been established.
/// Bear in mind this call will block the calling thread if the console is connected to an USB host device but no USB session has been established. /// Bear in mind this call will block the calling thread if the console is connected to a USB host device but no USB session has been established.
/// If the console is disconnected during this block, the function will return false. /// If the console is disconnected during this block, the function will return false.
/// If the console isn't connected to an USB host device when this function is called, false will be returned right away. /// If the console isn't connected to a USB host device when this function is called, false will be returned right away.
bool usbIsReady(void); bool usbIsReady(void);
/// Sends file properties to the host device before starting a file data transfer. Must be called before usbSendFileData(). /// Sends file properties to the host device before starting a file data transfer. Must be called before usbSendFileData().

View file

@ -1,8 +1,8 @@
/*----------------------------------------------------------------------------/ /*----------------------------------------------------------------------------/
/ FatFs - Generic FAT Filesystem module R0.14 / / FatFs - Generic FAT Filesystem module R0.14b /
/-----------------------------------------------------------------------------/ /-----------------------------------------------------------------------------/
/ /
/ Copyright (C) 2019, ChaN, all right reserved. / Copyright (C) 2021, ChaN, all right reserved.
/ /
/ FatFs module is an open source software. Redistribution and use of FatFs in / FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided / source and binary forms, with or without modification, are permitted provided
@ -20,7 +20,7 @@
#ifndef FF_DEFINED #ifndef FF_DEFINED
#define FF_DEFINED 86606 /* Revision ID */ #define FF_DEFINED 86631 /* Revision ID */
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -35,10 +35,14 @@ extern "C" {
/* Integer types used for FatFs API */ /* Integer types used for FatFs API */
#if defined(_WIN32) /* Main development platform */ #if defined(_WIN32) /* Windows VC++ (for development only) */
#define FF_INTDEF 2 #define FF_INTDEF 2
#include <windows.h> #include <windows.h>
typedef unsigned __int64 QWORD; typedef unsigned __int64 QWORD;
#include <float.h>
#define isnan(v) _isnan(v)
#define isinf(v) (!_finite(v))
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */ #elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
#define FF_INTDEF 2 #define FF_INTDEF 2
#include <stdint.h> #include <stdint.h>
@ -48,6 +52,7 @@ typedef uint16_t WORD; /* 16-bit unsigned integer */
typedef uint32_t DWORD; /* 32-bit unsigned integer */ typedef uint32_t DWORD; /* 32-bit unsigned integer */
typedef uint64_t QWORD; /* 64-bit unsigned integer */ typedef uint64_t QWORD; /* 64-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */ typedef WORD WCHAR; /* UTF-16 character type */
#else /* Earlier than C99 */ #else /* Earlier than C99 */
#define FF_INTDEF 1 #define FF_INTDEF 1
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
@ -58,28 +63,29 @@ typedef WORD WCHAR; /* UTF-16 character type */
#endif #endif
/* Definitions of volume management */ /* Type of file size and LBA variables */
#if FF_MULTI_PARTITION /* Multiple partition configuration */ #if FF_FS_EXFAT
typedef struct { #if FF_INTDEF != 2
BYTE pd; /* Physical drive number */ #error exFAT feature wants C99 or later
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
#endif #endif
typedef QWORD FSIZE_t;
#if FF_STR_VOLUME_ID #if FF_LBA64
#ifndef FF_VOLUME_STRS typedef QWORD LBA_t;
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */ #else
typedef DWORD LBA_t;
#endif #endif
#else
#if FF_LBA64
#error exFAT needs to be enabled when enable 64-bit LBA
#endif
typedef DWORD FSIZE_t;
typedef DWORD LBA_t;
#endif #endif
/* Type of path name strings on FatFs API */ /* Type of path name strings on FatFs API (TCHAR) */
#ifndef _INC_TCHAR
#define _INC_TCHAR
#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ #if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */
typedef WCHAR TCHAR; typedef WCHAR TCHAR;
@ -101,28 +107,22 @@ typedef char TCHAR;
#define _TEXT(x) x #define _TEXT(x) x
#endif #endif
#endif
/* Definitions of volume management */
/* Type of file size and LBA variables */ #if FF_MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
#endif
#if FF_FS_EXFAT #if FF_STR_VOLUME_ID
#if FF_INTDEF != 2 #ifndef FF_VOLUME_STRS
#error exFAT feature wants C99 or later extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
#endif #endif
typedef QWORD FSIZE_t;
#if FF_LBA64
typedef QWORD LBA_t;
#else
typedef DWORD LBA_t;
#endif
#else
#if FF_LBA64
#error exFAT needs to be enabled when enable 64-bit LBA
#endif
typedef DWORD FSIZE_t;
typedef DWORD LBA_t;
#endif #endif
@ -345,10 +345,6 @@ TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the fil
#define f_rmdir(path) f_unlink(path) #define f_rmdir(path) f_unlink(path)
#define f_unmount(path) f_mount(0, path, 0) #define f_unmount(path) f_mount(0, path, 0)
#ifndef EOF
#define EOF (-1)
#endif

View file

@ -2,7 +2,7 @@
/ FatFs Functional Configurations / FatFs Functional Configurations
/---------------------------------------------------------------------------*/ /---------------------------------------------------------------------------*/
#define FFCONF_DEF 86606 /* Revision ID */ #define FFCONF_DEF 86631 /* Revision ID */
/*---------------------------------------------------------------------------/ /*---------------------------------------------------------------------------/
/ Function Configurations / Function Configurations
@ -25,14 +25,6 @@
/ 3: f_lseek() function is removed in addition to 2. */ / 3: f_lseek() function is removed in addition to 2. */
#define FF_USE_STRFUNC 0
/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */
#define FF_USE_FIND 0 #define FF_USE_FIND 0
/* This option switches filtered directory read functions, f_findfirst() and /* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ / f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
@ -64,6 +56,30 @@
/* This option switches f_forward() function. (0:Disable or 1:Enable) */ /* This option switches f_forward() function. (0:Disable or 1:Enable) */
#define FF_USE_STRFUNC 0
#define FF_PRINT_LLI 0
#define FF_PRINT_FLOAT 0
#define FF_STRF_ENCODE 0
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
/ f_printf().
/
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion.
/
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
makes f_printf() support floating point argument. These features want C99 or later.
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
/ to be read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
/*---------------------------------------------------------------------------/ /*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations / Locale and Namespace Configurations
/---------------------------------------------------------------------------*/ /---------------------------------------------------------------------------*/
@ -137,19 +153,6 @@
/ on character encoding. When LFN is not enabled, these options have no effect. */ / on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_STRF_ENCODE 3
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
/ f_putc(), f_puts and f_printf() convert the character encoding in it.
/ This option selects assumption of character encoding ON THE FILE to be
/ read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
#define FF_FS_RPATH 0 #define FF_FS_RPATH 0
/* This option configures support for relative path. /* This option configures support for relative path.
/ /
@ -194,19 +197,19 @@
#define FF_MAX_SS 512 #define FF_MAX_SS 512
/* This set of options configures the range of sector size to be supported. (512, /* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and / 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk. But a larger value may be required for on-board flash memory and some / harddisk, but a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured / type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement / for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */ / GET_SECTOR_SIZE command. */
#define FF_LBA64 0 #define FF_LBA64 1
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable) /* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */ / To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
#define FF_MIN_GPT 0x100000000 #define FF_MIN_GPT 0x10000000
/* Minimum number of sectors to switch GPT format to create partition in f_mkfs and /* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */ / f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */
@ -228,7 +231,7 @@
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */ / buffer in the filesystem object (FATFS) is used for the file data transfer. */
#define FF_FS_EXFAT 0 #define FF_FS_EXFAT 1
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable) /* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1) / To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */ / Note that enabling exFAT discards ANSI C (C89) compatibility. */

View file

@ -58,136 +58,142 @@ static bool bfttfDecodeFont(BfttfFontInfo *font_info);
bool bfttfInitialize(void) bool bfttfInitialize(void)
{ {
mutexLock(&g_bfttfMutex);
u32 count = 0;
NcaContext *nca_ctx = NULL; NcaContext *nca_ctx = NULL;
TitleInfo *title_info = NULL;
u64 prev_title_id = 0;
RomFileSystemContext romfs_ctx = {0}; RomFileSystemContext romfs_ctx = {0};
RomFileSystemFileEntry *romfs_file_entry = NULL; bool ret = false;
bool ret = g_bfttfInterfaceInit; SCOPED_LOCK(&g_bfttfMutex)
if (ret) goto end;
/* Allocate memory for a temporary NCA context. */
nca_ctx = calloc(1, sizeof(NcaContext));
if (!nca_ctx)
{ {
LOG_MSG("Failed to allocate memory for temporary NCA context!"); ret = g_bfttfInterfaceInit;
goto end; if (ret) break;
u32 count = 0;
u64 prev_title_id = 0;
/* Allocate memory for a temporary NCA context. */
nca_ctx = calloc(1, sizeof(NcaContext));
if (!nca_ctx)
{
LOG_MSG("Failed to allocate memory for temporary NCA context!");
break;
}
/* Retrieve BFTTF data. */
for(u32 i = 0; i < g_fontInfoCount; i++)
{
BfttfFontInfo *font_info = &(g_fontInfo[i]);
TitleInfo *title_info = NULL;
RomFileSystemFileEntry *romfs_file_entry = NULL;
/* Check if the title ID for the current font container matches the one from the previous font container. */
/* We won't have to reinitialize both NCA and RomFS contexts if that's the case. */
if (font_info->title_id != prev_title_id)
{
/* Get title info. */
if (!(title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, font_info->title_id)))
{
LOG_MSG("Failed to get title info for %016lX!", font_info->title_id);
continue;
}
/* Initialize NCA context. */
if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), NULL))
{
LOG_MSG("Failed to initialize Data NCA context for %016lX!", font_info->title_id);
continue;
}
/* Initialize RomFS context. */
/* This will also free a previous RomFS context, if available. */
if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[0])))
{
LOG_MSG("Failed to initialize RomFS context for Data NCA from %016lX!", font_info->title_id);
continue;
}
/* Update previous title ID. */
prev_title_id = font_info->title_id;
}
/* Get RomFS file entry. */
if (!(romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, font_info->path)))
{
LOG_MSG("Failed to retrieve RomFS file entry in %016lX!", font_info->title_id);
continue;
}
/* Check file size. */
if (!romfs_file_entry->size)
{
LOG_MSG("File size for \"%s\" in %016lX is zero!", font_info->path, font_info->title_id);
continue;
}
/* Allocate memory for BFTTF data. */
if (!(font_info->data = malloc(romfs_file_entry->size)))
{
LOG_MSG("Failed to allocate 0x%lX bytes for \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id);
continue;
}
/* Read BFTFF data. */
if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, font_info->data, romfs_file_entry->size, 0))
{
LOG_MSG("Failed to read 0x%lX bytes long \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id);
free(font_info->data);
font_info->data = NULL;
continue;
}
/* Update BFTTF size. */
font_info->size = (u32)romfs_file_entry->size;
/* Decode BFTTF data. */
if (!bfttfDecodeFont(font_info))
{
LOG_MSG("Failed to decode 0x%lX bytes long \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id);
free(font_info->data);
font_info->data = NULL;
font_info->size = 0;
continue;
}
/* Increase retrieved BFTTF count. */
count++;
}
/* Update flags. */
ret = g_bfttfInterfaceInit = (count > 0);
if (!ret) LOG_MSG("No BFTTF fonts retrieved!");
} }
/* Retrieve BFTTF data. */
for(u32 i = 0; i < g_fontInfoCount; i++)
{
BfttfFontInfo *font_info = &(g_fontInfo[i]);
/* Check if the title ID for the current font container matches the one from the previous font container. */
/* We won't have to reinitialize both NCA and RomFS contexts if that's the case. */
if (font_info->title_id != prev_title_id)
{
/* Get title info. */
if (!(title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, font_info->title_id)))
{
LOG_MSG("Failed to get title info for %016lX!", font_info->title_id);
continue;
}
/* Initialize NCA context. */
if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), NULL))
{
LOG_MSG("Failed to initialize Data NCA context for %016lX!", font_info->title_id);
continue;
}
/* Initialize RomFS context. */
if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[0])))
{
LOG_MSG("Failed to initialize RomFS context for Data NCA from %016lX!", font_info->title_id);
continue;
}
/* Update previous title ID. */
prev_title_id = font_info->title_id;
}
/* Get RomFS file entry. */
if (!(romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, font_info->path)))
{
LOG_MSG("Failed to retrieve RomFS file entry in %016lX!", font_info->title_id);
continue;
}
/* Check file size. */
if (!romfs_file_entry->size)
{
LOG_MSG("File size for \"%s\" in %016lX is zero!", font_info->path, font_info->title_id);
continue;
}
/* Allocate memory for BFTTF data. */
if (!(font_info->data = malloc(romfs_file_entry->size)))
{
LOG_MSG("Failed to allocate 0x%lX bytes for \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id);
continue;
}
/* Read BFTFF data. */
if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, font_info->data, romfs_file_entry->size, 0))
{
LOG_MSG("Failed to read 0x%lX bytes long \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id);
free(font_info->data);
font_info->data = NULL;
continue;
}
/* Update BFTTF size. */
font_info->size = (u32)romfs_file_entry->size;
/* Decode BFTTF data. */
if (!bfttfDecodeFont(font_info))
{
LOG_MSG("Failed to decode 0x%lX bytes long \"%s\" in %016lX!", romfs_file_entry->size, font_info->path, font_info->title_id);
free(font_info->data);
font_info->data = NULL;
font_info->size = 0;
continue;
}
/* Increase retrieved BFTTF count. */
count++;
}
ret = g_bfttfInterfaceInit = (count > 0);
if (!ret) LOG_MSG("No BFTTF fonts retrieved!");
end:
romfsFreeContext(&romfs_ctx); romfsFreeContext(&romfs_ctx);
if (nca_ctx) free(nca_ctx); if (nca_ctx) free(nca_ctx);
mutexUnlock(&g_bfttfMutex);
return ret; return ret;
} }
void bfttfExit(void) void bfttfExit(void)
{ {
mutexLock(&g_bfttfMutex); SCOPED_LOCK(&g_bfttfMutex)
/* Free BFTTF data. */
for(u32 i = 0; i < g_fontInfoCount; i++)
{ {
BfttfFontInfo *font_info = &(g_fontInfo[i]); /* Free BFTTF data. */
font_info->size = 0; for(u32 i = 0; i < g_fontInfoCount; i++)
if (font_info->data) free(font_info->data); {
BfttfFontInfo *font_info = &(g_fontInfo[i]);
font_info->size = 0;
if (font_info->data)
{
free(font_info->data);
font_info->data = NULL;
}
}
g_bfttfInterfaceInit = false;
} }
g_bfttfInterfaceInit = false;
mutexUnlock(&g_bfttfMutex);
} }
bool bfttfGetFontByType(BfttfFontData *font_data, u8 font_type) bool bfttfGetFontByType(BfttfFontData *font_data, u8 font_type)
@ -198,18 +204,25 @@ bool bfttfGetFontByType(BfttfFontData *font_data, u8 font_type)
return false; return false;
} }
BfttfFontInfo *font_info = &(g_fontInfo[font_type]); bool ret = false;
if (font_info->size <= 8 || !font_info->data)
SCOPED_LOCK(&g_bfttfMutex)
{ {
LOG_MSG("BFTTF font data unavailable for type 0x%02X!", font_type); BfttfFontInfo *font_info = &(g_fontInfo[font_type]);
return false; if (font_info->size <= 8 || !font_info->data)
{
LOG_MSG("BFTTF font data unavailable for type 0x%02X!", font_type);
break;
}
font_data->type = font_type;
font_data->size = (font_info->size - 8);
font_data->address = (font_info->data + 8);
ret = true;
} }
font_data->type = font_type; return ret;
font_data->size = (font_info->size - 8);
font_data->address = (font_info->data + 8);
return true;
} }
static bool bfttfDecodeFont(BfttfFontInfo *font_info) static bool bfttfDecodeFont(BfttfFontInfo *font_info)

View file

@ -51,48 +51,40 @@ static void certCopyCertificateChainDataToMemoryBuffer(void *dst, const Certific
bool certRetrieveCertificateByName(Certificate *dst, const char *name) bool certRetrieveCertificateByName(Certificate *dst, const char *name)
{ {
mutexLock(&g_esCertSaveMutex);
bool ret = false;
if (!dst || !name || !*name) if (!dst || !name || !*name)
{ {
LOG_MSG("Invalid parameters!"); LOG_MSG("Invalid parameters!");
goto end; return false;
} }
if (!certOpenEsCertSaveFile()) goto end; bool ret = false;
ret = _certRetrieveCertificateByName(dst, name); SCOPED_LOCK(&g_esCertSaveMutex)
{
certCloseEsCertSaveFile(); if (!certOpenEsCertSaveFile()) break;
ret = _certRetrieveCertificateByName(dst, name);
end: certCloseEsCertSaveFile();
mutexUnlock(&g_esCertSaveMutex); }
return ret; return ret;
} }
bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer) bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer)
{ {
mutexLock(&g_esCertSaveMutex);
bool ret = false;
if (!dst || !issuer || strncmp(issuer, "Root-", 5) != 0) if (!dst || !issuer || strncmp(issuer, "Root-", 5) != 0)
{ {
LOG_MSG("Invalid parameters!"); LOG_MSG("Invalid parameters!");
goto end; return false;
} }
if (!certOpenEsCertSaveFile()) goto end; bool ret = false;
ret = _certRetrieveCertificateChainBySignatureIssuer(dst, issuer); SCOPED_LOCK(&g_esCertSaveMutex)
{
certCloseEsCertSaveFile(); if (!certOpenEsCertSaveFile()) break;
ret = _certRetrieveCertificateChainBySignatureIssuer(dst, issuer);
end: certCloseEsCertSaveFile();
mutexUnlock(&g_esCertSaveMutex); }
return ret; return ret;
} }

View file

@ -123,7 +123,7 @@ static bool gamecardGetHandleAndStorage(u32 partition);
NX_INLINE void gamecardCloseHandle(void); NX_INLINE void gamecardCloseHandle(void);
static bool gamecardOpenStorageArea(u8 area); static bool gamecardOpenStorageArea(u8 area);
static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool lock); static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset);
static void gamecardCloseStorageArea(void); static void gamecardCloseStorageArea(void);
static bool gamecardGetStorageAreasSizes(void); static bool gamecardGetStorageAreasSizes(void);
@ -134,228 +134,247 @@ static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition
bool gamecardInitialize(void) bool gamecardInitialize(void)
{ {
mutexLock(&g_gameCardMutex);
Result rc = 0; Result rc = 0;
bool ret = false;
bool ret = g_gameCardInterfaceInit; SCOPED_LOCK(&g_gameCardMutex)
if (ret) goto end;
/* Allocate memory for the gamecard read buffer. */
g_gameCardReadBuf = malloc(GAMECARD_READ_BUFFER_SIZE);
if (!g_gameCardReadBuf)
{ {
LOG_MSG("Unable to allocate memory for the gamecard read buffer!"); ret = g_gameCardInterfaceInit;
goto end; if (ret) break;
/* Allocate memory for the gamecard read buffer. */
g_gameCardReadBuf = malloc(GAMECARD_READ_BUFFER_SIZE);
if (!g_gameCardReadBuf)
{
LOG_MSG("Unable to allocate memory for the gamecard read buffer!");
break;
}
/* Open device operator. */
rc = fsOpenDeviceOperator(&g_deviceOperator);
if (R_FAILED(rc))
{
LOG_MSG("fsOpenDeviceOperator failed! (0x%08X).", rc);
break;
}
g_openDeviceOperator = true;
/* Open gamecard detection event notifier. */
rc = fsOpenGameCardDetectionEventNotifier(&g_gameCardEventNotifier);
if (R_FAILED(rc))
{
LOG_MSG("fsOpenGameCardDetectionEventNotifier failed! (0x%08X)", rc);
break;
}
g_openEventNotifier = true;
/* Retrieve gamecard detection kernel event. */
rc = fsEventNotifierGetEventHandle(&g_gameCardEventNotifier, &g_gameCardKernelEvent, true);
if (R_FAILED(rc))
{
LOG_MSG("fsEventNotifierGetEventHandle failed! (0x%08X)", rc);
break;
}
g_loadKernelEvent = true;
/* Create user-mode exit event. */
ueventCreate(&g_gameCardDetectionThreadExitEvent, true);
/* Create user-mode gamecard status change event. */
ueventCreate(&g_gameCardStatusChangeEvent, true);
/* Create gamecard detection thread. */
if (!(g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread())) break;
/* Update flags. */
ret = g_gameCardInterfaceInit = true;
} }
/* Open device operator. */
rc = fsOpenDeviceOperator(&g_deviceOperator);
if (R_FAILED(rc))
{
LOG_MSG("fsOpenDeviceOperator failed! (0x%08X).", rc);
goto end;
}
g_openDeviceOperator = true;
/* Open gamecard detection event notifier. */
rc = fsOpenGameCardDetectionEventNotifier(&g_gameCardEventNotifier);
if (R_FAILED(rc))
{
LOG_MSG("fsOpenGameCardDetectionEventNotifier failed! (0x%08X)", rc);
goto end;
}
g_openEventNotifier = true;
/* Retrieve gamecard detection kernel event. */
rc = fsEventNotifierGetEventHandle(&g_gameCardEventNotifier, &g_gameCardKernelEvent, true);
if (R_FAILED(rc))
{
LOG_MSG("fsEventNotifierGetEventHandle failed! (0x%08X)", rc);
goto end;
}
g_loadKernelEvent = true;
/* Create user-mode exit event. */
ueventCreate(&g_gameCardDetectionThreadExitEvent, true);
/* Create user-mode gamecard status change event. */
ueventCreate(&g_gameCardStatusChangeEvent, true);
/* Create gamecard detection thread. */
if (!(g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread())) goto end;
ret = g_gameCardInterfaceInit = true;
end:
mutexUnlock(&g_gameCardMutex);
return ret; return ret;
} }
void gamecardExit(void) void gamecardExit(void)
{ {
mutexLock(&g_gameCardMutex); SCOPED_LOCK(&g_gameCardMutex)
/* Destroy gamecard detection thread. */
if (g_gameCardDetectionThreadCreated)
{ {
gamecardDestroyDetectionThread(); /* Destroy gamecard detection thread. */
g_gameCardDetectionThreadCreated = false; 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;
}
g_gameCardInterfaceInit = 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;
}
g_gameCardInterfaceInit = false;
mutexUnlock(&g_gameCardMutex);
} }
UEvent *gamecardGetStatusChangeUserEvent(void) UEvent *gamecardGetStatusChangeUserEvent(void)
{ {
mutexLock(&g_gameCardMutex); UEvent *event = NULL;
UEvent *event = (g_gameCardInterfaceInit ? &g_gameCardStatusChangeEvent : NULL);
mutexUnlock(&g_gameCardMutex); SCOPED_LOCK(&g_gameCardMutex)
{
if (g_gameCardInterfaceInit) event = &g_gameCardStatusChangeEvent;
}
return event; return event;
} }
u8 gamecardGetStatus(void) u8 gamecardGetStatus(void)
{ {
mutexLock(&g_gameCardMutex); u8 status = GameCardStatus_NotInserted;
u8 status = (g_gameCardInserted ? (g_gameCardInfoLoaded ? GameCardStatus_InsertedAndInfoLoaded : GameCardStatus_InsertedAndInfoNotLoaded) : GameCardStatus_NotInserted);
mutexUnlock(&g_gameCardMutex); SCOPED_LOCK(&g_gameCardMutex)
{
if (g_gameCardInserted) status = (g_gameCardInfoLoaded ? GameCardStatus_InsertedAndInfoLoaded : GameCardStatus_InsertedAndInfoNotLoaded);
}
return status; return status;
} }
bool gamecardReadStorage(void *out, u64 read_size, u64 offset) bool gamecardReadStorage(void *out, u64 read_size, u64 offset)
{ {
return gamecardReadStorageArea(out, read_size, offset, true); bool ret = false;
SCOPED_LOCK(&g_gameCardMutex) ret = gamecardReadStorageArea(out, read_size, offset);
return ret;
} }
/* Read full FS program memory to retrieve the GameCardInitialData block, which is part of the GameCardKeyArea block. */
/* In FS program memory, this is stored as part of the GameCardSecurityInformation struct, which is returned by Lotus command "ChangeToSecureMode" (0xF). */
/* This means it is only available *after* the gamecard secure area has been mounted, which is taken care of in gamecardReadInitialData(). */
/* The GameCardSecurityInformation struct is only kept for documentation purposes. It isn't used at all to retrieve the GameCardInitialData block. */
bool gamecardGetKeyArea(GameCardKeyArea *out) bool gamecardGetKeyArea(GameCardKeyArea *out)
{ {
/* Read full FS program memory to retrieve the GameCardInitialData block, which is part of the GameCardKeyArea block. */ bool ret = false;
/* In FS program memory, this is stored as part of the GameCardSecurityInformation struct, which is returned by Lotus command "ChangeToSecureMode" (0xF). */ SCOPED_LOCK(&g_gameCardMutex) ret = gamecardReadInitialData(out);
/* This means it is only available *after* the gamecard secure area has been mounted, which is taken care of in gamecardReadInitialData(). */
/* The GameCardSecurityInformation struct is only kept for documentation purposes. It isn't used at all to retrieve the GameCardInitialData block. */
mutexLock(&g_gameCardMutex);
bool ret = gamecardReadInitialData(out);
mutexUnlock(&g_gameCardMutex);
return ret; return ret;
} }
bool gamecardGetHeader(GameCardHeader *out) bool gamecardGetHeader(GameCardHeader *out)
{ {
mutexLock(&g_gameCardMutex); bool ret = false;
bool ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) memcpy(out, &g_gameCardHeader, sizeof(GameCardHeader)); SCOPED_LOCK(&g_gameCardMutex)
mutexUnlock(&g_gameCardMutex); {
ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) memcpy(out, &g_gameCardHeader, sizeof(GameCardHeader));
}
return ret; return ret;
} }
bool gamecardGetCertificate(FsGameCardCertificate *out) bool gamecardGetCertificate(FsGameCardCertificate *out)
{ {
Result rc = 0;
bool ret = false; bool ret = false;
mutexLock(&g_gameCardMutex); SCOPED_LOCK(&g_gameCardMutex)
if (g_gameCardInserted && g_gameCardHandle.value && out)
{ {
if (!g_gameCardInserted || !g_gameCardHandle.value || !out) break;
/* Read the gamecard certificate using the official IPC call. */ /* Read the gamecard certificate using the official IPC call. */
rc = fsDeviceOperatorGetGameCardDeviceCertificate(&g_deviceOperator, &g_gameCardHandle, out); Result rc = fsDeviceOperatorGetGameCardDeviceCertificate(&g_deviceOperator, &g_gameCardHandle, out);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X)", rc); LOG_MSG("fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X)", rc);
/* Attempt to manually read the gamecard certificate. */ /* Attempt to manually read the gamecard certificate. */
if (gamecardReadStorageArea(out, sizeof(FsGameCardCertificate), GAMECARD_CERTIFICATE_OFFSET, false)) rc = 0; if (gamecardReadStorageArea(out, sizeof(FsGameCardCertificate), GAMECARD_CERTIFICATE_OFFSET)) rc = 0;
} }
ret = R_SUCCEEDED(rc); ret = R_SUCCEEDED(rc);
} }
mutexUnlock(&g_gameCardMutex);
return ret; return ret;
} }
bool gamecardGetTotalSize(u64 *out) bool gamecardGetTotalSize(u64 *out)
{ {
mutexLock(&g_gameCardMutex); bool ret = false;
bool ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) *out = g_gameCardStorageTotalSize; SCOPED_LOCK(&g_gameCardMutex)
mutexUnlock(&g_gameCardMutex); {
ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) *out = g_gameCardStorageTotalSize;
}
return ret; return ret;
} }
bool gamecardGetTrimmedSize(u64 *out) bool gamecardGetTrimmedSize(u64 *out)
{ {
mutexLock(&g_gameCardMutex); bool ret = false;
bool ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) *out = (sizeof(GameCardHeader) + GAMECARD_PAGE_OFFSET(g_gameCardHeader.valid_data_end_address)); SCOPED_LOCK(&g_gameCardMutex)
mutexUnlock(&g_gameCardMutex); {
ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) *out = (sizeof(GameCardHeader) + GAMECARD_PAGE_OFFSET(g_gameCardHeader.valid_data_end_address));
}
return ret; return ret;
} }
bool gamecardGetRomCapacity(u64 *out) bool gamecardGetRomCapacity(u64 *out)
{ {
mutexLock(&g_gameCardMutex); bool ret = false;
bool ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) *out = g_gameCardCapacity; SCOPED_LOCK(&g_gameCardMutex)
mutexUnlock(&g_gameCardMutex); {
ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) *out = g_gameCardCapacity;
}
return ret; return ret;
} }
bool gamecardGetBundledFirmwareUpdateVersion(VersionType1 *out) bool gamecardGetBundledFirmwareUpdateVersion(VersionType1 *out)
{ {
Result rc = 0;
u64 update_id = 0;
u32 update_version = 0;
bool ret = false; bool ret = false;
mutexLock(&g_gameCardMutex); SCOPED_LOCK(&g_gameCardMutex)
if (g_gameCardInserted && g_gameCardHandle.value && out)
{ {
rc = fsDeviceOperatorUpdatePartitionInfo(&g_deviceOperator, &g_gameCardHandle, &update_version, &update_id); if (!g_gameCardInserted || !g_gameCardHandle.value || !out) break;
u64 update_id = 0;
u32 update_version = 0;
Result rc = fsDeviceOperatorUpdatePartitionInfo(&g_deviceOperator, &g_gameCardHandle, &update_version, &update_id);
if (R_FAILED(rc)) LOG_MSG("fsDeviceOperatorUpdatePartitionInfo failed! (0x%08X)", rc); if (R_FAILED(rc)) LOG_MSG("fsDeviceOperatorUpdatePartitionInfo failed! (0x%08X)", rc);
ret = (R_SUCCEEDED(rc) && update_id == GAMECARD_UPDATE_TID); ret = (R_SUCCEEDED(rc) && update_id == GAMECARD_UPDATE_TID);
if (ret) out->value = update_version; if (ret) out->value = update_version;
} }
mutexUnlock(&g_gameCardMutex);
return ret; return ret;
} }
@ -367,49 +386,46 @@ bool gamecardGetHashFileSystemContext(u8 hfs_partition_type, HashFileSystemConte
return false; return false;
} }
HashFileSystemContext *fs_ctx = NULL; bool ret = false;
bool success = false;
mutexLock(&g_gameCardMutex);
/* Free Hash FS context. */ /* Free Hash FS context. */
hfsFreeContext(out); hfsFreeContext(out);
/* Get pointer to the Hash FS context for the requested partition. */ SCOPED_LOCK(&g_gameCardMutex)
fs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type);
if (!fs_ctx) goto end;
/* Fill Hash FS context. */
out->name = strdup(fs_ctx->name);
if (!out->name)
{ {
LOG_MSG("Failed to duplicate Hash FS partition name! (%s).", fs_ctx->name); /* Get pointer to the Hash FS context for the requested partition. */
goto end; HashFileSystemContext *fs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type);
if (!fs_ctx) break;
/* Fill Hash FS context. */
out->name = strdup(fs_ctx->name);
if (!out->name)
{
LOG_MSG("Failed to duplicate Hash FS partition name! (%s).", fs_ctx->name);
break;
}
out->type = fs_ctx->type;
out->offset = fs_ctx->offset;
out->size = fs_ctx->size;
out->header_size = fs_ctx->header_size;
out->header = calloc(fs_ctx->header_size, sizeof(u8));
if (!out->header)
{
LOG_MSG("Failed to duplicate Hash FS partition header! (%s).", fs_ctx->name);
break;
}
memcpy(out->header, fs_ctx->header, fs_ctx->header_size);
/* Update flag. */
ret = true;
} }
out->type = fs_ctx->type; if (!ret) hfsFreeContext(out);
out->offset = fs_ctx->offset;
out->size = fs_ctx->size;
out->header_size = fs_ctx->header_size;
out->header = calloc(fs_ctx->header_size, sizeof(u8)); return ret;
if (!out->header)
{
LOG_MSG("Failed to duplicate Hash FS partition header! (%s).", fs_ctx->name);
goto end;
}
memcpy(out->header, fs_ctx->header, fs_ctx->header_size);
/* Update flag. */
success = true;
end:
if (!success) hfsFreeContext(out);
mutexUnlock(&g_gameCardMutex);
return success;
} }
bool gamecardGetHashFileSystemEntryInfoByName(u8 hfs_partition_type, const char *entry_name, u64 *out_offset, u64 *out_size) bool gamecardGetHashFileSystemEntryInfoByName(u8 hfs_partition_type, const char *entry_name, u64 *out_offset, u64 *out_size)
@ -420,31 +436,27 @@ bool gamecardGetHashFileSystemEntryInfoByName(u8 hfs_partition_type, const char
return false; return false;
} }
HashFileSystemContext *fs_ctx = NULL; bool ret = false;
HashFileSystemEntry *fs_entry = NULL;
bool success = false;
mutexLock(&g_gameCardMutex); SCOPED_LOCK(&g_gameCardMutex)
{
/* Get pointer to the Hash FS context for the requested partition. */
HashFileSystemContext *fs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type);
if (!fs_ctx) break;
/* Get Hash FS entry by name. */
HashFileSystemEntry *fs_entry = hfsGetEntryByName(fs_ctx, entry_name);
if (!fs_entry) break;
/* Update output variables. */
if (out_offset) *out_offset = (fs_ctx->offset + fs_ctx->header_size + fs_entry->offset);
if (out_size) *out_size = fs_entry->size;
/* Update flag. */
ret = true;
}
/* Get pointer to the Hash FS context for the requested partition. */ return ret;
fs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type);
if (!fs_ctx) goto end;
/* Get Hash FS entry by name. */
fs_entry = hfsGetEntryByName(fs_ctx, entry_name);
if (!fs_entry) goto end;
/* Update output variables. */
if (out_offset) *out_offset = (fs_ctx->offset + fs_ctx->header_size + fs_entry->offset);
if (out_size) *out_size = fs_entry->size;
/* Update flag. */
success = true;
end:
mutexUnlock(&g_gameCardMutex);
return success;
} }
static bool gamecardCreateDetectionThread(void) static bool gamecardCreateDetectionThread(void)
@ -479,10 +491,11 @@ static void gamecardDetectionThreadFunc(void *arg)
/* Retrieve initial gamecard insertion status. */ /* Retrieve initial gamecard insertion status. */
/* Load gamecard info right away if a gamecard is inserted, then signal the user mode gamecard status change event. */ /* Load gamecard info right away if a gamecard is inserted, then signal the user mode gamecard status change event. */
mutexLock(&g_gameCardMutex); SCOPED_LOCK(&g_gameCardMutex)
g_gameCardInserted = gamecardIsInserted(); {
if (g_gameCardInserted) gamecardLoadInfo(); g_gameCardInserted = gamecardIsInserted();
mutexUnlock(&g_gameCardMutex); if (g_gameCardInserted) gamecardLoadInfo();
}
ueventSignal(&g_gameCardStatusChangeEvent); ueventSignal(&g_gameCardStatusChangeEvent);
@ -495,24 +508,24 @@ static void gamecardDetectionThreadFunc(void *arg)
/* Exit event triggered. */ /* Exit event triggered. */
if (idx == 1) break; if (idx == 1) break;
mutexLock(&g_gameCardMutex); SCOPED_LOCK(&g_gameCardMutex)
/* Retrieve current gamecard insertion status. */
/* Only proceed if we're dealing with a status change. */
g_gameCardInserted = gamecardIsInserted();
gamecardFreeInfo();
if (g_gameCardInserted)
{ {
/* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules. */ /* Free gamecard info before proceeding. */
utilsSleep(GAMECARD_ACCESS_WAIT_TIME); gamecardFreeInfo();
/* Load gamecard info. */ /* Retrieve current gamecard insertion status. */
gamecardLoadInfo(); /* Only proceed if we're dealing with a status change. */
g_gameCardInserted = gamecardIsInserted();
if (g_gameCardInserted)
{
/* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules. */
utilsSleep(GAMECARD_ACCESS_WAIT_TIME);
/* Load gamecard info. */
gamecardLoadInfo();
}
} }
mutexUnlock(&g_gameCardMutex);
/* Signal user mode gamecard status change event. */ /* Signal user mode gamecard status change event. */
ueventSignal(&g_gameCardStatusChangeEvent); ueventSignal(&g_gameCardStatusChangeEvent);
} }
@ -550,7 +563,7 @@ static void gamecardLoadInfo(void)
} }
/* Read gamecard header. */ /* Read gamecard header. */
if (!gamecardReadStorageArea(&g_gameCardHeader, sizeof(GameCardHeader), 0, false)) if (!gamecardReadStorageArea(&g_gameCardHeader, sizeof(GameCardHeader), 0))
{ {
LOG_MSG("Failed to read gamecard header!"); LOG_MSG("Failed to read gamecard header!");
goto end; goto end;
@ -798,21 +811,18 @@ static bool gamecardOpenStorageArea(u8 area)
return true; return true;
} }
static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool lock) static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset)
{ {
if (lock) mutexLock(&g_gameCardMutex);
bool success = false;
if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardStorageTotalSize) if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardStorageTotalSize)
{ {
LOG_MSG("Invalid parameters!"); LOG_MSG("Invalid parameters!");
goto end; return false;
} }
Result rc = 0; Result rc = 0;
u8 *out_u8 = (u8*)out; u8 *out_u8 = (u8*)out;
u8 area = (offset < g_gameCardStorageNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure); u8 area = (offset < g_gameCardStorageNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure);
bool success = false;
/* Handle reads that span both the normal and secure gamecard storage areas. */ /* Handle reads that span both the normal and secure gamecard storage areas. */
if (area == GameCardStorageArea_Normal && (offset + read_size) > g_gameCardStorageNormalAreaSize) if (area == GameCardStorageArea_Normal && (offset + read_size) > g_gameCardStorageNormalAreaSize)
@ -820,7 +830,8 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
/* Calculate normal storage area size difference. */ /* Calculate normal storage area size difference. */
u64 diff_size = (g_gameCardStorageNormalAreaSize - offset); u64 diff_size = (g_gameCardStorageNormalAreaSize - offset);
if (!gamecardReadStorageArea(out_u8, diff_size, offset, false)) goto end; /* Read normal storage area data. */
if (!gamecardReadStorageArea(out_u8, diff_size, offset)) goto end;
/* Adjust variables to read right from the start of the secure storage area. */ /* Adjust variables to read right from the start of the secure storage area. */
read_size -= diff_size; read_size -= diff_size;
@ -870,12 +881,10 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
memcpy(out_u8, g_gameCardReadBuf + data_start_offset, out_chunk_size); memcpy(out_u8, g_gameCardReadBuf + data_start_offset, out_chunk_size);
success = (block_size > GAMECARD_READ_BUFFER_SIZE ? gamecardReadStorageArea(out_u8 + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size, false) : true); success = (block_size > GAMECARD_READ_BUFFER_SIZE ? gamecardReadStorageArea(out_u8 + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size) : true);
} }
end: end:
if (lock) mutexUnlock(&g_gameCardMutex);
return success; return success;
} }
@ -1014,7 +1023,7 @@ static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char
fs_ctx->type = i; fs_ctx->type = i;
/* Read partial Hash FS header. */ /* Read partial Hash FS header. */
if (!gamecardReadStorageArea(&fs_header, sizeof(HashFileSystemHeader), offset, false)) if (!gamecardReadStorageArea(&fs_header, sizeof(HashFileSystemHeader), offset))
{ {
LOG_MSG("Failed to read partial Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->name, offset); LOG_MSG("Failed to read partial Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->name, offset);
goto end; goto end;
@ -1050,7 +1059,7 @@ static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char
} }
/* Read full Hash FS header. */ /* Read full Hash FS header. */
if (!gamecardReadStorageArea(fs_ctx->header, fs_ctx->header_size, offset, false)) if (!gamecardReadStorageArea(fs_ctx->header, fs_ctx->header_size, offset))
{ {
LOG_MSG("Failed to read full Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->name, offset); LOG_MSG("Failed to read full Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->name, offset);
goto end; goto end;

View file

@ -33,11 +33,14 @@
/* Type definitions. */ /* Type definitions. */
typedef bool (*KeysIsKeyMandatoryFunction)(void); /* Used to determine if a key is mandatory or not at runtime. */
typedef struct { typedef struct {
char name[64]; char name[64];
u8 hash[SHA256_HASH_SIZE]; u8 hash[SHA256_HASH_SIZE];
u64 size; u64 size;
void *dst; void *dst;
KeysIsKeyMandatoryFunction mandatory_func; ///< If NULL, key is mandatory.
} KeysMemoryKey; } KeysMemoryKey;
typedef struct { typedef struct {
@ -53,6 +56,10 @@ typedef struct {
u8 nca_header_kek_sealed[AES_128_KEY_SIZE]; ///< Generated from nca_header_kek_source. Sealed by the SMC AES engine. u8 nca_header_kek_sealed[AES_128_KEY_SIZE]; ///< Generated from nca_header_kek_source. Sealed by the SMC AES engine.
u8 nca_header_key[AES_128_KEY_SIZE * 2]; ///< Generated from nca_header_kek_sealed and nca_header_key_source. u8 nca_header_key[AES_128_KEY_SIZE * 2]; ///< Generated from nca_header_kek_sealed and nca_header_key_source.
///< RSA-2048-PSS moduli used to verify the main signature from NCA headers.
u8 nca_main_signature_moduli_prod[NcaMainSignatureKeyGeneration_Max][RSA2048_PUBKEY_SIZE]; ///< Moduli used in retail units. Retrieved from the .rodata segment in the FS sysmodule.
u8 nca_main_signature_moduli_dev[NcaMainSignatureKeyGeneration_Max][RSA2048_PUBKEY_SIZE]; ///< Moduli used in development units. Retrieved from the .rodata segment in the FS sysmodule.
///< AES-128-ECB keys needed to handle key area crypto from NCA headers. ///< AES-128-ECB keys needed to handle key area crypto from NCA headers.
u8 nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Count][AES_128_KEY_SIZE]; ///< Retrieved from the .rodata segment in the FS sysmodule. u8 nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Count][AES_128_KEY_SIZE]; ///< Retrieved from the .rodata segment in the FS sysmodule.
u8 nca_kaek_sealed[NcaKeyAreaEncryptionKeyIndex_Count][NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Generated from nca_kaek_sources. Sealed by the SMC AES engine. u8 nca_kaek_sealed[NcaKeyAreaEncryptionKeyIndex_Count][NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Generated from nca_kaek_sources. Sealed by the SMC AES engine.
@ -80,6 +87,27 @@ typedef struct {
NXDT_ASSERT(EticketRsaDeviceKey, 0x240); NXDT_ASSERT(EticketRsaDeviceKey, 0x240);
/* Function prototypes. */
static bool keysIsProductionModulus1xMandatory(void);
static bool keysIsProductionModulus9xMandatory(void);
static bool keysIsDevelopmentModulus1xMandatory(void);
static bool keysIsDevelopmentModulus9xMandatory(void);
static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info);
static bool keysDeriveNcaHeaderKey(void);
static bool keysDeriveSealedNcaKeyAreaEncryptionKeys(void);
static int keysGetKeyAndValueFromFile(FILE *f, char **key, char **value);
static char keysConvertHexCharToBinary(char c);
static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size);
static bool keysReadKeysFromFile(void);
static bool keysGetDecryptedEticketRsaDeviceKey(void);
static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void *n);
/* Global variables. */ /* Global variables. */
static KeysNcaKeyset g_ncaKeyset = {0}; static KeysNcaKeyset g_ncaKeyset = {0};
@ -101,7 +129,7 @@ static KeysMemoryInfo g_fsRodataMemoryInfo = {
.data = NULL, .data = NULL,
.data_size = 0 .data_size = 0
}, },
.key_count = 4, .key_count = 8,
.keys = { .keys = {
{ {
.name = "nca_header_kek_source", .name = "nca_header_kek_source",
@ -109,8 +137,49 @@ static KeysMemoryInfo g_fsRodataMemoryInfo = {
0x18, 0x88, 0xCA, 0xED, 0x55, 0x51, 0xB3, 0xED, 0xE0, 0x14, 0x99, 0xE8, 0x7C, 0xE0, 0xD8, 0x68, 0x18, 0x88, 0xCA, 0xED, 0x55, 0x51, 0xB3, 0xED, 0xE0, 0x14, 0x99, 0xE8, 0x7C, 0xE0, 0xD8, 0x68,
0x27, 0xF8, 0x08, 0x20, 0xEF, 0xB2, 0x75, 0x92, 0x10, 0x55, 0xAA, 0x4E, 0x2A, 0xBD, 0xFF, 0xC2 0x27, 0xF8, 0x08, 0x20, 0xEF, 0xB2, 0x75, 0x92, 0x10, 0x55, 0xAA, 0x4E, 0x2A, 0xBD, 0xFF, 0xC2
}, },
.size = AES_128_KEY_SIZE, .size = sizeof(g_ncaKeyset.nca_header_kek_source),
.dst = g_ncaKeyset.nca_header_kek_source .dst = g_ncaKeyset.nca_header_kek_source,
.mandatory_func = NULL
},
{
.name = "nca_main_signature_modulus_prod_00",
.hash = {
0xF9, 0x2E, 0x84, 0x98, 0x17, 0x2C, 0xAF, 0x9C, 0x20, 0xE3, 0xF1, 0xF7, 0xD3, 0xE7, 0x2C, 0x62,
0x50, 0xA9, 0x40, 0x7A, 0xE7, 0x84, 0xE0, 0x03, 0x58, 0x07, 0x85, 0xA5, 0x68, 0x0B, 0x80, 0x33
},
.size = sizeof(g_ncaKeyset.nca_main_signature_moduli_prod[NcaMainSignatureKeyGeneration_100_811]),
.dst = g_ncaKeyset.nca_main_signature_moduli_prod[NcaMainSignatureKeyGeneration_100_811],
.mandatory_func = &keysIsProductionModulus1xMandatory
},
{
.name = "nca_main_signature_modulus_prod_01",
.hash = {
0x5F, 0x6B, 0xE3, 0x1C, 0x31, 0x6E, 0x7C, 0xB2, 0x1C, 0xA7, 0xB9, 0xA1, 0x70, 0x6A, 0x9D, 0x58,
0x04, 0xEB, 0x90, 0x53, 0x72, 0xEF, 0xCB, 0x56, 0xD1, 0x93, 0xF2, 0xAF, 0x9E, 0x8A, 0xD1, 0xFA
},
.size = sizeof(g_ncaKeyset.nca_main_signature_moduli_prod[NcaMainSignatureKeyGeneration_900_1202]),
.dst = g_ncaKeyset.nca_main_signature_moduli_prod[NcaMainSignatureKeyGeneration_900_1202],
.mandatory_func = &keysIsProductionModulus9xMandatory
},
{
.name = "nca_main_signature_modulus_dev_00",
.hash = {
0x50, 0xF8, 0x26, 0xBB, 0x13, 0xFE, 0xB2, 0x6D, 0x83, 0xCF, 0xFF, 0xD8, 0x38, 0x45, 0xC3, 0x51,
0x4D, 0xCB, 0x06, 0x91, 0x83, 0x52, 0x06, 0x35, 0x7A, 0xC1, 0xDA, 0x6B, 0xF1, 0x60, 0x9F, 0x18
},
.size = sizeof(g_ncaKeyset.nca_main_signature_moduli_dev[NcaMainSignatureKeyGeneration_100_811]),
.dst = g_ncaKeyset.nca_main_signature_moduli_dev[NcaMainSignatureKeyGeneration_100_811],
.mandatory_func = &keysIsDevelopmentModulus1xMandatory
},
{
.name = "nca_main_signature_modulus_dev_01",
.hash = {
0x56, 0xF5, 0x06, 0xEF, 0x8E, 0xCA, 0x2A, 0x29, 0x6F, 0x65, 0x45, 0xE1, 0x87, 0x60, 0x01, 0x11,
0xBC, 0xC7, 0x38, 0x56, 0x99, 0x16, 0xAD, 0xA5, 0xDD, 0x89, 0xF2, 0xE9, 0xAB, 0x28, 0x5B, 0x18
},
.size = sizeof(g_ncaKeyset.nca_main_signature_moduli_dev[NcaMainSignatureKeyGeneration_900_1202]),
.dst = g_ncaKeyset.nca_main_signature_moduli_dev[NcaMainSignatureKeyGeneration_900_1202],
.mandatory_func = &keysIsDevelopmentModulus9xMandatory
}, },
{ {
.name = "nca_kaek_application_source", .name = "nca_kaek_application_source",
@ -118,8 +187,9 @@ static KeysMemoryInfo g_fsRodataMemoryInfo = {
0x04, 0xAD, 0x66, 0x14, 0x3C, 0x72, 0x6B, 0x2A, 0x13, 0x9F, 0xB6, 0xB2, 0x11, 0x28, 0xB4, 0x6F, 0x04, 0xAD, 0x66, 0x14, 0x3C, 0x72, 0x6B, 0x2A, 0x13, 0x9F, 0xB6, 0xB2, 0x11, 0x28, 0xB4, 0x6F,
0x56, 0xC5, 0x53, 0xB2, 0xB3, 0x88, 0x71, 0x10, 0x30, 0x42, 0x98, 0xD8, 0xD0, 0x09, 0x2D, 0x9E 0x56, 0xC5, 0x53, 0xB2, 0xB3, 0x88, 0x71, 0x10, 0x30, 0x42, 0x98, 0xD8, 0xD0, 0x09, 0x2D, 0x9E
}, },
.size = AES_128_KEY_SIZE, .size = sizeof(g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Application]),
.dst = g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Application] .dst = g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Application],
.mandatory_func = NULL
}, },
{ {
.name = "nca_kaek_ocean_source", .name = "nca_kaek_ocean_source",
@ -127,8 +197,9 @@ static KeysMemoryInfo g_fsRodataMemoryInfo = {
0xFD, 0x43, 0x40, 0x00, 0xC8, 0xFF, 0x2B, 0x26, 0xF8, 0xE9, 0xA9, 0xD2, 0xD2, 0xC1, 0x2F, 0x6B, 0xFD, 0x43, 0x40, 0x00, 0xC8, 0xFF, 0x2B, 0x26, 0xF8, 0xE9, 0xA9, 0xD2, 0xD2, 0xC1, 0x2F, 0x6B,
0xE5, 0x77, 0x3C, 0xBB, 0x9D, 0xC8, 0x63, 0x00, 0xE1, 0xBD, 0x99, 0xF8, 0xEA, 0x33, 0xA4, 0x17 0xE5, 0x77, 0x3C, 0xBB, 0x9D, 0xC8, 0x63, 0x00, 0xE1, 0xBD, 0x99, 0xF8, 0xEA, 0x33, 0xA4, 0x17
}, },
.size = AES_128_KEY_SIZE, .size = sizeof(g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Ocean]),
.dst = g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Ocean] .dst = g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Ocean],
.mandatory_func = NULL
}, },
{ {
.name = "nca_kaek_system_source", .name = "nca_kaek_system_source",
@ -136,8 +207,9 @@ static KeysMemoryInfo g_fsRodataMemoryInfo = {
0x1F, 0x17, 0xB1, 0xFD, 0x51, 0xAD, 0x1C, 0x23, 0x79, 0xB5, 0x8F, 0x15, 0x2C, 0xA4, 0x91, 0x2E, 0x1F, 0x17, 0xB1, 0xFD, 0x51, 0xAD, 0x1C, 0x23, 0x79, 0xB5, 0x8F, 0x15, 0x2C, 0xA4, 0x91, 0x2E,
0xC2, 0x10, 0x64, 0x41, 0xE5, 0x17, 0x22, 0xF3, 0x87, 0x00, 0xD5, 0x93, 0x7A, 0x11, 0x62, 0xF7 0xC2, 0x10, 0x64, 0x41, 0xE5, 0x17, 0x22, 0xF3, 0x87, 0x00, 0xD5, 0x93, 0x7A, 0x11, 0x62, 0xF7
}, },
.size = AES_128_KEY_SIZE, .size = sizeof(g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_System]),
.dst = g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_System] .dst = g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_System],
.mandatory_func = NULL
} }
} }
}; };
@ -157,94 +229,84 @@ static KeysMemoryInfo g_fsDataMemoryInfo = {
0x8F, 0x78, 0x3E, 0x46, 0x85, 0x2D, 0xF6, 0xBE, 0x0B, 0xA4, 0xE1, 0x92, 0x73, 0xC4, 0xAD, 0xBA, 0x8F, 0x78, 0x3E, 0x46, 0x85, 0x2D, 0xF6, 0xBE, 0x0B, 0xA4, 0xE1, 0x92, 0x73, 0xC4, 0xAD, 0xBA,
0xEE, 0x16, 0x38, 0x00, 0x43, 0xE1, 0xB8, 0xC4, 0x18, 0xC4, 0x08, 0x9A, 0x8B, 0xD6, 0x4A, 0xA6 0xEE, 0x16, 0x38, 0x00, 0x43, 0xE1, 0xB8, 0xC4, 0x18, 0xC4, 0x08, 0x9A, 0x8B, 0xD6, 0x4A, 0xA6
}, },
.size = (AES_128_KEY_SIZE * 2), .size = sizeof(g_ncaKeyset.nca_header_key_source),
.dst = g_ncaKeyset.nca_header_key_source .dst = g_ncaKeyset.nca_header_key_source,
.mandatory_func = NULL
} }
} }
}; };
/* Function prototypes. */
static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info);
static bool keysDeriveNcaHeaderKey(void);
static bool keysDeriveSealedNcaKeyAreaEncryptionKeys(void);
static int keysGetKeyAndValueFromFile(FILE *f, char **key, char **value);
static char keysConvertHexCharToBinary(char c);
static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size);
static bool keysReadKeysFromFile(void);
static bool keysGetDecryptedEticketRsaDeviceKey(void);
static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void *n);
bool keysLoadNcaKeyset(void) bool keysLoadNcaKeyset(void)
{ {
mutexLock(&g_ncaKeysetMutex); bool ret = false;
bool ret = g_ncaKeysetLoaded; SCOPED_LOCK(&g_ncaKeysetMutex)
if (ret) goto end;
/* Retrieve FS .rodata keys. */
if (!keysRetrieveKeysFromProgramMemory(&g_fsRodataMemoryInfo))
{ {
LOG_MSG("Unable to retrieve keys from FS .rodata segment!"); ret = g_ncaKeysetLoaded;
goto end; if (ret) break;
/* Retrieve FS .rodata keys. */
if (!keysRetrieveKeysFromProgramMemory(&g_fsRodataMemoryInfo))
{
LOG_MSG("Unable to retrieve keys from FS .rodata segment!");
break;
}
/* Retrieve FS .data keys. */
if (!keysRetrieveKeysFromProgramMemory(&g_fsDataMemoryInfo))
{
LOG_MSG("Unable to retrieve keys from FS .data segment!");
break;
}
/* Derive NCA header key. */
if (!keysDeriveNcaHeaderKey())
{
LOG_MSG("Unable to derive NCA header key!");
break;
}
/* Derive sealed NCA KAEKs. */
if (!keysDeriveSealedNcaKeyAreaEncryptionKeys())
{
LOG_MSG("Unable to derive sealed NCA KAEKs!");
break;
}
/* Read additional keys from the keys file. */
if (!keysReadKeysFromFile()) break;
/* Get decrypted eTicket RSA device key. */
if (!keysGetDecryptedEticketRsaDeviceKey()) break;
/* Update flags. */
ret = g_ncaKeysetLoaded = true;
} }
/* Retrieve FS .data keys. */
if (!keysRetrieveKeysFromProgramMemory(&g_fsDataMemoryInfo))
{
LOG_MSG("Unable to retrieve keys from FS .data segment!");
goto end;
}
/* Derive NCA header key. */
if (!keysDeriveNcaHeaderKey())
{
LOG_MSG("Unable to derive NCA header key!");
goto end;
}
/* Derive sealed NCA KAEKs. */
if (!keysDeriveSealedNcaKeyAreaEncryptionKeys())
{
LOG_MSG("Unable to derive sealed NCA KAEKs!");
goto end;
}
/* Read additional keys from the keys file. */
if (!keysReadKeysFromFile()) goto end;
/* Get decrypted eTicket RSA device key. */
if (!keysGetDecryptedEticketRsaDeviceKey()) goto end;
ret = g_ncaKeysetLoaded = true;
end:
/*if (ret) /*if (ret)
{ {
LOG_DATA(&g_ncaKeyset, sizeof(KeysNcaKeyset), "NCA keyset dump:"); LOG_DATA(&g_ncaKeyset, sizeof(KeysNcaKeyset), "NCA keyset dump:");
LOG_DATA(&g_eTicketRsaDeviceKey, sizeof(SetCalRsa2048DeviceKey), "eTicket RSA device key dump:"); LOG_DATA(&g_eTicketRsaDeviceKey, sizeof(SetCalRsa2048DeviceKey), "eTicket RSA device key dump:");
}*/ }*/
mutexUnlock(&g_ncaKeysetMutex);
return ret; return ret;
} }
const u8 *keysGetNcaHeaderKey(void) const u8 *keysGetNcaHeaderKey(void)
{ {
mutexLock(&g_ncaKeysetMutex); const u8 *ret = NULL;
const u8 *ptr = (g_ncaKeysetLoaded ? (const u8*)(g_ncaKeyset.nca_header_key) : NULL);
mutexUnlock(&g_ncaKeysetMutex); SCOPED_LOCK(&g_ncaKeysetMutex)
return ptr; {
if (g_ncaKeysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_header_key);
}
return ret;
} }
bool keysDecryptNcaKeyAreaEntry(u8 kaek_index, u8 key_generation, void *dst, const void *src) bool keysDecryptNcaKeyAreaEntry(u8 kaek_index, u8 key_generation, void *dst, const void *src)
{ {
Result rc = 0; bool ret = false;
bool success = false;
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation); u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
if (kaek_index >= NcaKeyAreaEncryptionKeyIndex_Count) if (kaek_index >= NcaKeyAreaEncryptionKeyIndex_Count)
@ -265,23 +327,20 @@ bool keysDecryptNcaKeyAreaEntry(u8 kaek_index, u8 key_generation, void *dst, con
goto end; goto end;
} }
mutexLock(&g_ncaKeysetMutex); SCOPED_LOCK(&g_ncaKeysetMutex)
if (g_ncaKeysetLoaded)
{ {
rc = splCryptoGenerateAesKey(g_ncaKeyset.nca_kaek_sealed[kaek_index][key_gen_val], src, dst); if (!g_ncaKeysetLoaded) break;
if (!(success = R_SUCCEEDED(rc))) LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X).", rc); Result rc = splCryptoGenerateAesKey(g_ncaKeyset.nca_kaek_sealed[kaek_index][key_gen_val], src, dst);
if (!(ret = R_SUCCEEDED(rc))) LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X).", rc);
} }
mutexUnlock(&g_ncaKeysetMutex);
end: end:
return success; return ret;
} }
const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation) const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation)
{ {
const u8 *ptr = NULL; const u8 *ret = NULL;
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation); u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
if (kaek_index >= NcaKeyAreaEncryptionKeyIndex_Count) if (kaek_index >= NcaKeyAreaEncryptionKeyIndex_Count)
@ -296,14 +355,13 @@ const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation)
goto end; goto end;
} }
mutexLock(&g_ncaKeysetMutex); SCOPED_LOCK(&g_ncaKeysetMutex)
{
if (g_ncaKeysetLoaded) ptr = (const u8*)(g_ncaKeyset.nca_kaek[kaek_index][key_gen_val]); if (g_ncaKeysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_kaek[kaek_index][key_gen_val]);
}
mutexUnlock(&g_ncaKeysetMutex);
end: end:
return ptr; return ret;
} }
bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *out_titlekey) bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *out_titlekey)
@ -314,22 +372,22 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
return false; return false;
} }
size_t out_keydata_size = 0; bool ret = false;
u8 out_keydata[0x100] = {0};
EticketRsaDeviceKey *eticket_rsa_key = NULL;
bool success = false;
mutexLock(&g_ncaKeysetMutex); SCOPED_LOCK(&g_ncaKeysetMutex)
if (g_ncaKeysetLoaded)
{ {
if (!g_ncaKeysetLoaded) break;
size_t out_keydata_size = 0;
u8 out_keydata[0x100] = {0};
/* Get eTicket RSA device key. */ /* Get eTicket RSA device key. */
eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key; EticketRsaDeviceKey *eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key;
/* Perform a RSA-OAEP unwrap operation to get the encrypted titlekey. */ /* Perform a RSA-OAEP unwrap operation to get the encrypted titlekey. */
success = (rsa2048OaepDecryptAndVerify(out_keydata, sizeof(out_keydata), rsa_wrapped_titlekey, eticket_rsa_key->modulus, eticket_rsa_key->exponent, sizeof(eticket_rsa_key->exponent), \ ret = (rsa2048OaepDecryptAndVerify(out_keydata, sizeof(out_keydata), rsa_wrapped_titlekey, eticket_rsa_key->modulus, eticket_rsa_key->exponent, sizeof(eticket_rsa_key->exponent), \
g_nullHash, &out_keydata_size) && out_keydata_size >= AES_128_KEY_SIZE); g_nullHash, &out_keydata_size) && out_keydata_size >= AES_128_KEY_SIZE);
if (success) if (ret)
{ {
/* Copy RSA-OAEP unwrapped titlekey. */ /* Copy RSA-OAEP unwrapped titlekey. */
memcpy(out_titlekey, out_keydata, AES_128_KEY_SIZE); memcpy(out_titlekey, out_keydata, AES_128_KEY_SIZE);
@ -338,14 +396,12 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
} }
} }
mutexUnlock(&g_ncaKeysetMutex); return ret;
return success;
} }
const u8 *keysGetTicketCommonKey(u8 key_generation) const u8 *keysGetTicketCommonKey(u8 key_generation)
{ {
const u8 *ptr = NULL; const u8 *ret = NULL;
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation); u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
if (key_gen_val >= NcaKeyGeneration_Max) if (key_gen_val >= NcaKeyGeneration_Max)
@ -354,14 +410,33 @@ const u8 *keysGetTicketCommonKey(u8 key_generation)
goto end; goto end;
} }
mutexLock(&g_ncaKeysetMutex); SCOPED_LOCK(&g_ncaKeysetMutex)
{
if (g_ncaKeysetLoaded) ptr = (const u8*)(g_ncaKeyset.ticket_common_keys[key_gen_val]); if (g_ncaKeysetLoaded) ret = (const u8*)(g_ncaKeyset.ticket_common_keys[key_gen_val]);
}
mutexUnlock(&g_ncaKeysetMutex);
end: end:
return ptr; return ret;
}
static bool keysIsProductionModulus1xMandatory(void)
{
return !utilsIsDevelopmentUnit();
}
static bool keysIsProductionModulus9xMandatory(void)
{
return (!utilsIsDevelopmentUnit() && hosversionAtLeast(9, 0, 0));
}
static bool keysIsDevelopmentModulus1xMandatory(void)
{
return utilsIsDevelopmentUnit();
}
static bool keysIsDevelopmentModulus9xMandatory(void)
{
return (utilsIsDevelopmentUnit() && hosversionAtLeast(9, 0, 0));
} }
static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info) static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info)
@ -372,7 +447,6 @@ static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info)
return false; return false;
} }
bool found;
u8 tmp_hash[SHA256_HASH_SIZE]; u8 tmp_hash[SHA256_HASH_SIZE];
bool success = false; bool success = false;
@ -380,14 +454,13 @@ static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info)
for(u32 i = 0; i < info->key_count; i++) for(u32 i = 0; i < info->key_count; i++)
{ {
found = false;
KeysMemoryKey *key = &(info->keys[i]); KeysMemoryKey *key = &(info->keys[i]);
bool found = false, mandatory = (key->mandatory_func != NULL ? key->mandatory_func() : true);
if (!key->dst) if (!key->dst)
{ {
LOG_MSG("Invalid destination pointer for key \"%s\" in program %016lX!", key->name, info->location.program_id); LOG_MSG("Invalid destination pointer for key \"%s\" in program %016lX!", key->name, info->location.program_id);
goto end; if (mandatory) goto end;
} }
/* Hash every key length-sized byte chunk in the process memory buffer until a match is found. */ /* Hash every key length-sized byte chunk in the process memory buffer until a match is found. */
@ -409,7 +482,7 @@ static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info)
if (!found) if (!found)
{ {
LOG_MSG("Unable to locate key \"%s\" in process memory from program %016lX!", key->name, info->location.program_id); LOG_MSG("Unable to locate key \"%s\" in process memory from program %016lX!", key->name, info->location.program_id);
goto end; if (mandatory) goto end;
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -45,10 +45,8 @@ bool memRetrieveProgramMemorySegment(MemoryLocation *location)
return false; return false;
} }
mutexLock(&g_memMutex); bool ret = false;
bool ret = memRetrieveProgramMemory(location, true); SCOPED_LOCK(&g_memMutex) ret = memRetrieveProgramMemory(location, true);
mutexUnlock(&g_memMutex);
return ret; return ret;
} }
@ -60,10 +58,8 @@ bool memRetrieveFullProgramMemory(MemoryLocation *location)
return false; return false;
} }
mutexLock(&g_memMutex); bool ret = false;
bool ret = memRetrieveProgramMemory(location, false); SCOPED_LOCK(&g_memMutex) ret = memRetrieveProgramMemory(location, false);
mutexUnlock(&g_memMutex);
return ret; return ret;
} }

View file

@ -52,32 +52,35 @@ NX_INLINE bool ncaIsVersion0KeyAreaEncrypted(NcaContext *ctx);
NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx); NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx);
NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx); NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx);
static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock); static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset);
static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool lock); static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val);
static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch); static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch);
static bool ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_size, u64 patch_offset, void *buf, u64 buf_size, u64 buf_offset); static bool ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_size, u64 patch_offset, void *buf, u64 buf_size, u64 buf_offset);
static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock); static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset);
bool ncaAllocateCryptoBuffer(void) bool ncaAllocateCryptoBuffer(void)
{ {
mutexLock(&g_ncaCryptoBufferMutex); bool ret = false;
if (!g_ncaCryptoBuffer) g_ncaCryptoBuffer = malloc(NCA_CRYPTO_BUFFER_SIZE);
bool ret = (g_ncaCryptoBuffer != NULL); SCOPED_LOCK(&g_ncaCryptoBufferMutex)
mutexUnlock(&g_ncaCryptoBufferMutex); {
if (!g_ncaCryptoBuffer) g_ncaCryptoBuffer = malloc(NCA_CRYPTO_BUFFER_SIZE);
ret = (g_ncaCryptoBuffer != NULL);
}
return ret; return ret;
} }
void ncaFreeCryptoBuffer(void) void ncaFreeCryptoBuffer(void)
{ {
mutexLock(&g_ncaCryptoBufferMutex); SCOPED_LOCK(&g_ncaCryptoBufferMutex)
if (g_ncaCryptoBuffer)
{ {
if (!g_ncaCryptoBuffer) break;
free(g_ncaCryptoBuffer); free(g_ncaCryptoBuffer);
g_ncaCryptoBuffer = NULL; g_ncaCryptoBuffer = NULL;
} }
mutexUnlock(&g_ncaCryptoBufferMutex);
} }
bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik) bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik)
@ -296,22 +299,30 @@ bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset)
bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset) bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset)
{ {
return _ncaReadFsSection(ctx, out, read_size, offset, true); bool ret = false;
SCOPED_LOCK(&g_ncaCryptoBufferMutex) ret = _ncaReadFsSection(ctx, out, read_size, offset);
return ret;
} }
bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val) bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val)
{ {
return _ncaReadAesCtrExStorageFromBktrSection(ctx, out, read_size, offset, ctr_val, true); bool ret = false;
SCOPED_LOCK(&g_ncaCryptoBufferMutex) ret = _ncaReadAesCtrExStorageFromBktrSection(ctx, out, read_size, offset, ctr_val);
return ret;
} }
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset) void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset)
{ {
return _ncaGenerateEncryptedFsSectionBlock(ctx, data, data_size, data_offset, out_block_size, out_block_offset, true); void *ret = NULL;
SCOPED_LOCK(&g_ncaCryptoBufferMutex) ret = _ncaGenerateEncryptedFsSectionBlock(ctx, data, data_size, data_offset, out_block_size, out_block_offset);
return ret;
} }
bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out) bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out)
{ {
return ncaGenerateHashDataPatch(ctx, data, data_size, data_offset, out, false); bool ret = false;
SCOPED_LOCK(&g_ncaCryptoBufferMutex) ret = ncaGenerateHashDataPatch(ctx, data, data_size, data_offset, out, false);
return ret;
} }
void ncaWriteHierarchicalSha256PatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalSha256Patch *patch, void *buf, u64 buf_size, u64 buf_offset) void ncaWriteHierarchicalSha256PatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalSha256Patch *patch, void *buf, u64 buf_size, u64 buf_offset)
@ -333,7 +344,9 @@ void ncaWriteHierarchicalSha256PatchToMemoryBuffer(NcaContext *ctx, NcaHierarchi
bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalIntegrityPatch *out) bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalIntegrityPatch *out)
{ {
return ncaGenerateHashDataPatch(ctx, data, data_size, data_offset, out, true); bool ret = false;
SCOPED_LOCK(&g_ncaCryptoBufferMutex) ret = ncaGenerateHashDataPatch(ctx, data, data_size, data_offset, out, true);
return ret;
} }
void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalIntegrityPatch *patch, void *buf, u64 buf_size, u64 buf_offset) void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalIntegrityPatch *patch, void *buf, u64 buf_size, u64 buf_offset)
@ -748,18 +761,14 @@ NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx)
return false; return false;
} }
static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock) static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset)
{ {
if (lock) mutexLock(&g_ncaCryptoBufferMutex);
bool ret = false;
if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \
ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_AesCtrEx || !out || !read_size || \ ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_AesCtrEx || !out || !read_size || \
(offset + read_size) > ctx->section_size) (offset + read_size) > ctx->section_size)
{ {
LOG_MSG("Invalid NCA FS section header parameters!"); LOG_MSG("Invalid NCA FS section header parameters!");
goto end; return false;
} }
size_t crypt_res = 0; size_t crypt_res = 0;
@ -771,6 +780,8 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
u64 block_start_offset = 0, block_end_offset = 0, block_size = 0; u64 block_start_offset = 0, block_end_offset = 0, block_size = 0;
u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0; u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0;
bool ret = false;
if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
(nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || (content_offset + read_size) > nca_ctx->content_size) (nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || (content_offset + read_size) > nca_ctx->content_size)
{ {
@ -861,25 +872,19 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
/* Copy decrypted data. */ /* Copy decrypted data. */
memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size); memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size);
ret = (block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadFsSection(ctx, (u8*)out + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size, false) : true); ret = (block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadFsSection(ctx, (u8*)out + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size) : true);
end: end:
if (lock) mutexUnlock(&g_ncaCryptoBufferMutex);
return ret; return ret;
} }
static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool lock) static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val)
{ {
if (lock) mutexLock(&g_ncaCryptoBufferMutex);
bool ret = false;
if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \
ctx->section_type != NcaFsSectionType_PatchRomFs || ctx->encryption_type != NcaEncryptionType_AesCtrEx || !out || !read_size || (offset + read_size) > ctx->section_size) ctx->section_type != NcaFsSectionType_PatchRomFs || ctx->encryption_type != NcaEncryptionType_AesCtrEx || !out || !read_size || (offset + read_size) > ctx->section_size)
{ {
LOG_MSG("Invalid NCA FS section header parameters!"); LOG_MSG("Invalid NCA FS section header parameters!");
goto end; return false;
} }
NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx; NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx;
@ -888,6 +893,8 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
u64 block_start_offset = 0, block_end_offset = 0, block_size = 0; u64 block_start_offset = 0, block_end_offset = 0, block_size = 0;
u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0; u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0;
bool ret = false;
if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
(content_offset + read_size) > nca_ctx->content_size) (content_offset + read_size) > nca_ctx->content_size)
{ {
@ -939,19 +946,15 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
/* Copy decrypted data. */ /* Copy decrypted data. */
memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size); memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size);
ret = (block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadAesCtrExStorageFromBktrSection(ctx, (u8*)out + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size, ctr_val, false) : true); ret = (block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadAesCtrExStorageFromBktrSection(ctx, (u8*)out + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size, ctr_val) : true);
end: end:
if (lock) mutexUnlock(&g_ncaCryptoBufferMutex);
return ret; return ret;
} }
/* In this function, the term "layer" is used as a generic way to refer to both HierarchicalSha256 hash regions and HierarchicalIntegrity verification levels. */ /* In this function, the term "layer" is used as a generic way to refer to both HierarchicalSha256 hash regions and HierarchicalIntegrity verification levels. */
static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch) static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch)
{ {
mutexLock(&g_ncaCryptoBufferMutex);
NcaContext *nca_ctx = NULL; NcaContext *nca_ctx = NULL;
NcaHierarchicalSha256Patch *hierarchical_sha256_patch = (!is_integrity_patch ? ((NcaHierarchicalSha256Patch*)out) : NULL); NcaHierarchicalSha256Patch *hierarchical_sha256_patch = (!is_integrity_patch ? ((NcaHierarchicalSha256Patch*)out) : NULL);
NcaHierarchicalIntegrityPatch *hierarchical_integrity_patch = (is_integrity_patch ? ((NcaHierarchicalIntegrityPatch*)out) : NULL); NcaHierarchicalIntegrityPatch *hierarchical_integrity_patch = (is_integrity_patch ? ((NcaHierarchicalIntegrityPatch*)out) : NULL);
@ -1067,7 +1070,7 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data,
} }
/* Read current layer block. */ /* Read current layer block. */
if (!_ncaReadFsSection(ctx, cur_layer_block, cur_layer_read_size, cur_layer_read_start_offset, false)) if (!_ncaReadFsSection(ctx, cur_layer_block, cur_layer_read_size, cur_layer_read_start_offset))
{ {
LOG_MSG("Failed to read 0x%lX bytes long hierarchical layer #%u data block from offset 0x%lX! (current).", cur_layer_read_size, i - 1, cur_layer_read_start_offset); LOG_MSG("Failed to read 0x%lX bytes long hierarchical layer #%u data block from offset 0x%lX! (current).", cur_layer_read_size, i - 1, cur_layer_read_start_offset);
goto end; goto end;
@ -1088,7 +1091,7 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data,
} }
/* Read parent layer block. */ /* Read parent layer block. */
if (!_ncaReadFsSection(ctx, parent_layer_block, parent_layer_read_size, parent_layer_offset + parent_layer_read_start_offset, false)) if (!_ncaReadFsSection(ctx, parent_layer_block, parent_layer_read_size, parent_layer_offset + parent_layer_read_start_offset))
{ {
LOG_MSG("Failed to read 0x%lX bytes long hierarchical layer #%u data block from offset 0x%lX! (parent).", parent_layer_read_size, i - 2, parent_layer_read_start_offset); LOG_MSG("Failed to read 0x%lX bytes long hierarchical layer #%u data block from offset 0x%lX! (parent).", parent_layer_read_size, i - 2, parent_layer_read_start_offset);
goto end; goto end;
@ -1110,7 +1113,7 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data,
/* Reencrypt current layer block. */ /* Reencrypt current layer block. */
cur_layer_patch->data = _ncaGenerateEncryptedFsSectionBlock(ctx, cur_layer_block + cur_layer_read_patch_offset, cur_data_size, cur_layer_offset + cur_data_offset, \ cur_layer_patch->data = _ncaGenerateEncryptedFsSectionBlock(ctx, cur_layer_block + cur_layer_read_patch_offset, cur_data_size, cur_layer_offset + cur_data_offset, \
&(cur_layer_patch->size), &(cur_layer_patch->offset), false); &(cur_layer_patch->size), &(cur_layer_patch->offset));
if (!cur_layer_patch->data) if (!cur_layer_patch->data)
{ {
LOG_MSG("Failed to generate encrypted 0x%lX bytes long hierarchical layer #%u data block!", cur_data_size, i - 1); LOG_MSG("Failed to generate encrypted 0x%lX bytes long hierarchical layer #%u data block!", cur_data_size, i - 1);
@ -1160,8 +1163,6 @@ end:
} }
} }
mutexUnlock(&g_ncaCryptoBufferMutex);
return success; return success;
} }
@ -1188,10 +1189,8 @@ static bool ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64
return ((patch_block_offset + buf_block_size) == patch_size); return ((patch_block_offset + buf_block_size) == patch_size);
} }
static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock) static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset)
{ {
if (lock) mutexLock(&g_ncaCryptoBufferMutex);
u8 *out = NULL; u8 *out = NULL;
bool success = false; bool success = false;
@ -1278,7 +1277,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
} }
/* Read decrypted data using aligned offset and size. */ /* Read decrypted data using aligned offset and size. */
if (!_ncaReadFsSection(ctx, out, block_size, block_start_offset, false)) if (!_ncaReadFsSection(ctx, out, block_size, block_start_offset))
{ {
LOG_MSG("Failed to read decrypted NCA \"%s\" FS section #%u data block!", nca_ctx->content_id_str, ctx->section_num); LOG_MSG("Failed to read decrypted NCA \"%s\" FS section #%u data block!", nca_ctx->content_id_str, ctx->section_num);
goto end; goto end;
@ -1318,7 +1317,5 @@ end:
out = NULL; out = NULL;
} }
if (lock) mutexUnlock(&g_ncaCryptoBufferMutex);
return out; return out;
} }

View file

@ -38,29 +38,29 @@ static char *g_logBuffer = NULL;
static size_t g_logBufferLength = 0; static size_t g_logBufferLength = 0;
static const char *g_utf8Bom = "\xEF\xBB\xBF"; static const char *g_utf8Bom = "\xEF\xBB\xBF";
static const char *g_logStrFormat = "[%d-%02d-%02d %02d:%02d:%02d.%lu] %s -> "; static const char *g_logStrFormat = "[%d-%02d-%02d %02d:%02d:%02d.%09lu] %s -> ";
static const char *g_logLineBreak = "\r\n"; static const char *g_logLineBreak = "\r\n";
/* Function prototypes. */ /* Function prototypes. */
static void _logWriteStringToLogFile(const char *src, bool lock); static void _logWriteStringToLogFile(const char *src);
static void _logWriteFormattedStringToLogFile(bool save, const char *func_name, const char *fmt, va_list args, bool lock); static void _logWriteFormattedStringToLogFile(bool save, const char *func_name, const char *fmt, va_list args);
static void _logFlushLogFile(bool lock); static void _logFlushLogFile(void);
static bool logAllocateLogBuffer(void); static bool logAllocateLogBuffer(void);
static bool logOpenLogFile(void); static bool logOpenLogFile(void);
void logWriteStringToLogFile(const char *src) void logWriteStringToLogFile(const char *src)
{ {
_logWriteStringToLogFile(src, true); SCOPED_LOCK(&g_logMutex) _logWriteStringToLogFile(src);
} }
__attribute__((format(printf, 2, 3))) void logWriteFormattedStringToLogFile(const char *func_name, const char *fmt, ...) __attribute__((format(printf, 2, 3))) void logWriteFormattedStringToLogFile(const char *func_name, const char *fmt, ...)
{ {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
_logWriteFormattedStringToLogFile(true, func_name, fmt, args, true); SCOPED_LOCK(&g_logMutex) _logWriteFormattedStringToLogFile(true, func_name, fmt, args);
va_end(args); va_end(args);
} }
@ -135,8 +135,6 @@ __attribute__((format(printf, 4, 5))) void logWriteBinaryDataToLogFile(const voi
size_t data_str_size = ((data_size * 2) + 3); size_t data_str_size = ((data_size * 2) + 3);
char *data_str = NULL; char *data_str = NULL;
mutexLock(&g_logMutex);
/* Allocate memory for the hex string representation of the provided binary data. */ /* Allocate memory for the hex string representation of the provided binary data. */
data_str = calloc(data_str_size, sizeof(char)); data_str = calloc(data_str_size, sizeof(char));
if (!data_str) goto end; if (!data_str) goto end;
@ -145,91 +143,93 @@ __attribute__((format(printf, 4, 5))) void logWriteBinaryDataToLogFile(const voi
utilsGenerateHexStringFromData(data_str, data_str_size, data, data_size, true); utilsGenerateHexStringFromData(data_str, data_str_size, data, data_size, true);
strcat(data_str, g_logLineBreak); strcat(data_str, g_logLineBreak);
/* Write formatted string. */ SCOPED_LOCK(&g_logMutex)
va_start(args, fmt); {
_logWriteFormattedStringToLogFile(false, func_name, fmt, args, false); /* Write formatted string. */
va_end(args); va_start(args, fmt);
_logWriteFormattedStringToLogFile(false, func_name, fmt, args);
/* Write hex string representation. */ va_end(args);
_logWriteStringToLogFile(data_str, false);
/* Write hex string representation. */
_logWriteStringToLogFile(data_str);
}
end: end:
if (data_str) free(data_str); if (data_str) free(data_str);
mutexUnlock(&g_logMutex);
} }
void logFlushLogFile(void) void logFlushLogFile(void)
{ {
_logFlushLogFile(true); SCOPED_LOCK(&g_logMutex) _logFlushLogFile();
} }
void logCloseLogFile(void) void logCloseLogFile(void)
{ {
mutexLock(&g_logMutex); SCOPED_LOCK(&g_logMutex)
/* Flush log buffer. */
_logFlushLogFile(false);
/* Close logfile. */
if (serviceIsActive(&(g_logFile.s)))
{ {
fsFileClose(&g_logFile); /* Flush log buffer. */
memset(&g_logFile, 0, sizeof(FsFile)); _logFlushLogFile();
/* Commit SD card filesystem changes. */ /* Close logfile. */
utilsCommitSdCardFileSystemChanges(); if (serviceIsActive(&(g_logFile.s)))
{
fsFileClose(&g_logFile);
memset(&g_logFile, 0, sizeof(FsFile));
/* Commit SD card filesystem changes. */
utilsCommitSdCardFileSystemChanges();
}
/* Free log buffer. */
if (g_logBuffer)
{
free(g_logBuffer);
g_logBuffer = NULL;
}
/* Reset logfile offset. */
g_logFileOffset = 0;
} }
/* Free log buffer. */
if (g_logBuffer)
{
free(g_logBuffer);
g_logBuffer = NULL;
}
g_logFileOffset = 0;
mutexUnlock(&g_logMutex);
} }
void logGetLastMessage(char *dst, size_t dst_size) void logGetLastMessage(char *dst, size_t dst_size)
{ {
mutexLock(&g_logMutex); SCOPED_LOCK(&g_logMutex)
if (dst && dst_size > 1 && *g_lastLogMsg) snprintf(dst, dst_size, "%s", g_lastLogMsg); {
mutexUnlock(&g_logMutex); if (dst && dst_size > 1 && *g_lastLogMsg) snprintf(dst, dst_size, "%s", g_lastLogMsg);
}
} }
void logControlMutex(bool lock) void logControlMutex(bool lock)
{ {
if (lock) bool locked = mutexIsLockedByCurrentThread(&g_logMutex);
if (!locked && lock)
{ {
mutexLock(&g_logMutex); mutexLock(&g_logMutex);
} else { } else
if (locked && !lock)
{
mutexUnlock(&g_logMutex); mutexUnlock(&g_logMutex);
} }
} }
static void _logWriteStringToLogFile(const char *src, bool lock) static void _logWriteStringToLogFile(const char *src)
{ {
if (!src || !*src) return; /* Make sure we have allocated memory for the log buffer and opened the logfile. */
if (!src || !*src || !logAllocateLogBuffer() || !logOpenLogFile()) return;
if (lock) mutexLock(&g_logMutex);
Result rc = 0; Result rc = 0;
size_t src_len = strlen(src), tmp_len = 0; size_t src_len = strlen(src), tmp_len = 0;
/* Make sure we have allocated memory for the log buffer and opened the logfile. */
if (!logAllocateLogBuffer() || !logOpenLogFile()) goto end;
/* Check if the formatted string length is lower than the log buffer size. */ /* Check if the formatted string length is lower than the log buffer size. */
if (src_len < LOG_BUF_SIZE) if (src_len < LOG_BUF_SIZE)
{ {
/* Flush log buffer contents (if needed). */ /* Flush log buffer contents (if needed). */
if ((g_logBufferLength + src_len) >= LOG_BUF_SIZE) if ((g_logBufferLength + src_len) >= LOG_BUF_SIZE)
{ {
_logFlushLogFile(false); _logFlushLogFile();
if (g_logBufferLength) goto end; if (g_logBufferLength) return;
} }
/* Copy string into the log buffer. */ /* Copy string into the log buffer. */
@ -237,14 +237,14 @@ static void _logWriteStringToLogFile(const char *src, bool lock)
g_logBufferLength += src_len; g_logBufferLength += src_len;
} else { } else {
/* Flush log buffer. */ /* Flush log buffer. */
_logFlushLogFile(false); _logFlushLogFile();
if (g_logBufferLength) goto end; if (g_logBufferLength) return;
/* Write string data until it no longer exceeds the log buffer size. */ /* Write string data until it no longer exceeds the log buffer size. */
while(src_len >= LOG_BUF_SIZE) while(src_len >= LOG_BUF_SIZE)
{ {
rc = fsFileWrite(&g_logFile, g_logFileOffset, src + tmp_len, LOG_BUF_SIZE, FsWriteOption_Flush); rc = fsFileWrite(&g_logFile, g_logFileOffset, src + tmp_len, LOG_BUF_SIZE, FsWriteOption_Flush);
if (R_FAILED(rc)) goto end; if (R_FAILED(rc)) return;
g_logFileOffset += LOG_BUF_SIZE; g_logFileOffset += LOG_BUF_SIZE;
tmp_len += LOG_BUF_SIZE; tmp_len += LOG_BUF_SIZE;
@ -261,18 +261,14 @@ static void _logWriteStringToLogFile(const char *src, bool lock)
#if LOG_FORCE_FLUSH == 1 #if LOG_FORCE_FLUSH == 1
/* Flush log buffer. */ /* Flush log buffer. */
_logFlushLogFile(false); _logFlushLogFile();
#endif #endif
end:
if (lock) mutexUnlock(&g_logMutex);
} }
static void _logWriteFormattedStringToLogFile(bool save, const char *func_name, const char *fmt, va_list args, bool lock) static void _logWriteFormattedStringToLogFile(bool save, const char *func_name, const char *fmt, va_list args)
{ {
if (!func_name || !*func_name || !fmt || !*fmt) return; /* Make sure we have allocated memory for the log buffer and opened the logfile. */
if (!func_name || !*func_name || !fmt || !*fmt || !logAllocateLogBuffer() || !logOpenLogFile()) return;
if (lock) mutexLock(&g_logMutex);
Result rc = 0; Result rc = 0;
@ -293,10 +289,10 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
/* Get formatted string length. */ /* Get formatted string length. */
str1_len = snprintf(NULL, 0, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); str1_len = snprintf(NULL, 0, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name);
if (str1_len <= 0) goto end; if (str1_len <= 0) return;
str2_len = vsnprintf(NULL, 0, fmt, args); str2_len = vsnprintf(NULL, 0, fmt, args);
if (str2_len <= 0) goto end; if (str2_len <= 0) return;
log_str_len = (size_t)(str1_len + str2_len + 2); log_str_len = (size_t)(str1_len + str2_len + 2);
@ -313,17 +309,14 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
tmp_len = 0; tmp_len = 0;
} }
/* Make sure we have allocated memory for the log buffer and opened the logfile. */
if (!logAllocateLogBuffer() || !logOpenLogFile()) goto end;
/* Check if the formatted string length is less than the log buffer size. */ /* Check if the formatted string length is less than the log buffer size. */
if (log_str_len < LOG_BUF_SIZE) if (log_str_len < LOG_BUF_SIZE)
{ {
/* Flush log buffer contents (if needed). */ /* Flush log buffer contents (if needed). */
if ((g_logBufferLength + log_str_len) >= LOG_BUF_SIZE) if ((g_logBufferLength + log_str_len) >= LOG_BUF_SIZE)
{ {
_logFlushLogFile(false); _logFlushLogFile();
if (g_logBufferLength) goto end; if (g_logBufferLength) return;
} }
/* Nice and easy string formatting using the log buffer. */ /* Nice and easy string formatting using the log buffer. */
@ -333,12 +326,12 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
g_logBufferLength += log_str_len; g_logBufferLength += log_str_len;
} else { } else {
/* Flush log buffer. */ /* Flush log buffer. */
_logFlushLogFile(false); _logFlushLogFile();
if (g_logBufferLength) goto end; if (g_logBufferLength) return;
/* Allocate memory for a temporary buffer. This will hold the formatted string. */ /* Allocate memory for a temporary buffer. This will hold the formatted string. */
tmp_str = calloc(log_str_len + 1, sizeof(char)); tmp_str = calloc(log_str_len + 1, sizeof(char));
if (!tmp_str) goto end; if (!tmp_str) return;
/* Generate formatted string. */ /* Generate formatted string. */
sprintf(tmp_str, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); sprintf(tmp_str, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name);
@ -366,20 +359,16 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
#if LOG_FORCE_FLUSH == 1 #if LOG_FORCE_FLUSH == 1
/* Flush log buffer. */ /* Flush log buffer. */
_logFlushLogFile(false); _logFlushLogFile();
#endif #endif
end: end:
if (tmp_str) free(tmp_str); if (tmp_str) free(tmp_str);
if (lock) mutexUnlock(&g_logMutex);
} }
static void _logFlushLogFile(bool lock) static void _logFlushLogFile(void)
{ {
if (lock) mutexLock(&g_logMutex); if (!serviceIsActive(&(g_logFile.s)) || !g_logBuffer || !g_logBufferLength) return;
if (!serviceIsActive(&(g_logFile.s)) || !g_logBuffer || !g_logBufferLength) goto end;
/* Write log buffer contents and flush the written data right away. */ /* Write log buffer contents and flush the written data right away. */
Result rc = fsFileWrite(&g_logFile, g_logFileOffset, g_logBuffer, g_logBufferLength, FsWriteOption_Flush); Result rc = fsFileWrite(&g_logFile, g_logFileOffset, g_logBuffer, g_logBufferLength, FsWriteOption_Flush);
@ -390,9 +379,6 @@ static void _logFlushLogFile(bool lock)
*g_logBuffer = '\0'; *g_logBuffer = '\0';
g_logBufferLength = 0; g_logBufferLength = 0;
} }
end:
if (lock) mutexUnlock(&g_logMutex);
} }
static bool logAllocateLogBuffer(void) static bool logAllocateLogBuffer(void)
@ -407,6 +393,8 @@ static bool logOpenLogFile(void)
if (serviceIsActive(&(g_logFile.s))) return true; if (serviceIsActive(&(g_logFile.s))) return true;
Result rc = 0; Result rc = 0;
bool use_root = true;
const char *launch_path = utilsGetLaunchPath();
char path[FS_MAX_PATH] = {0}, *ptr1 = NULL, *ptr2 = NULL; char path[FS_MAX_PATH] = {0}, *ptr1 = NULL, *ptr2 = NULL;
/* Get SD card FsFileSystem object. */ /* Get SD card FsFileSystem object. */
@ -414,27 +402,22 @@ static bool logOpenLogFile(void)
if (!sdmc_fs) return false; if (!sdmc_fs) return false;
/* Generate logfile path. */ /* Generate logfile path. */
if (g_appLaunchPath) if (launch_path)
{ {
ptr1 = strchr(g_appLaunchPath, '/'); ptr1 = strchr(launch_path, '/');
ptr2 = strrchr(g_appLaunchPath, '/'); ptr2 = strrchr(launch_path, '/');
if (ptr1 != ptr2) if (ptr1 && ptr2 && ptr1 != ptr2)
{ {
/* Create logfile in the current working directory. */ /* Create logfile in the current working directory. */
snprintf(path, sizeof(path), "%.*s", (int)((ptr2 - ptr1) + 1), ptr1); snprintf(path, sizeof(path), "%.*s" LOG_FILE_NAME, (int)((ptr2 - ptr1) + 1), ptr1);
use_root = false;
size_t path_len = strlen(path);
snprintf(path + path_len, sizeof(path) - path_len, LOG_FILE_NAME);
} else {
/* Create logfile in the SD card root directory. */
sprintf(path, "/" LOG_FILE_NAME);
} }
} else {
/* Create logfile in the SD card root directory. */
sprintf(path, "/" LOG_FILE_NAME);
} }
/* Create logfile in the SD card root directory. */
if (use_root) sprintf(path, "/" LOG_FILE_NAME);
/* Create file. This will fail if the logfile exists, so we don't check its return value. */ /* Create file. This will fail if the logfile exists, so we don't check its return value. */
fsFsCreateFile(sdmc_fs, path, 0, 0); fsFsCreateFile(sdmc_fs, path, 0, 0);

View file

@ -33,22 +33,26 @@
/* Global variables. */ /* Global variables. */
static bool g_resourcesInit = false, g_isDevUnit = false; static bool g_resourcesInit = false;
static Mutex g_resourcesMutex = 0; static Mutex g_resourcesMutex = 0;
static FsFileSystem *g_sdCardFileSystem = NULL; static FsFileSystem *g_sdCardFileSystem = NULL;
static FsStorage g_emmcBisSystemPartitionStorage = {0}; static const char *g_appLaunchPath = NULL;
static FATFS *g_emmcBisSystemPartitionFatFsObj = NULL;
static AppletType g_programAppletType = 0;
static bool g_homeButtonBlocked = false;
static Mutex g_homeButtonMutex = 0;
static u8 g_customFirmwareType = UtilsCustomFirmwareType_Unknown; static u8 g_customFirmwareType = UtilsCustomFirmwareType_Unknown;
static bool g_isDevUnit = false;
static AppletType g_programAppletType = AppletType_None;
static FsStorage g_emmcBisSystemPartitionStorage = {0};
static FATFS *g_emmcBisSystemPartitionFatFsObj = NULL;
static AppletHookCookie g_systemOverclockCookie = {0}; static AppletHookCookie g_systemOverclockCookie = {0};
static bool g_homeButtonBlocked = false;
static int g_nxLinkSocketFd = -1; static int g_nxLinkSocketFd = -1;
static const char *g_sizeSuffixes[] = { "B", "KiB", "MiB", "GiB" }; static const char *g_sizeSuffixes[] = { "B", "KiB", "MiB", "GiB" };
@ -59,10 +63,14 @@ static const size_t g_illegalFileSystemCharsLength = (MAX_ELEMENTS(g_illegalFile
/* Function prototypes. */ /* Function prototypes. */
static void _utilsGetLaunchPath(int program_argc, const char **program_argv);
static void _utilsGetCustomFirmwareType(void); static void _utilsGetCustomFirmwareType(void);
static bool _utilsIsDevelopmentUnit(void); static bool _utilsIsDevelopmentUnit(void);
static bool _utilsAppletModeCheck(void);
static bool utilsMountEmmcBisSystemPartitionStorage(void); static bool utilsMountEmmcBisSystemPartitionStorage(void);
static void utilsUnmountEmmcBisSystemPartitionStorage(void); static void utilsUnmountEmmcBisSystemPartitionStorage(void);
@ -70,129 +78,99 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param);
static void utilsPrintConsoleError(void); static void utilsPrintConsoleError(void);
bool utilsInitializeResources(void) bool utilsInitializeResources(const int program_argc, const char **program_argv)
{ {
mutexLock(&g_resourcesMutex);
Result rc = 0; Result rc = 0;
bool ret = false;
bool ret = g_resourcesInit; SCOPED_LOCK(&g_resourcesMutex)
if (ret) goto end;
/* Retrieve pointer to the application launch path. */
if (g_argc && g_argv)
{ {
for(int i = 0; i < g_argc; i++) ret = g_resourcesInit;
if (ret) break;
/* Retrieve pointer to the application launch path. */
_utilsGetLaunchPath(program_argc, program_argv);
/* Retrieve pointer to the SD card FsFileSystem element. */
if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem("sdmc:")))
{ {
if (g_argv[i] && !strncmp(g_argv[i], "sdmc:/", 6)) LOG_MSG("Failed to retrieve FsFileSystem object for the SD card!");
{ break;
g_appLaunchPath = (const char*)g_argv[i];
break;
}
} }
/* Create logfile. */
logWriteStringToLogFile("________________________________________________________________\r\n");
LOG_MSG(APP_TITLE " v%u.%u.%u starting. Built on " __DATE__ " - " __TIME__ ".", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
if (g_appLaunchPath) LOG_MSG("Launch path: \"%s\".", g_appLaunchPath);
/* Log Horizon OS version. */
u32 hos_version = hosversionGet();
LOG_MSG("Horizon OS version: %u.%u.%u.", HOSVER_MAJOR(hos_version), HOSVER_MINOR(hos_version), HOSVER_MICRO(hos_version));
/* Initialize needed services. */
if (!servicesInitialize()) break;
/* Retrieve custom firmware type. */
_utilsGetCustomFirmwareType();
if (g_customFirmwareType != UtilsCustomFirmwareType_Unknown) LOG_MSG("Detected %s CFW.", (g_customFirmwareType == UtilsCustomFirmwareType_Atmosphere ? "Atmosphère" : \
(g_customFirmwareType == UtilsCustomFirmwareType_SXOS ? "SX OS" : "ReiNX")));
/* Check if we're not running under a development unit. */
if (!_utilsIsDevelopmentUnit()) break;
LOG_MSG("Running under %s unit.", g_isDevUnit ? "development" : "retail");
/* Get applet type. */
g_programAppletType = appletGetAppletType();
LOG_MSG("Running under %s mode.", _utilsAppletModeCheck() ? "applet" : "title override");
/* Initialize USB interface. */
if (!usbInitialize()) break;
/* Initialize USB Mass Storage interface. */
if (!umsInitialize()) break;
/* Load NCA keyset. */
if (!keysLoadNcaKeyset()) break;
/* Allocate NCA crypto buffer. */
if (!ncaAllocateCryptoBuffer())
{
LOG_MSG("Unable to allocate memory for NCA crypto buffer!");
break;
}
/* Initialize gamecard interface. */
if (!gamecardInitialize()) break;
/* Initialize title interface. */
if (!titleInitialize()) break;
/* Initialize BFTTF interface. */
if (!bfttfInitialize()) break;
/* Mount eMMC BIS System partition. */
if (!utilsMountEmmcBisSystemPartitionStorage()) break;
/* Disable screen dimming and auto sleep. */
appletSetMediaPlaybackState(true);
/* Overclock system. */
utilsOverclockSystem(true);
/* Setup an applet hook to change the hardware clocks after a system mode change (docked <-> undocked). */
appletHook(&g_systemOverclockCookie, utilsOverclockSystemAppletHook, NULL);
/* Mount application RomFS. */
romfsInit();
/* Redirect stdout and stderr over network to nxlink. */
rc = socketInitializeDefault();
if (R_SUCCEEDED(rc)) g_nxLinkSocketFd = nxlinkConnectToHost(true, true);
/* Update flags. */
ret = g_resourcesInit = true;
} }
/* Retrieve pointer to the SD card FsFileSystem element. */
if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem("sdmc:"))) goto end;
/* Create logfile. */
logWriteStringToLogFile("________________________________________________________________\r\n");
LOG_MSG(APP_TITLE " v%u.%u.%u starting. Built on " __DATE__ " - " __TIME__ ".", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
if (g_appLaunchPath) LOG_MSG("Launch path: \"%s\".", g_appLaunchPath);
/* Log Horizon OS version. */
u32 hos_version = hosversionGet();
LOG_MSG("Horizon OS version: %u.%u.%u.", HOSVER_MAJOR(hos_version), HOSVER_MINOR(hos_version), HOSVER_MICRO(hos_version));
/* Initialize needed services. */
if (!servicesInitialize())
{
LOG_MSG("Failed to initialize needed services!");
goto end;
}
/* Retrieve custom firmware type. */
_utilsGetCustomFirmwareType();
LOG_MSG("Detected %s CFW.", (g_customFirmwareType == UtilsCustomFirmwareType_Atmosphere ? "Atmosphère" : (g_customFirmwareType == UtilsCustomFirmwareType_SXOS ? "SX OS" : "ReiNX")));
/* Check if we're not running under a development unit. */
if (!_utilsIsDevelopmentUnit()) goto end;
LOG_MSG("Running under %s unit.", g_isDevUnit ? "development" : "retail");
/* Get applet type. */
g_programAppletType = appletGetAppletType();
LOG_MSG("Running under %s mode.", utilsAppletModeCheck() ? "applet" : "title override");
/* Initialize USB interface. */
if (!usbInitialize())
{
LOG_MSG("Failed to initialize USB interface!");
goto end;
}
/* Initialize USB Mass Storage interface. */
if (!umsInitialize()) goto end;
/* Load NCA keyset. */
if (!keysLoadNcaKeyset())
{
LOG_MSG("Failed to load NCA keyset!");
goto end;
}
/* Allocate NCA crypto buffer. */
if (!ncaAllocateCryptoBuffer())
{
LOG_MSG("Unable to allocate memory for NCA crypto buffer!");
goto end;
}
/* Initialize gamecard interface. */
if (!gamecardInitialize())
{
LOG_MSG("Failed to initialize gamecard interface!");
goto end;
}
/* Initialize title interface. */
if (!titleInitialize())
{
LOG_MSG("Failed to initialize the title interface!");
goto end;
}
/* Initialize BFTTF interface. */
if (!bfttfInitialize())
{
LOG_MSG("Failed to initialize BFTTF interface!");
goto end;
}
/* Mount eMMC BIS System partition. */
if (!utilsMountEmmcBisSystemPartitionStorage()) goto end;
/* Disable screen dimming and auto sleep. */
appletSetMediaPlaybackState(true);
/* Overclock system. */
utilsOverclockSystem(true);
/* Setup an applet hook to change the hardware clocks after a system mode change (docked <-> undocked). */
appletHook(&g_systemOverclockCookie, utilsOverclockSystemAppletHook, NULL);
/* Mount application RomFS. */
romfsInit();
/* Redirect stdout and stderr over network to nxlink. */
rc = socketInitializeDefault();
if (R_SUCCEEDED(rc)) g_nxLinkSocketFd = nxlinkConnectToHost(true, true);
/* Update flags. */
ret = g_resourcesInit = true;
end:
mutexUnlock(&g_resourcesMutex);
if (!ret) utilsPrintConsoleError(); if (!ret) utilsPrintConsoleError();
return ret; return ret;
@ -200,62 +178,121 @@ end:
void utilsCloseResources(void) void utilsCloseResources(void)
{ {
mutexLock(&g_resourcesMutex); SCOPED_LOCK(&g_resourcesMutex)
/* Close nxlink socket. */
if (g_nxLinkSocketFd >= 0)
{ {
close(g_nxLinkSocketFd); /* Close nxlink socket. */
g_nxLinkSocketFd = -1; if (g_nxLinkSocketFd >= 0)
{
close(g_nxLinkSocketFd);
g_nxLinkSocketFd = -1;
}
socketExit();
/* Unmount application RomFS. */
romfsExit();
/* Unset our overclock applet hook. */
appletUnhook(&g_systemOverclockCookie);
/* Restore hardware clocks. */
utilsOverclockSystem(false);
/* Enable screen dimming and auto sleep. */
appletSetMediaPlaybackState(false);
/* Unblock HOME button presses. */
utilsChangeHomeButtonBlockStatus(false);
/* Unmount eMMC BIS System partition. */
utilsUnmountEmmcBisSystemPartitionStorage();
/* Deinitialize BFTTF interface. */
bfttfExit();
/* Deinitialize title interface. */
titleExit();
/* Deinitialize gamecard interface. */
gamecardExit();
/* Free NCA crypto buffer. */
ncaFreeCryptoBuffer();
/* Close USB Mass Storage interface. */
umsExit();
/* Close USB interface. */
usbExit();
/* Close initialized services. */
servicesClose();
/* Close logfile. */
logCloseLogFile();
g_resourcesInit = false;
}
}
const char *utilsGetLaunchPath(void)
{
return g_appLaunchPath;
}
FsFileSystem *utilsGetSdCardFileSystemObject(void)
{
return g_sdCardFileSystem;
}
bool utilsCommitSdCardFileSystemChanges(void)
{
return (g_sdCardFileSystem ? R_SUCCEEDED(fsFsCommit(g_sdCardFileSystem)) : false);
}
u8 utilsGetCustomFirmwareType(void)
{
return g_customFirmwareType;
}
bool utilsIsDevelopmentUnit(void)
{
return g_isDevUnit;
}
bool utilsAppletModeCheck(void)
{
return _utilsAppletModeCheck();
}
FsStorage *utilsGetEmmcBisSystemPartitionStorage(void)
{
return &g_emmcBisSystemPartitionStorage;
}
void utilsOverclockSystem(bool overclock)
{
u32 cpu_rate = ((overclock ? CPU_CLKRT_OVERCLOCKED : CPU_CLKRT_NORMAL) * 1000000);
u32 mem_rate = ((overclock ? MEM_CLKRT_OVERCLOCKED : MEM_CLKRT_NORMAL) * 1000000);
servicesChangeHardwareClockRates(cpu_rate, mem_rate);
}
void utilsChangeHomeButtonBlockStatus(bool block)
{
SCOPED_LOCK(&g_resourcesMutex)
{
/* Only change HOME button blocking status if we're running as a regular application or a system application, and if its current blocking status is different than the requested one. */
if (_utilsAppletModeCheck() || block == g_homeButtonBlocked) break;
if (block)
{
appletBeginBlockingHomeButtonShortAndLongPressed(0);
} else {
appletEndBlockingHomeButtonShortAndLongPressed();
}
g_homeButtonBlocked = block;
} }
socketExit();
/* Unmount application RomFS. */
romfsExit();
/* Unset our overclock applet hook. */
appletUnhook(&g_systemOverclockCookie);
/* Restore hardware clocks. */
utilsOverclockSystem(false);
/* Enable screen dimming and auto sleep. */
appletSetMediaPlaybackState(false);
/* Unblock HOME button presses. */
utilsChangeHomeButtonBlockStatus(false);
/* Unmount eMMC BIS System partition. */
utilsUnmountEmmcBisSystemPartitionStorage();
/* Deinitialize BFTTF interface. */
bfttfExit();
/* Deinitialize title interface. */
titleExit();
/* Deinitialize gamecard interface. */
gamecardExit();
/* Free NCA crypto buffer. */
ncaFreeCryptoBuffer();
/* Close USB Mass Storage interface. */
umsExit();
/* Close USB interface. */
usbExit();
/* Close initialized services. */
servicesClose();
/* Close logfile. */
logCloseLogFile();
g_resourcesInit = false;
mutexUnlock(&g_resourcesMutex);
} }
bool utilsCreateThread(Thread *out_thread, ThreadFunc func, void *arg, int cpu_id) bool utilsCreateThread(Thread *out_thread, ThreadFunc func, void *arg, int cpu_id)
@ -336,14 +373,6 @@ void utilsJoinThread(Thread *thread)
memset(thread, 0, sizeof(Thread)); memset(thread, 0, sizeof(Thread));
} }
bool utilsIsDevelopmentUnit(void)
{
mutexLock(&g_resourcesMutex);
bool ret = (g_resourcesInit && g_isDevUnit);
mutexUnlock(&g_resourcesMutex);
return ret;
}
__attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...) __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...)
{ {
if (!dst || !dst_size || (!*dst && *dst_size) || (*dst && !*dst_size) || !fmt || !*fmt) if (!dst || !dst_size || (!*dst && *dst_size) || (*dst && !*dst_size) || !fmt || !*fmt)
@ -510,18 +539,6 @@ bool utilsGetFileSystemStatsByPath(const char *path, u64 *out_total, u64 *out_fr
return true; return true;
} }
FsFileSystem *utilsGetSdCardFileSystemObject(void)
{
return g_sdCardFileSystem;
}
bool utilsCommitSdCardFileSystemChanges(void)
{
if (!g_sdCardFileSystem) return false;
Result rc = fsFsCommit(g_sdCardFileSystem);
return R_SUCCEEDED(rc);
}
bool utilsCheckIfFileExists(const char *path) bool utilsCheckIfFileExists(const char *path)
{ {
if (!path || !*path) return false; if (!path || !*path) return false;
@ -610,46 +627,18 @@ char *utilsGeneratePath(const char *prefix, const char *filename, const char *ex
return path; return path;
} }
bool utilsAppletModeCheck(void) static void _utilsGetLaunchPath(int program_argc, const char **program_argv)
{ {
return (g_programAppletType != AppletType_Application && g_programAppletType != AppletType_SystemApplication); if (program_argc <= 0 || !program_argv) return;
}
void utilsChangeHomeButtonBlockStatus(bool block)
{
mutexLock(&g_homeButtonMutex);
/* Only change HOME button blocking status if we're running as a regular application or a system application, and if it's current blocking status is different than the requested one. */ for(int i = 0; i < program_argc; i++)
if (!utilsAppletModeCheck() && block != g_homeButtonBlocked)
{ {
if (block) if (program_argv[i] && !strncmp(program_argv[i], "sdmc:/", 6))
{ {
appletBeginBlockingHomeButtonShortAndLongPressed(0); g_appLaunchPath = program_argv[i];
} else { break;
appletEndBlockingHomeButtonShortAndLongPressed();
} }
g_homeButtonBlocked = block;
} }
mutexUnlock(&g_homeButtonMutex);
}
u8 utilsGetCustomFirmwareType(void)
{
return g_customFirmwareType;
}
FsStorage *utilsGetEmmcBisSystemPartitionStorage(void)
{
return &g_emmcBisSystemPartitionStorage;
}
void utilsOverclockSystem(bool overclock)
{
u32 cpu_rate = ((overclock ? CPU_CLKRT_OVERCLOCKED : CPU_CLKRT_NORMAL) * 1000000);
u32 mem_rate = ((overclock ? MEM_CLKRT_OVERCLOCKED : MEM_CLKRT_NORMAL) * 1000000);
servicesChangeHardwareClockRates(cpu_rate, mem_rate);
} }
static void _utilsGetCustomFirmwareType(void) static void _utilsGetCustomFirmwareType(void)
@ -676,6 +665,11 @@ static bool _utilsIsDevelopmentUnit(void)
return R_SUCCEEDED(rc); return R_SUCCEEDED(rc);
} }
static bool _utilsAppletModeCheck(void)
{
return (g_programAppletType > AppletType_Application && g_programAppletType < AppletType_SystemApplication);
}
static bool utilsMountEmmcBisSystemPartitionStorage(void) static bool utilsMountEmmcBisSystemPartitionStorage(void)
{ {
Result rc = 0; Result rc = 0;

View file

@ -39,7 +39,7 @@ typedef struct {
/* Function prototypes. */ /* Function prototypes. */
static bool _servicesCheckInitializedServiceByName(const char *name, bool lock); static bool _servicesCheckInitializedServiceByName(const char *name);
static Result servicesAtmosphereHasService(bool *out, SmServiceName name); static Result servicesAtmosphereHasService(bool *out, SmServiceName name);
static Result servicesGetExosphereApiVersion(u32 *out); static Result servicesGetExosphereApiVersion(u32 *out);
@ -82,134 +82,123 @@ static const u32 g_atmosphereTipcVersion = MAKEHOSVERSION(0, 19, 0);
bool servicesInitialize(void) bool servicesInitialize(void)
{ {
mutexLock(&g_servicesMutex);
Result rc = 0;
bool ret = true; bool ret = true;
for(u32 i = 0; i < g_serviceInfoCount; i++) SCOPED_LOCK(&g_servicesMutex)
{ {
ServiceInfo *service_info = &(g_serviceInfo[i]); for(u32 i = 0; i < g_serviceInfoCount; i++)
/* Check if this service has been already initialized. */
if (service_info->initialized) continue;
/* Check if this service depends on a condition function. */
if (service_info->cond_func != NULL)
{ {
/* Run the condition function - it will update the current service member. */ ServiceInfo *service_info = &(g_serviceInfo[i]);
/* Skip this service if the required conditions aren't met. */
if (!service_info->cond_func(service_info)) continue; /* Check if this service has been already initialized. */
if (service_info->initialized) continue;
/* Check if this service depends on a condition function. */
if (service_info->cond_func != NULL)
{
/* Run the condition function - it will update the current service member. */
/* Skip this service if the required conditions aren't met. */
if (!service_info->cond_func(service_info)) continue;
}
/* Check if this service actually has a valid initialization function. */
if (service_info->init_func == NULL) continue;
/* Initialize service. */
Result rc = service_info->init_func();
if (R_FAILED(rc))
{
LOG_MSG("Failed to initialize \"%s\" service! (0x%08X).", service_info->name, rc);
ret = false;
break;
}
/* Update flag. */
service_info->initialized = true;
} }
/* Check if this service actually has a valid initialization function. */
if (service_info->init_func == NULL) continue;
/* Initialize service. */
rc = service_info->init_func();
if (R_FAILED(rc))
{
LOG_MSG("Failed to initialize \"%s\" service! (0x%08X).", service_info->name, rc);
ret = false;
break;
}
/* Update flag. */
service_info->initialized = true;
} }
mutexUnlock(&g_servicesMutex);
return ret; return ret;
} }
void servicesClose(void) void servicesClose(void)
{ {
mutexLock(&g_servicesMutex); SCOPED_LOCK(&g_servicesMutex)
for(u32 i = 0; i < g_serviceInfoCount; i++)
{ {
ServiceInfo *service_info = &(g_serviceInfo[i]); for(u32 i = 0; i < g_serviceInfoCount; i++)
{
/* Check if this service has not been initialized, or if it doesn't have a valid close function. */ ServiceInfo *service_info = &(g_serviceInfo[i]);
if (!service_info->initialized || service_info->close_func == NULL) continue;
/* Check if this service has not been initialized, or if it doesn't have a valid close function. */
/* Close service. */ if (!service_info->initialized || service_info->close_func == NULL) continue;
service_info->close_func();
/* Close service. */
/* Update flag. */ service_info->close_func();
service_info->initialized = false;
/* Update flag. */
service_info->initialized = false;
}
} }
mutexUnlock(&g_servicesMutex);
} }
bool servicesCheckInitializedServiceByName(const char *name) bool servicesCheckInitializedServiceByName(const char *name)
{ {
return _servicesCheckInitializedServiceByName(name, true); bool ret = false;
SCOPED_LOCK(&g_servicesMutex) ret = _servicesCheckInitializedServiceByName(name);
return ret;
} }
bool servicesCheckRunningServiceByName(const char *name) bool servicesCheckRunningServiceByName(const char *name)
{ {
Result rc = 0; bool ret = false;
bool out = false;
SmServiceName service_name = {0};
mutexLock(&g_servicesMutex); SCOPED_LOCK(&g_servicesMutex)
if (!name || !*name || !_servicesCheckInitializedServiceByName("spl:", false))
{ {
LOG_MSG("Invalid parameters!"); if (!name || !*name || !_servicesCheckInitializedServiceByName("spl:"))
goto end; {
LOG_MSG("Invalid parameters!");
break;
}
Result rc = servicesAtmosphereHasService(&ret, smEncodeName(name));
if (R_FAILED(rc)) LOG_MSG("servicesAtmosphereHasService failed for \"%s\"! (0x%08X).", name, rc);
} }
service_name = smEncodeName(name); return ret;
rc = servicesAtmosphereHasService(&out, service_name);
if (R_FAILED(rc)) LOG_MSG("servicesAtmosphereHasService failed for \"%s\"! (0x%08X).", name, rc);
end:
mutexUnlock(&g_servicesMutex);
return out;
} }
void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate) void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate)
{ {
Result rc1 = 0, rc2 = 0; SCOPED_LOCK(&g_servicesMutex)
mutexLock(&g_servicesMutex);
if ((g_clkSvcUsePcv && !_servicesCheckInitializedServiceByName("pcv", false)) || (!g_clkSvcUsePcv && !_servicesCheckInitializedServiceByName("clkrst", false)))
{ {
LOG_MSG("Error: clock service uninitialized."); if ((g_clkSvcUsePcv && !_servicesCheckInitializedServiceByName("pcv")) || (!g_clkSvcUsePcv && !_servicesCheckInitializedServiceByName("clkrst")))
goto end; {
LOG_MSG("Error: clock service uninitialized.");
break;
}
Result rc1 = 0, rc2 = 0;
if (g_clkSvcUsePcv)
{
rc1 = pcvSetClockRate(PcvModule_CpuBus, cpu_rate);
rc2 = pcvSetClockRate(PcvModule_EMC, mem_rate);
} else {
rc1 = clkrstSetClockRate(&g_clkrstCpuSession, cpu_rate);
rc2 = clkrstSetClockRate(&g_clkrstMemSession, mem_rate);
}
if (R_FAILED(rc1)) LOG_MSG("%sSetClockRate failed! (0x%08X) (CPU).", (g_clkSvcUsePcv ? "pcv" : "clkrst"), rc1);
if (R_FAILED(rc2)) LOG_MSG("%sSetClockRate failed! (0x%08X) (MEM).", (g_clkSvcUsePcv ? "pcv" : "clkrst"), rc2);
} }
if (g_clkSvcUsePcv)
{
rc1 = pcvSetClockRate(PcvModule_CpuBus, cpu_rate);
rc2 = pcvSetClockRate(PcvModule_EMC, mem_rate);
} else {
rc1 = clkrstSetClockRate(&g_clkrstCpuSession, cpu_rate);
rc2 = clkrstSetClockRate(&g_clkrstMemSession, mem_rate);
}
if (R_FAILED(rc1)) LOG_MSG("%sSetClockRate failed! (0x%08X) (CPU).", (g_clkSvcUsePcv ? "pcv" : "clkrst"), rc1);
if (R_FAILED(rc2)) LOG_MSG("%sSetClockRate failed! (0x%08X) (MEM).", (g_clkSvcUsePcv ? "pcv" : "clkrst"), rc2);
end:
mutexUnlock(&g_servicesMutex);
} }
static bool _servicesCheckInitializedServiceByName(const char *name, bool lock) static bool _servicesCheckInitializedServiceByName(const char *name)
{ {
if (!name || !*name) return false;
bool ret = false; bool ret = false;
if (lock) mutexLock(&g_servicesMutex);
if (!name || !*name) goto end;
for(u32 i = 0; i < g_serviceInfoCount; i++) for(u32 i = 0; i < g_serviceInfoCount; i++)
{ {
ServiceInfo *service_info = &(g_serviceInfo[i]); ServiceInfo *service_info = &(g_serviceInfo[i]);
@ -221,9 +210,6 @@ static bool _servicesCheckInitializedServiceByName(const char *name, bool lock)
} }
} }
end:
if (lock) mutexUnlock(&g_servicesMutex);
return ret; return ret;
} }

View file

@ -428,7 +428,7 @@ static bool titleRefreshGameCardTitleInfo(void);
static void titleRemoveGameCardTitleInfoEntries(void); static void titleRemoveGameCardTitleInfoEntries(void);
static bool titleIsUserApplicationContentAvailable(u64 app_id); static bool titleIsUserApplicationContentAvailable(u64 app_id);
static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id, bool lock); static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id);
static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b); static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b);
static int titleUserApplicationMetadataEntrySortFunction(const void *a, const void *b); static int titleUserApplicationMetadataEntrySortFunction(const void *a, const void *b);
@ -436,114 +436,114 @@ static int titleOrphanTitleInfoSortFunction(const void *a, const void *b);
bool titleInitialize(void) bool titleInitialize(void)
{ {
mutexLock(&g_titleMutex); bool ret = false;
bool ret = g_titleInterfaceInit; SCOPED_LOCK(&g_titleMutex)
if (ret) goto end;
/* Allocate memory for the ns application control data. */
/* This will be used each time we need to retrieve the metadata from an application. */
g_nsAppControlData = calloc(1, sizeof(NsApplicationControlData));
if (!g_nsAppControlData)
{ {
LOG_MSG("Failed to allocate memory for the ns application control data!"); ret = g_titleInterfaceInit;
goto end; if (ret) break;
/* Allocate memory for the ns application control data. */
/* This will be used each time we need to retrieve the metadata from an application. */
g_nsAppControlData = calloc(1, sizeof(NsApplicationControlData));
if (!g_nsAppControlData)
{
LOG_MSG("Failed to allocate memory for the ns application control data!");
break;
}
/* Generate application metadata entries from hardcoded system titles, since we can't retrieve their names via ns. */
if (!titleGenerateMetadataEntriesFromSystemTitles())
{
LOG_MSG("Failed to generate application metadata from hardcoded system titles!");
break;
}
/* Generate application metadata entries from ns records. */
/* Theoretically speaking, we should only need to do this once. */
/* However, if any new gamecard is inserted while the application is running, we *will* have to retrieve the metadata from its application(s). */
if (!titleGenerateMetadataEntriesFromNsRecords())
{
LOG_MSG("Failed to generate application metadata from ns records!");
break;
}
/* Open eMMC System, eMMC User and SD card ncm databases. */
if (!titleOpenNcmDatabases())
{
LOG_MSG("Failed to open ncm databases!");
break;
}
/* Open eMMC System, eMMC User and SD card ncm storages. */
if (!titleOpenNcmStorages())
{
LOG_MSG("Failed to open ncm storages!");
break;
}
/* Load title info by retrieving content meta keys from available eMMC System, eMMC User and SD card titles. */
if (!titleLoadPersistentStorageTitleInfo())
{
LOG_MSG("Failed to load persistent storage title info!");
break;
}
/* Create user-mode exit event. */
ueventCreate(&g_titleGameCardInfoThreadExitEvent, true);
/* Retrieve gamecard status change user event. */
g_titleGameCardStatusChangeUserEvent = gamecardGetStatusChangeUserEvent();
if (!g_titleGameCardStatusChangeUserEvent)
{
LOG_MSG("Failed to retrieve gamecard status change user event!");
break;
}
/* Create user-mode gamecard update info event. */
ueventCreate(&g_titleGameCardUpdateInfoUserEvent, true);
/* Create gamecard title info thread. */
if (!(g_titleGameCardInfoThreadCreated = titleCreateGameCardInfoThread())) break;
/* Update flags. */
ret = g_titleInterfaceInit = true;
} }
/* Generate application metadata entries from hardcoded system titles, since we can't retrieve their names via ns. */
if (!titleGenerateMetadataEntriesFromSystemTitles())
{
LOG_MSG("Failed to generate application metadata from hardcoded system titles!");
goto end;
}
/* Generate application metadata entries from ns records. */
/* Theoretically speaking, we should only need to do this once. */
/* However, if any new gamecard is inserted while the application is running, we *will* have to retrieve the metadata from its application(s). */
if (!titleGenerateMetadataEntriesFromNsRecords())
{
LOG_MSG("Failed to generate application metadata from ns records!");
goto end;
}
/* Open eMMC System, eMMC User and SD card ncm databases. */
if (!titleOpenNcmDatabases())
{
LOG_MSG("Failed to open ncm databases!");
goto end;
}
/* Open eMMC System, eMMC User and SD card ncm storages. */
if (!titleOpenNcmStorages())
{
LOG_MSG("Failed to open ncm storages!");
goto end;
}
/* Load title info by retrieving content meta keys from available eMMC System, eMMC User and SD card titles. */
if (!titleLoadPersistentStorageTitleInfo())
{
LOG_MSG("Failed to load persistent storage title info!");
goto end;
}
/* Create user-mode exit event. */
ueventCreate(&g_titleGameCardInfoThreadExitEvent, true);
/* Retrieve gamecard status change user event. */
g_titleGameCardStatusChangeUserEvent = gamecardGetStatusChangeUserEvent();
if (!g_titleGameCardStatusChangeUserEvent)
{
LOG_MSG("Failed to retrieve gamecard status change user event!");
goto end;
}
/* Create user-mode gamecard update info event. */
ueventCreate(&g_titleGameCardUpdateInfoUserEvent, true);
/* Create gamecard title info thread. */
if (!(g_titleGameCardInfoThreadCreated = titleCreateGameCardInfoThread())) goto end;
ret = g_titleInterfaceInit = true;
end:
mutexUnlock(&g_titleMutex);
return ret; return ret;
} }
void titleExit(void) void titleExit(void)
{ {
mutexLock(&g_titleMutex); SCOPED_LOCK(&g_titleMutex)
/* Destroy gamecard detection thread. */
if (g_titleGameCardInfoThreadCreated)
{ {
titleDestroyGameCardInfoThread(); /* Destroy gamecard detection thread. */
g_titleGameCardInfoThreadCreated = false; if (g_titleGameCardInfoThreadCreated)
{
titleDestroyGameCardInfoThread();
g_titleGameCardInfoThreadCreated = false;
}
/* Free title info. */
titleFreeTitleInfo();
/* Close gamecard ncm database and storage (if needed). */
titleCloseNcmDatabaseAndStorageFromGameCard();
/* Close eMMC System, eMMC User and SD card ncm storages. */
titleCloseNcmStorages();
/* Close eMMC System, eMMC User and SD card ncm databases. */
titleCloseNcmDatabases();
/* Free application metadata. */
titleFreeApplicationMetadata();
/* Free ns application control data. */
if (g_nsAppControlData) free(g_nsAppControlData);
g_titleInterfaceInit = false;
} }
/* Free title info. */
titleFreeTitleInfo();
/* Close gamecard ncm database and storage (if needed). */
titleCloseNcmDatabaseAndStorageFromGameCard();
/* Close eMMC System, eMMC User and SD card ncm storages. */
titleCloseNcmStorages();
/* Close eMMC System, eMMC User and SD card ncm databases. */
titleCloseNcmDatabases();
/* Free application metadata. */
titleFreeApplicationMetadata();
/* Free ns application control data. */
if (g_nsAppControlData) free(g_nsAppControlData);
g_titleInterfaceInit = false;
mutexUnlock(&g_titleMutex);
} }
NcmContentMetaDatabase *titleGetNcmDatabaseByStorageId(u8 storage_id) NcmContentMetaDatabase *titleGetNcmDatabaseByStorageId(u8 storage_id)
@ -598,296 +598,291 @@ NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id)
TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count) TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count)
{ {
mutexLock(&g_titleMutex);
u32 start_idx = (is_system ? 0 : g_systemTitlesCount);
u32 max_val = (is_system ? g_systemTitlesCount : g_appMetadataCount);
u32 app_count = 0; u32 app_count = 0;
TitleApplicationMetadata **app_metadata = NULL, **tmp_app_metadata = NULL; TitleApplicationMetadata **app_metadata = NULL, **tmp_app_metadata = NULL;
if (!g_titleInterfaceInit || !g_appMetadata || !*g_appMetadata || (is_system && g_appMetadataCount < g_systemTitlesCount) || (!is_system && g_appMetadataCount == g_systemTitlesCount) || !out_count) SCOPED_LOCK(&g_titleMutex)
{ {
LOG_MSG("Invalid parameters!"); if (!g_titleInterfaceInit || !g_appMetadata || !*g_appMetadata || (is_system && g_appMetadataCount < g_systemTitlesCount) || (!is_system && g_appMetadataCount == g_systemTitlesCount) || \
goto end; !out_count)
}
for(u32 i = start_idx; i < max_val; i++)
{
TitleApplicationMetadata *cur_app_metadata = g_appMetadata[i];
if (!cur_app_metadata) continue;
/* Skip current metadata entry if content data for this title isn't available. */
if ((is_system && !_titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, cur_app_metadata->title_id, false)) || \
(!is_system && !titleIsUserApplicationContentAvailable(cur_app_metadata->title_id))) continue;
/* Reallocate application metadata pointer array. */
tmp_app_metadata = realloc(app_metadata, (app_count + 1) * sizeof(TitleApplicationMetadata*));
if (!tmp_app_metadata)
{ {
LOG_MSG("Failed to reallocate application metadata pointer array!"); LOG_MSG("Invalid parameters!");
if (app_metadata) free(app_metadata); break;
app_metadata = NULL;
goto end;
} }
app_metadata = tmp_app_metadata; u32 start_idx = (is_system ? 0 : g_systemTitlesCount), max_val = (is_system ? g_systemTitlesCount : g_appMetadataCount);
tmp_app_metadata = NULL; bool error = false;
/* Set current pointer and increase counter. */ for(u32 i = start_idx; i < max_val; i++)
app_metadata[app_count++] = cur_app_metadata; {
TitleApplicationMetadata *cur_app_metadata = g_appMetadata[i];
if (!cur_app_metadata) continue;
/* Skip current metadata entry if content data for this title isn't available. */
if ((is_system && !_titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, cur_app_metadata->title_id)) || \
(!is_system && !titleIsUserApplicationContentAvailable(cur_app_metadata->title_id))) continue;
/* Reallocate application metadata pointer array. */
tmp_app_metadata = realloc(app_metadata, (app_count + 1) * sizeof(TitleApplicationMetadata*));
if (!tmp_app_metadata)
{
LOG_MSG("Failed to reallocate application metadata pointer array!");
if (app_metadata) free(app_metadata);
app_metadata = NULL;
error = true;
break;
}
app_metadata = tmp_app_metadata;
tmp_app_metadata = NULL;
/* Set current pointer and increase counter. */
app_metadata[app_count++] = cur_app_metadata;
}
if (error) break;
/* Update output counter. */
*out_count = app_count;
if (!app_metadata || !app_count) LOG_MSG("No content data found for %s!", is_system ? "system titles" : "user applications");
} }
/* Update output counter. */
*out_count = app_count;
if (!app_metadata || !app_count) LOG_MSG("No content data found for %s!", is_system ? "system titles" : "user applications");
end:
mutexUnlock(&g_titleMutex);
return app_metadata; return app_metadata;
} }
TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id)
{ {
return _titleGetInfoFromStorageByTitleId(storage_id, title_id, true); TitleInfo *ret = NULL;
SCOPED_LOCK(&g_titleMutex) ret = _titleGetInfoFromStorageByTitleId(storage_id, title_id);
return ret;
} }
bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out) bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out)
{ {
mutexLock(&g_titleMutex); bool ret = false;
bool success = false; SCOPED_LOCK(&g_titleMutex)
if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !app_id || !out)
{ {
LOG_MSG("Invalid parameters!"); if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !app_id || !out)
goto end;
}
/* Clear output. */
memset(out, 0, sizeof(TitleUserApplicationData));
/* Get first user application title info. */
out->app_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id, false);
/* Get first patch title info. */
out->patch_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetPatchIdByApplicationId(app_id), false);
/* Get first add-on content title info. */
for(u32 i = g_titleInfoBuiltInUserStartIndex; i < g_titleInfoCount; i++)
{
TitleInfo *title_info = g_titleInfo[i];
if (title_info && title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id))
{ {
out->aoc_info = title_info; LOG_MSG("Invalid parameters!");
break; break;
} }
/* Clear output. */
memset(out, 0, sizeof(TitleUserApplicationData));
/* Get info for the first user application title. */
out->app_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id);
/* Get info for the first patch title. */
out->patch_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetPatchIdByApplicationId(app_id));
/* Get info for the first add-on content title. */
for(u32 i = g_titleInfoBuiltInUserStartIndex; i < g_titleInfoCount; i++)
{
TitleInfo *title_info = g_titleInfo[i];
if (title_info && title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id))
{
out->aoc_info = title_info;
break;
}
}
/* Check retrieved title info. */
ret = (out->app_info || out->patch_info || out->aoc_info);
if (!ret) LOG_MSG("Failed to retrieve user application data for ID \"%016lX\"!", app_id);
} }
/* Check retrieved title info. */ return ret;
success = (out->app_info || out->patch_info || out->aoc_info);
if (!success)
{
LOG_MSG("Failed to retrieve user application data for ID \"%016lX\"!", app_id);
goto end;
}
end:
mutexUnlock(&g_titleMutex);
return success;
} }
bool titleAreOrphanTitlesAvailable(void) bool titleAreOrphanTitlesAvailable(void)
{ {
mutexLock(&g_titleMutex); bool ret = false;
bool ret = (g_titleInterfaceInit && g_orphanTitleInfo && *g_orphanTitleInfo && g_orphanTitleInfoCount > 0); SCOPED_LOCK(&g_titleMutex) ret = (g_titleInterfaceInit && g_orphanTitleInfo && *g_orphanTitleInfo && g_orphanTitleInfoCount > 0);
mutexUnlock(&g_titleMutex);
return ret; return ret;
} }
TitleInfo **titleGetInfoFromOrphanTitles(u32 *out_count) TitleInfo **titleGetInfoFromOrphanTitles(u32 *out_count)
{ {
mutexLock(&g_titleMutex);
TitleInfo **orphan_info = NULL; TitleInfo **orphan_info = NULL;
if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !g_orphanTitleInfo || !*g_orphanTitleInfo || !g_orphanTitleInfoCount || !out_count) SCOPED_LOCK(&g_titleMutex)
{ {
LOG_MSG("Invalid parameters!"); if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !g_orphanTitleInfo || !*g_orphanTitleInfo || !g_orphanTitleInfoCount || !out_count)
goto end; {
LOG_MSG("Invalid parameters!");
break;
}
/* Allocate orphan title info pointer array. */
orphan_info = calloc(g_orphanTitleInfoCount, sizeof(TitleInfo*));
if (!orphan_info)
{
LOG_MSG("Failed to allocate memory for orphan title info pointer array!");
break;
}
/* Get pointers to orphan title info entries. */
for(u32 i = 0; i < g_orphanTitleInfoCount; i++) orphan_info[i] = g_orphanTitleInfo[i];
/* Update output counter. */
*out_count = g_orphanTitleInfoCount;
} }
/* Allocate orphan title info pointer array. */
orphan_info = calloc(g_orphanTitleInfoCount, sizeof(TitleInfo*));
if (!orphan_info)
{
LOG_MSG("Failed to allocate memory for orphan title info pointer array!");
goto end;
}
/* Get pointers to orphan title info entries. */
for(u32 i = 0; i < g_orphanTitleInfoCount; i++) orphan_info[i] = g_orphanTitleInfo[i];
/* Update output counter. */
*out_count = g_orphanTitleInfoCount;
end:
mutexUnlock(&g_titleMutex);
return orphan_info; return orphan_info;
} }
bool titleIsGameCardInfoUpdated(void) bool titleIsGameCardInfoUpdated(void)
{ {
mutexLock(&g_titleMutex); bool ret = false;
/* Check if the gamecard thread detected a gamecard status change. */ SCOPED_LOCK(&g_titleMutex)
bool ret = (g_titleInterfaceInit && g_titleGameCardInfoThreadCreated && g_titleGameCardInfoUpdated); {
if (!ret) goto end; /* Check if the gamecard thread detected a gamecard status change. */
ret = (g_titleInterfaceInit && g_titleGameCardInfoThreadCreated && g_titleGameCardInfoUpdated);
if (!ret) break;
/* Signal the gamecard update info user event. */
ueventSignal(&g_titleGameCardUpdateInfoUserEvent);
/* Wait for the gamecard thread to wake us up. */
condvarWait(&g_gameCardCondVar, &g_titleMutex);
/* Update output value and gamecard info updated flag (if needed). */
ret = g_titleGameCardInfoUpdated;
if (ret) g_titleGameCardInfoUpdated = false;
}
/* Signal the gamecard update info user event. */
ueventSignal(&g_titleGameCardUpdateInfoUserEvent);
/* Wait for the gamecard thread to wake us up. */
condvarWait(&g_gameCardCondVar, &g_titleMutex);
/* Update output value and gamecard info updated flag (if needed). */
ret = g_titleGameCardInfoUpdated;
if (ret) g_titleGameCardInfoUpdated = false;
end:
mutexUnlock(&g_titleMutex);
return ret; return ret;
} }
char *titleGenerateFileName(const TitleInfo *title_info, u8 name_convention, u8 illegal_char_replace_type) char *titleGenerateFileName(const TitleInfo *title_info, u8 name_convention, u8 illegal_char_replace_type)
{ {
mutexLock(&g_titleMutex);
char *filename = NULL;
char title_name[0x400] = {0};
if (!title_info || title_info->meta_key.type < NcmContentMetaType_Application || title_info->meta_key.type > NcmContentMetaType_Delta || name_convention > TitleFileNameConvention_IdAndVersionOnly || \ if (!title_info || title_info->meta_key.type < NcmContentMetaType_Application || title_info->meta_key.type > NcmContentMetaType_Delta || name_convention > TitleFileNameConvention_IdAndVersionOnly || \
(name_convention == TitleFileNameConvention_Full && illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)) (name_convention == TitleFileNameConvention_Full && illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly))
{ {
LOG_MSG("Invalid parameters!"); LOG_MSG("Invalid parameters!");
goto end; return NULL;
} }
u8 type = (title_info->meta_key.type - 0x80); char *filename = NULL;
/* Generate filename for this title. */ SCOPED_LOCK(&g_titleMutex)
if (name_convention == TitleFileNameConvention_Full)
{ {
if (title_info->app_metadata && *(title_info->app_metadata->lang_entry.name)) char title_name[0x400] = {0};
u8 type = (title_info->meta_key.type - 0x80);
/* Generate filename for this title. */
if (name_convention == TitleFileNameConvention_Full)
{ {
sprintf(title_name, "%s ", title_info->app_metadata->lang_entry.name); if (title_info->app_metadata && *(title_info->app_metadata->lang_entry.name))
if (illegal_char_replace_type) utilsReplaceIllegalCharacters(title_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly); {
sprintf(title_name, "%s ", title_info->app_metadata->lang_entry.name);
if (illegal_char_replace_type) utilsReplaceIllegalCharacters(title_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly);
}
sprintf(title_name + strlen(title_name), "[%016lX][v%u][%s]", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type]);
} else
if (name_convention == TitleFileNameConvention_IdAndVersionOnly)
{
sprintf(title_name, "%016lX_v%u_%s", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type]);
} }
sprintf(title_name + strlen(title_name), "[%016lX][v%u][%s]", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type]); /* Duplicate generated filename. */
} else filename = strdup(title_name);
if (name_convention == TitleFileNameConvention_IdAndVersionOnly) if (!filename) LOG_MSG("Failed to duplicate generated filename!");
{
sprintf(title_name, "%016lX_v%u_%s", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type]);
} }
/* Duplicate generated filename. */
filename = strdup(title_name);
if (!filename) LOG_MSG("Failed to duplicate generated filename!");
end:
mutexUnlock(&g_titleMutex);
return filename; return filename;
} }
char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_type) char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_type)
{ {
mutexLock(&g_titleMutex); char *filename = NULL;
size_t cur_filename_len = 0; SCOPED_LOCK(&g_titleMutex)
char *filename = NULL, *tmp_filename = NULL;
char app_name[0x400] = {0};
if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !g_titleGameCardAvailable || !g_titleInfoGameCardCount || \
name_convention > TitleFileNameConvention_IdAndVersionOnly || (name_convention == TitleFileNameConvention_Full && \
illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly))
{ {
LOG_MSG("Invalid parameters!"); if (!g_titleInterfaceInit || !g_titleInfo || !*g_titleInfo || !g_titleInfoCount || !g_titleGameCardAvailable || !g_titleInfoGameCardCount || \
goto end; name_convention > TitleFileNameConvention_IdAndVersionOnly || (name_convention == TitleFileNameConvention_Full && \
} illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly))
for(u32 i = g_titleInfoGameCardStartIndex; i < g_titleInfoCount; i++)
{
TitleInfo *app_info = g_titleInfo[i];
if (!app_info || app_info->meta_key.type != NcmContentMetaType_Application) continue;
u32 app_version = app_info->meta_key.version;
/* Check if the inserted gamecard holds any bundled patches for the current user application. */
/* If so, we'll use the highest patch version available as part of the filename. */
for(u32 j = g_titleInfoGameCardStartIndex; j < g_titleInfoCount; j++)
{ {
if (j == i) continue; LOG_MSG("Invalid parameters!");
break;
TitleInfo *patch_info = g_titleInfo[j];
if (!patch_info || patch_info->meta_key.type != NcmContentMetaType_Patch || !titleCheckIfPatchIdBelongsToApplicationId(app_info->meta_key.id, patch_info->meta_key.id) || \
patch_info->meta_key.version <= app_version) continue;
app_version = patch_info->meta_key.version;
} }
/* Generate current user application name. */ size_t cur_filename_len = 0;
*app_name = '\0'; char *tmp_filename = NULL, app_name[0x400] = {0};
bool error = false;
if (name_convention == TitleFileNameConvention_Full) for(u32 i = g_titleInfoGameCardStartIndex; i < g_titleInfoCount; i++)
{ {
if (cur_filename_len) strcat(app_name, " + "); TitleInfo *app_info = g_titleInfo[i];
if (!app_info || app_info->meta_key.type != NcmContentMetaType_Application) continue;
if (app_info->app_metadata && *(app_info->app_metadata->lang_entry.name)) u32 app_version = app_info->meta_key.version;
/* Check if the inserted gamecard holds any bundled patches for the current user application. */
/* If so, we'll use the highest patch version available as part of the filename. */
for(u32 j = g_titleInfoGameCardStartIndex; j < g_titleInfoCount; j++)
{ {
sprintf(app_name + strlen(app_name), "%s ", app_info->app_metadata->lang_entry.name); if (j == i) continue;
if (illegal_char_replace_type) utilsReplaceIllegalCharacters(app_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly);
TitleInfo *patch_info = g_titleInfo[j];
if (!patch_info || patch_info->meta_key.type != NcmContentMetaType_Patch || !titleCheckIfPatchIdBelongsToApplicationId(app_info->meta_key.id, patch_info->meta_key.id) || \
patch_info->meta_key.version <= app_version) continue;
app_version = patch_info->meta_key.version;
} }
sprintf(app_name + strlen(app_name), "[%016lX][v%u]", app_info->meta_key.id, app_version); /* Generate current user application name. */
} else *app_name = '\0';
if (name_convention == TitleFileNameConvention_IdAndVersionOnly)
{ if (name_convention == TitleFileNameConvention_Full)
if (cur_filename_len) strcat(app_name, "+"); {
sprintf(app_name + strlen(app_name), "%016lX_v%u", app_info->meta_key.id, app_version); if (cur_filename_len) strcat(app_name, " + ");
if (app_info->app_metadata && *(app_info->app_metadata->lang_entry.name))
{
sprintf(app_name + strlen(app_name), "%s ", app_info->app_metadata->lang_entry.name);
if (illegal_char_replace_type) utilsReplaceIllegalCharacters(app_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly);
}
sprintf(app_name + strlen(app_name), "[%016lX][v%u]", app_info->meta_key.id, app_version);
} else
if (name_convention == TitleFileNameConvention_IdAndVersionOnly)
{
if (cur_filename_len) strcat(app_name, "+");
sprintf(app_name + strlen(app_name), "%016lX_v%u", app_info->meta_key.id, app_version);
}
/* Reallocate output buffer. */
size_t app_name_len = strlen(app_name);
tmp_filename = realloc(filename, (cur_filename_len + app_name_len + 1) * sizeof(char));
if (!tmp_filename)
{
LOG_MSG("Failed to reallocate filename buffer!");
if (filename) free(filename);
filename = NULL;
error = true;
break;
}
filename = tmp_filename;
tmp_filename = NULL;
/* Concatenate current user application name. */
filename[cur_filename_len] = '\0';
strcat(filename, app_name);
cur_filename_len += app_name_len;
} }
/* Reallocate output buffer. */ if (!filename && !error) LOG_MSG("Error: the inserted gamecard doesn't hold any user applications!");
size_t app_name_len = strlen(app_name);
tmp_filename = realloc(filename, (cur_filename_len + app_name_len + 1) * sizeof(char));
if (!tmp_filename)
{
LOG_MSG("Failed to reallocate filename buffer!");
if (filename) free(filename);
filename = NULL;
goto end;
}
filename = tmp_filename;
tmp_filename = NULL;
/* Concatenate current user application name. */
filename[cur_filename_len] = '\0';
strcat(filename, app_name);
cur_filename_len += app_name_len;
} }
if (!filename) LOG_MSG("Error: the inserted gamecard doesn't hold any user applications!");
end:
mutexUnlock(&g_titleMutex);
/* Fallback string if any errors occur. */ /* Fallback string if any errors occur. */
/* This function is guaranteed to fail with Kiosk / Quest gamecards, so that's why this is needed. */ /* This function is guaranteed to fail with Kiosk / Quest gamecards, so that's why this is needed. */
if (!filename) filename = strdup("gamecard"); if (!filename) filename = strdup("gamecard");
@ -1838,17 +1833,13 @@ static void titleGameCardInfoThreadFunc(void *arg)
if (!first_run) if (!first_run)
{ {
/* Update gamecard info updated flag. */ /* Update gamecard info updated flag. */
mutexLock(&g_titleMutex); SCOPED_LOCK(&g_titleMutex) g_titleGameCardInfoUpdated = true;
g_titleGameCardInfoUpdated = true;
mutexUnlock(&g_titleMutex);
/* Wait until another function signals us (titleIsGameCardInfoUpdated() or titleExit()). */ /* Wait until another function signals us (titleIsGameCardInfoUpdated or titleExit). */
rc = waitMulti(&idx, -1, update_info_waiter, exit_event_waiter); rc = waitMulti(&idx, -1, update_info_waiter, exit_event_waiter);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
mutexLock(&g_titleMutex); SCOPED_LOCK(&g_titleMutex) g_titleGameCardInfoUpdated = false;
g_titleGameCardInfoUpdated = false;
mutexUnlock(&g_titleMutex);
continue; continue;
} }
@ -1857,9 +1848,7 @@ static void titleGameCardInfoThreadFunc(void *arg)
} }
/* Update gamecard title info. */ /* Update gamecard title info. */
mutexLock(&g_titleMutex); SCOPED_LOCK(&g_titleMutex) g_titleGameCardInfoUpdated = (titleRefreshGameCardTitleInfo() && !first_run);
g_titleGameCardInfoUpdated = (titleRefreshGameCardTitleInfo() && !first_run);
mutexUnlock(&g_titleMutex);
if (first_run) if (first_run)
{ {
@ -2055,17 +2044,18 @@ static bool titleIsUserApplicationContentAvailable(u64 app_id)
return false; return false;
} }
static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id, bool lock) static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id)
{ {
if (lock) mutexLock(&g_titleMutex);
TitleInfo *info = NULL; TitleInfo *info = NULL;
u32 start_idx = ((storage_id == NcmStorageId_BuiltInSystem || storage_id == NcmStorageId_Any) ? 0 : (storage_id == NcmStorageId_BuiltInUser ? g_titleInfoBuiltInUserStartIndex : \ u32 start_idx = ((storage_id == NcmStorageId_BuiltInSystem || storage_id == NcmStorageId_Any) ? 0 : \
(storage_id == NcmStorageId_BuiltInUser ? g_titleInfoBuiltInUserStartIndex : \
(storage_id == NcmStorageId_SdCard ? g_titleInfoSdCardStartIndex : g_titleInfoGameCardStartIndex))); (storage_id == NcmStorageId_SdCard ? g_titleInfoSdCardStartIndex : g_titleInfoGameCardStartIndex)));
u32 max_val = (storage_id == NcmStorageId_BuiltInSystem ? g_titleInfoBuiltInSystemCount : (storage_id == NcmStorageId_BuiltInUser ? g_titleInfoBuiltInUserCount : \ u32 max_val = (storage_id == NcmStorageId_BuiltInSystem ? g_titleInfoBuiltInSystemCount : \
(storage_id == NcmStorageId_SdCard ? g_titleInfoSdCardCount : (storage_id == NcmStorageId_GameCard ? g_titleInfoGameCardCount : g_titleInfoCount)))); (storage_id == NcmStorageId_BuiltInUser ? g_titleInfoBuiltInUserCount : \
(storage_id == NcmStorageId_SdCard ? g_titleInfoSdCardCount : \
(storage_id == NcmStorageId_GameCard ? g_titleInfoGameCardCount : g_titleInfoCount))));
max_val += start_idx; max_val += start_idx;
@ -2086,11 +2076,9 @@ static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id,
} }
} }
if (!info && lock) LOG_MSG("Unable to find title info entry with ID \"%016lX\"! (storage ID %u).", title_id, storage_id); if (!info) LOG_MSG("Unable to find title info entry with ID \"%016lX\"! (storage ID %u).", title_id, storage_id);
end: end:
if (lock) mutexUnlock(&g_titleMutex);
return info; return info;
} }

View file

@ -43,109 +43,104 @@ static void umsFreeDeviceData(void);
bool umsInitialize(void) bool umsInitialize(void)
{ {
mutexLock(&g_umsMutex); bool ret = false;
Result rc = 0; SCOPED_LOCK(&g_umsMutex)
bool ret = g_umsInterfaceInit;
if (ret) goto end;
/* Initialize USB Mass Storage Host interface. */
rc = usbHsFsInitialize(0);
if (R_FAILED(rc))
{ {
LOG_MSG("usbHsFsInitialize failed! (0x%08X).", rc); ret = g_umsInterfaceInit;
goto end; if (ret) break;
/* Initialize USB Mass Storage Host interface. */
Result rc = usbHsFsInitialize(0);
if (R_FAILED(rc))
{
LOG_MSG("usbHsFsInitialize failed! (0x%08X).", rc);
break;
}
/* Get USB Mass Storage status change event. */
g_umsStatusChangeEvent = usbHsFsGetStatusChangeUserEvent();
/* Create user-mode exit event. */
ueventCreate(&g_umsDetectionThreadExitEvent, true);
/* Create USB Mass Storage detection thread. */
if (!(g_umsDetectionThreadCreated = umsCreateDetectionThread())) break;
/* Update flags. */
ret = g_umsInterfaceInit = true;
} }
/* Get USB Mass Storage status change event. */
g_umsStatusChangeEvent = usbHsFsGetStatusChangeUserEvent();
/* Create user-mode exit event. */
ueventCreate(&g_umsDetectionThreadExitEvent, true);
/* Create USB Mass Storage detection thread. */
if (!(g_umsDetectionThreadCreated = umsCreateDetectionThread())) goto end;
ret = g_umsInterfaceInit = true;
end:
mutexUnlock(&g_umsMutex);
return ret; return ret;
} }
void umsExit(void) void umsExit(void)
{ {
mutexLock(&g_umsMutex); SCOPED_LOCK(&g_umsMutex)
if (!g_umsInterfaceInit) goto end;
/* Destroy USB Mass Storage detection thread. */
if (g_umsDetectionThreadCreated)
{ {
umsDestroyDetectionThread(); /* Destroy USB Mass Storage detection thread. */
g_umsDetectionThreadCreated = false; if (g_umsDetectionThreadCreated)
{
umsDestroyDetectionThread();
g_umsDetectionThreadCreated = false;
}
/* Close USB Mass Storage Host interface. */
usbHsFsExit();
/* Update flag. */
g_umsInterfaceInit = false;
} }
/* Close USB Mass Storage Host interface. */
usbHsFsExit();
g_umsInterfaceInit = false;
end:
mutexUnlock(&g_umsMutex);
} }
bool umsIsDeviceInfoUpdated(void) bool umsIsDeviceInfoUpdated(void)
{ {
mutexLock(&g_umsMutex);
bool ret = false; bool ret = false;
if (g_umsInterfaceInit && g_umsDeviceInfoUpdated) SCOPED_LOCK(&g_umsMutex)
{ {
if (!g_umsInterfaceInit || !g_umsDeviceInfoUpdated) break;
ret = true; ret = true;
g_umsDeviceInfoUpdated = false; g_umsDeviceInfoUpdated = false;
} }
mutexUnlock(&g_umsMutex);
return ret; return ret;
} }
UsbHsFsDevice *umsGetDevices(u32 *out_count) UsbHsFsDevice *umsGetDevices(u32 *out_count)
{ {
mutexLock(&g_umsMutex);
UsbHsFsDevice *devices = NULL; UsbHsFsDevice *devices = NULL;
if (!g_umsInterfaceInit || !out_count) SCOPED_LOCK(&g_umsMutex)
{
LOG_MSG("Invalid parameters!");
goto end;
}
if (g_umsDeviceCount && g_umsDevices)
{ {
if (!g_umsInterfaceInit || !out_count)
{
LOG_MSG("Invalid parameters!");
break;
}
if (!g_umsDeviceCount || !g_umsDevices)
{
/* Update output device count. */
*out_count = 0;
break;
}
/* Allocate memory for the output devices. */ /* Allocate memory for the output devices. */
devices = calloc(g_umsDeviceCount, sizeof(UsbHsFsDevice)); devices = calloc(g_umsDeviceCount, sizeof(UsbHsFsDevice));
if (!devices) if (!devices)
{ {
LOG_MSG("Failed to allocate memory for %u devices!", g_umsDeviceCount); LOG_MSG("Failed to allocate memory for %u devices!", g_umsDeviceCount);
goto end; break;
} }
/* Copy device data. */ /* Copy device data. */
memcpy(devices, g_umsDevices, g_umsDeviceCount * sizeof(UsbHsFsDevice)); memcpy(devices, g_umsDevices, g_umsDeviceCount * sizeof(UsbHsFsDevice));
/* Update output device count. */
*out_count = g_umsDeviceCount;
} }
/* Update output device count. */
*out_count = ((g_umsDeviceCount && g_umsDevices) ? g_umsDeviceCount : 0);
end:
mutexUnlock(&g_umsMutex);
return devices; return devices;
} }
@ -189,53 +184,52 @@ static void umsDetectionThreadFunc(void *arg)
/* Exit event triggered. */ /* Exit event triggered. */
if (idx == 1) break; if (idx == 1) break;
mutexLock(&g_umsMutex); SCOPED_LOCK(&g_umsMutex)
/* Free USB Mass Storage device data. */
umsFreeDeviceData();
/* Get mounted device count. */
g_umsDeviceCount = usbHsFsGetMountedDeviceCount();
LOG_MSG("USB Mass Storage status change event triggered! Mounted USB Mass Storage device count: %u.", g_umsDeviceCount);
if (g_umsDeviceCount)
{ {
bool fail = false; /* Free USB Mass Storage device data. */
umsFreeDeviceData();
/* Allocate mounted devices buffer. */ /* Get mounted device count. */
g_umsDevices = calloc(g_umsDeviceCount, sizeof(UsbHsFsDevice)); g_umsDeviceCount = usbHsFsGetMountedDeviceCount();
if (g_umsDevices) LOG_MSG("USB Mass Storage status change event triggered! Mounted USB Mass Storage device count: %u.", g_umsDeviceCount);
if (g_umsDeviceCount)
{ {
/* List mounted devices. */ bool fail = false;
listed_device_count = usbHsFsListMountedDevices(g_umsDevices, g_umsDeviceCount);
if (listed_device_count) /* Allocate mounted devices buffer. */
g_umsDevices = calloc(g_umsDeviceCount, sizeof(UsbHsFsDevice));
if (g_umsDevices)
{ {
/* Check if we got as many devices as we expected. */ /* List mounted devices. */
if (listed_device_count == g_umsDeviceCount) listed_device_count = usbHsFsListMountedDevices(g_umsDevices, g_umsDeviceCount);
if (listed_device_count)
{ {
/* Update USB Mass Storage device info updated flag. */ /* Check if we got as many devices as we expected. */
g_umsDeviceInfoUpdated = true; if (listed_device_count == g_umsDeviceCount)
{
/* Update USB Mass Storage device info updated flag. */
g_umsDeviceInfoUpdated = true;
} else {
LOG_MSG("USB Mass Storage device count mismatch! (%u != %u).", listed_device_count, g_umsDeviceCount);
fail = true;
}
} else { } else {
LOG_MSG("USB Mass Storage device count mismatch! (%u != %u).", listed_device_count, g_umsDeviceCount); LOG_MSG("Failed to list mounted USB Mass Storage devices!");
fail = true; fail = true;
} }
} else { } else {
LOG_MSG("Failed to list mounted USB Mass Storage devices!"); LOG_MSG("Failed to allocate memory for mounted USB Mass Storage devices buffer!");
fail = true; fail = true;
} }
/* Free USB Mass Storage device data if something went wrong. */
if (fail) umsFreeDeviceData();
} else { } else {
LOG_MSG("Failed to allocate memory for mounted USB Mass Storage devices buffer!"); /* Update USB Mass Storage device info updated flag. */
fail = true; g_umsDeviceInfoUpdated = true;
} }
/* Free USB Mass Storage device data if something went wrong. */
if (fail) umsFreeDeviceData();
} else {
/* Update USB Mass Storage device info updated flag. */
g_umsDeviceInfoUpdated = true;
} }
mutexUnlock(&g_umsMutex);
} }
/* Free USB Mass Storage device data. */ /* Free USB Mass Storage device data. */

View file

@ -54,13 +54,6 @@
/* Type definitions. */ /* Type definitions. */
typedef struct {
RwLock lock, lock_in, lock_out;
bool initialized;
UsbDsInterface *interface;
UsbDsEndpoint *endpoint_in, *endpoint_out;
} UsbDeviceInterface;
typedef enum { typedef enum {
UsbCommandType_StartSession = 0, UsbCommandType_StartSession = 0,
UsbCommandType_SendFileProperties = 1, UsbCommandType_SendFileProperties = 1,
@ -187,9 +180,10 @@ NXDT_ASSERT(struct usb_ss_usb_device_capability_descriptor, 0xA);
/* Global variables. */ /* Global variables. */
static RwLock g_usbDeviceLock = {0}; static Mutex g_usbInterfaceMutex = 0;
static UsbDeviceInterface g_usbDeviceInterface = {0}; static UsbDsInterface *g_usbInterface = NULL;
static bool g_usbDeviceInterfaceInit = false; static UsbDsEndpoint *g_usbEndpointIn = NULL, *g_usbEndpointOut = NULL;
static bool g_usbInterfaceInit = false;
static Event *g_usbStateChangeEvent = NULL; static Event *g_usbStateChangeEvent = NULL;
static Thread g_usbDetectionThread = {0}; static Thread g_usbDetectionThread = {0};
@ -220,9 +214,6 @@ NX_INLINE void usbFreeTransferBuffer(void);
static bool usbInitializeComms(void); static bool usbInitializeComms(void);
static void usbCloseComms(void); static void usbCloseComms(void);
static void usbFreeDeviceInterface(void);
NX_INLINE bool usbInitializeDeviceInterface(void);
static bool usbInitializeDeviceInterface5x(void); static bool usbInitializeDeviceInterface5x(void);
static bool usbInitializeDeviceInterface1x(void); static bool usbInitializeDeviceInterface1x(void);
@ -238,45 +229,47 @@ bool usbInitialize(void)
{ {
bool ret = false; bool ret = false;
rwlockWriteLock(&g_usbDeviceLock); SCOPED_LOCK(&g_usbInterfaceMutex)
/* Allocate USB transfer buffer. */
if (!usbAllocateTransferBuffer())
{ {
LOG_MSG("Failed to allocate memory for the USB transfer buffer!"); ret = g_usbInterfaceInit;
goto end; if (ret) break;
/* Allocate USB transfer buffer. */
if (!usbAllocateTransferBuffer())
{
LOG_MSG("Failed to allocate memory for the USB transfer buffer!");
break;
}
/* Initialize USB device interface. */
if (!usbInitializeComms())
{
LOG_MSG("Failed to initialize USB device interface!");
break;
}
/* Retrieve USB state change kernel event. */
g_usbStateChangeEvent = usbDsGetStateChangeEvent();
if (!g_usbStateChangeEvent)
{
LOG_MSG("Failed to retrieve USB state change kernel event!");
break;
}
/* Create user-mode exit event. */
ueventCreate(&g_usbDetectionThreadExitEvent, true);
/* Create user-mode USB timeout event. */
ueventCreate(&g_usbTimeoutEvent, true);
/* Create USB detection thread. */
atomic_store(&g_usbDetectionThreadCreated, usbCreateDetectionThread());
if (!atomic_load(&g_usbDetectionThreadCreated)) break;
/* Update flags. */
ret = g_usbInterfaceInit = true;
} }
/* Initialize USB device interface. */
if (!usbInitializeComms())
{
LOG_MSG("Failed to initialize USB device interface!");
goto end;
}
/* Retrieve USB state change kernel event. */
g_usbStateChangeEvent = usbDsGetStateChangeEvent();
if (!g_usbStateChangeEvent)
{
LOG_MSG("Failed to retrieve USB state change kernel event!");
goto end;
}
/* Create user-mode exit event. */
ueventCreate(&g_usbDetectionThreadExitEvent, true);
/* Create user-mode USB timeout event. */
ueventCreate(&g_usbTimeoutEvent, true);
/* Create USB detection thread. */
atomic_store(&g_usbDetectionThreadCreated, usbCreateDetectionThread());
if (!atomic_load(&g_usbDetectionThreadCreated)) goto end;
ret = true;
end:
rwlockWriteUnlock(&g_usbDeviceLock);
return ret; return ret;
} }
@ -290,18 +283,20 @@ void usbExit(void)
} }
/* Now we can safely lock. */ /* Now we can safely lock. */
rwlockWriteLock(&g_usbDeviceLock); SCOPED_LOCK(&g_usbInterfaceMutex)
{
/* Clear USB state change kernel event. */ /* Clear USB state change kernel event. */
g_usbStateChangeEvent = NULL; g_usbStateChangeEvent = NULL;
/* Close USB device interface. */ /* Close USB device interface. */
usbCloseComms(); usbCloseComms();
/* Free USB transfer buffer. */ /* Free USB transfer buffer. */
usbFreeTransferBuffer(); usbFreeTransferBuffer();
rwlockWriteUnlock(&g_usbDeviceLock); /* Update flag. */
g_usbInterfaceInit = false;
}
} }
void *usbAllocatePageAlignedBuffer(size_t size) void *usbAllocatePageAlignedBuffer(size_t size)
@ -312,209 +307,188 @@ void *usbAllocatePageAlignedBuffer(size_t size)
bool usbIsReady(void) bool usbIsReady(void)
{ {
rwlockWriteLock(&g_usbDeviceLock); bool ret = false;
rwlockWriteLock(&(g_usbDeviceInterface.lock)); SCOPED_LOCK(&g_usbInterfaceMutex) ret = (g_usbHostAvailable && g_usbSessionStarted);
bool ret = (g_usbHostAvailable && g_usbSessionStarted);
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
rwlockWriteUnlock(&g_usbDeviceLock);
return ret; return ret;
} }
bool usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size) bool usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size)
{ {
rwlockWriteLock(&g_usbDeviceLock);
rwlockWriteLock(&(g_usbDeviceInterface.lock));
bool ret = false; bool ret = false;
UsbCommandSendFileProperties *cmd_block = NULL;
size_t filename_length = 0;
if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInit || !g_usbDeviceInterface.initialized || !g_usbHostAvailable || !g_usbSessionStarted || !filename || \ SCOPED_LOCK(&g_usbInterfaceMutex)
!(filename_length = strlen(filename)) || filename_length >= FS_MAX_PATH || (!g_nspTransferMode && ((file_size && nsp_header_size >= file_size) || g_usbTransferRemainingSize)) || \
(g_nspTransferMode && nsp_header_size))
{ {
LOG_MSG("Invalid parameters!"); size_t filename_length = 0;
goto end;
} if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || !filename || !(filename_length = strlen(filename)) || filename_length >= FS_MAX_PATH || \
(!g_nspTransferMode && ((file_size && nsp_header_size >= file_size) || g_usbTransferRemainingSize)) || (g_nspTransferMode && nsp_header_size))
/* Prepare command data. */ {
usbPrepareCommandHeader(UsbCommandType_SendFileProperties, (u32)sizeof(UsbCommandSendFileProperties)); LOG_MSG("Invalid parameters!");
goto end;
cmd_block = (UsbCommandSendFileProperties*)(g_usbTransferBuffer + sizeof(UsbCommandHeader)); }
memset(cmd_block, 0, sizeof(UsbCommandSendFileProperties));
/* Prepare command data. */
cmd_block->file_size = file_size; usbPrepareCommandHeader(UsbCommandType_SendFileProperties, (u32)sizeof(UsbCommandSendFileProperties));
cmd_block->filename_length = (u32)filename_length;
cmd_block->nsp_header_size = nsp_header_size; UsbCommandSendFileProperties *cmd_block = (UsbCommandSendFileProperties*)(g_usbTransferBuffer + sizeof(UsbCommandHeader));
snprintf(cmd_block->filename, sizeof(cmd_block->filename), "%s", filename); memset(cmd_block, 0, sizeof(UsbCommandSendFileProperties));
/* Send command. */ cmd_block->file_size = file_size;
ret = usbSendCommand(); cmd_block->filename_length = (u32)filename_length;
if (!ret) goto end; cmd_block->nsp_header_size = nsp_header_size;
snprintf(cmd_block->filename, sizeof(cmd_block->filename), "%s", filename);
/* Update variables. */
g_usbTransferRemainingSize = file_size; /* Send command. */
g_usbTransferWrittenSize = 0; ret = usbSendCommand();
if (!g_nspTransferMode) g_nspTransferMode = (file_size && nsp_header_size); if (!ret) goto end;
/* Update variables. */
g_usbTransferRemainingSize = file_size;
g_usbTransferWrittenSize = 0;
if (!g_nspTransferMode) g_nspTransferMode = (file_size && nsp_header_size);
end: end:
if (!ret && g_nspTransferMode) g_nspTransferMode = false; if (!ret && g_nspTransferMode) g_nspTransferMode = false;
}
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
rwlockWriteUnlock(&g_usbDeviceLock);
return ret; return ret;
} }
bool usbSendFileData(void *data, u64 data_size) bool usbSendFileData(void *data, u64 data_size)
{ {
rwlockWriteLock(&g_usbDeviceLock); bool ret = false;
rwlockWriteLock(&(g_usbDeviceInterface.lock));
void *buf = NULL; SCOPED_LOCK(&g_usbInterfaceMutex)
UsbStatus *cmd_status = NULL;
bool ret = false, zlt_required = false;
if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInit || !g_usbDeviceInterface.initialized || !g_usbHostAvailable || !g_usbSessionStarted || !g_usbTransferRemainingSize || !data || \
!data_size || data_size > USB_TRANSFER_BUFFER_SIZE || data_size > g_usbTransferRemainingSize)
{ {
LOG_MSG("Invalid parameters!"); void *buf = NULL;
goto end; bool zlt_required = false;
}
if (!g_usbTransferBuffer || !g_usbInterfaceInit || !g_usbHostAvailable || !g_usbSessionStarted || !g_usbTransferRemainingSize || !data || !data_size || data_size > USB_TRANSFER_BUFFER_SIZE || \
/* Optimization for buffers that already are page aligned. */ data_size > g_usbTransferRemainingSize)
if (IS_ALIGNED((u64)data, USB_TRANSFER_ALIGNMENT))
{
buf = data;
} else {
buf = g_usbTransferBuffer;
memcpy(buf, data, data_size);
}
/* Determine if we'll need to set a Zero Length Termination (ZLT) packet. */
/* This is automatically handled by usbDsEndpoint_PostBufferAsync(), depending on the ZLT setting from the input (write) endpoint. */
/* First, check if this is the last data chunk for this file. */
if ((g_usbTransferRemainingSize - data_size) == 0)
{
/* Enable ZLT if the last chunk size is aligned to the USB endpoint max packet size. */
if (IS_ALIGNED(data_size, g_usbEndpointMaxPacketSize))
{ {
zlt_required = true; LOG_MSG("Invalid parameters!");
usbSetZltPacket(true);
//LOG_MSG("ZLT enabled. Last chunk size: 0x%lX bytes.", data_size);
}
} else {
/* Disable ZLT if this is the first of multiple data chunks. */
if (!g_usbTransferWrittenSize)
{
usbSetZltPacket(false);
//LOG_MSG("ZLT disabled (first chunk).");
}
}
/* Send data chunk. */
if (!usbWrite(buf, data_size))
{
LOG_MSG("Failed to write 0x%lX bytes long file data chunk from offset 0x%lX! (total size: 0x%lX).", data_size, g_usbTransferWrittenSize, g_usbTransferRemainingSize + g_usbTransferWrittenSize);
goto end;
}
ret = true;
g_usbTransferRemainingSize -= data_size;
g_usbTransferWrittenSize += data_size;
/* Check if this is the last chunk. */
if (!g_usbTransferRemainingSize)
{
/* Check response from host device. */
if (!usbRead(g_usbTransferBuffer, sizeof(UsbStatus)))
{
LOG_MSG("Failed to read 0x%lX bytes long status block!", sizeof(UsbStatus));
ret = false;
goto end; goto end;
} }
cmd_status = (UsbStatus*)g_usbTransferBuffer; /* Optimization for buffers that already are page aligned. */
if (IS_ALIGNED((u64)data, USB_TRANSFER_ALIGNMENT))
if (cmd_status->magic != __builtin_bswap32(USB_CMD_HEADER_MAGIC))
{ {
LOG_MSG("Invalid status block magic word!"); buf = data;
ret = false; } else {
buf = g_usbTransferBuffer;
memcpy(buf, data, data_size);
}
/* Determine if we'll need to set a Zero Length Termination (ZLT) packet. */
/* This is automatically handled by usbDsEndpoint_PostBufferAsync(), depending on the ZLT setting from the input (write) endpoint. */
/* First, check if this is the last data chunk for this file. */
if ((g_usbTransferRemainingSize - data_size) == 0)
{
/* Enable ZLT if the last chunk size is aligned to the USB endpoint max packet size. */
if (IS_ALIGNED(data_size, g_usbEndpointMaxPacketSize))
{
zlt_required = true;
usbSetZltPacket(true);
//LOG_MSG("ZLT enabled. Last chunk size: 0x%lX bytes.", data_size);
}
} else {
/* Disable ZLT if this is the first of multiple data chunks. */
if (!g_usbTransferWrittenSize)
{
usbSetZltPacket(false);
//LOG_MSG("ZLT disabled (first chunk).");
}
}
/* Send data chunk. */
if (!(ret = usbWrite(buf, data_size)))
{
LOG_MSG("Failed to write 0x%lX bytes long file data chunk from offset 0x%lX! (total size: 0x%lX).", data_size, g_usbTransferWrittenSize, \
g_usbTransferRemainingSize + g_usbTransferWrittenSize);
goto end; goto end;
} }
ret = (cmd_status->status == UsbStatusType_Success); g_usbTransferRemainingSize -= data_size;
if (!ret) usbLogStatusDetail(cmd_status->status); g_usbTransferWrittenSize += data_size;
}
/* Check if this is the last chunk. */
if (!g_usbTransferRemainingSize)
{
/* Check response from host device. */
if (!(ret = usbRead(g_usbTransferBuffer, sizeof(UsbStatus))))
{
LOG_MSG("Failed to read 0x%lX bytes long status block!", sizeof(UsbStatus));
goto end;
}
UsbStatus *cmd_status = (UsbStatus*)g_usbTransferBuffer;
if (!(ret = (cmd_status->magic == __builtin_bswap32(USB_CMD_HEADER_MAGIC))))
{
LOG_MSG("Invalid status block magic word!");
goto end;
}
if (!(ret = (cmd_status->status == UsbStatusType_Success))) usbLogStatusDetail(cmd_status->status);
}
end: end:
/* Disable ZLT if it was previously enabled. */ /* Disable ZLT if it was previously enabled. */
if (zlt_required) usbSetZltPacket(false); if (zlt_required) usbSetZltPacket(false);
/* Reset variables in case of errors. */ /* Reset variables in case of errors. */
if (!ret) if (!ret)
{ {
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0; g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
g_nspTransferMode = false; g_nspTransferMode = false;
}
} }
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
rwlockWriteUnlock(&g_usbDeviceLock);
return ret; return ret;
} }
void usbCancelFileTransfer(void) void usbCancelFileTransfer(void)
{ {
rwlockWriteLock(&g_usbDeviceLock); SCOPED_LOCK(&g_usbInterfaceMutex)
rwlockWriteLock(&(g_usbDeviceInterface.lock)); {
if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || (!g_usbTransferRemainingSize && !g_nspTransferMode)) break;
if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInit || !g_usbDeviceInterface.initialized || !g_usbHostAvailable || !g_usbSessionStarted || (!g_usbTransferRemainingSize && \
!g_nspTransferMode)) goto end; /* Reset variables right away. */
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
/* Reset variables right away. */ g_nspTransferMode = false;
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
g_nspTransferMode = false; /* Prepare command data. */
usbPrepareCommandHeader(UsbCommandType_CancelFileTransfer, 0);
/* Prepare command data. */
usbPrepareCommandHeader(UsbCommandType_CancelFileTransfer, 0); /* Send command. We don't care about the result here. */
usbSendCommand();
/* Send command. We don't care about the result here. */ }
usbSendCommand();
end:
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
rwlockWriteUnlock(&g_usbDeviceLock);
} }
bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size) bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size)
{ {
rwlockWriteLock(&g_usbDeviceLock);
rwlockWriteLock(&(g_usbDeviceInterface.lock));
bool ret = false; bool ret = false;
if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInit || !g_usbDeviceInterface.initialized || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || \ SCOPED_LOCK(&g_usbInterfaceMutex)
!g_nspTransferMode || !nsp_header || !nsp_header_size || nsp_header_size > (USB_TRANSFER_BUFFER_SIZE - sizeof(UsbCommandHeader)))
{ {
LOG_MSG("Invalid parameters!"); if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || !g_nspTransferMode || !nsp_header || !nsp_header_size || \
goto end; nsp_header_size > (USB_TRANSFER_BUFFER_SIZE - sizeof(UsbCommandHeader)))
{
LOG_MSG("Invalid parameters!");
break;
}
/* Disable NSP transfer mode right away. */
g_nspTransferMode = false;
/* Prepare command data. */
usbPrepareCommandHeader(UsbCommandType_SendNspHeader, nsp_header_size);
memcpy(g_usbTransferBuffer + sizeof(UsbCommandHeader), nsp_header, nsp_header_size);
/* Send command. */
ret = usbSendCommand();
} }
/* Disable NSP transfer mode right away. */
g_nspTransferMode = false;
/* Prepare command data. */
usbPrepareCommandHeader(UsbCommandType_SendNspHeader, nsp_header_size);
memcpy(g_usbTransferBuffer + sizeof(UsbCommandHeader), nsp_header, nsp_header_size);
/* Send command. */
ret = usbSendCommand();
end:
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
rwlockWriteUnlock(&g_usbDeviceLock);
return ret; return ret;
} }
@ -549,55 +523,57 @@ static void usbDetectionThreadFunc(void *arg)
Waiter usb_timeout_event_waiter = waiterForUEvent(&g_usbTimeoutEvent); Waiter usb_timeout_event_waiter = waiterForUEvent(&g_usbTimeoutEvent);
Waiter exit_event_waiter = waiterForUEvent(&g_usbDetectionThreadExitEvent); Waiter exit_event_waiter = waiterForUEvent(&g_usbDetectionThreadExitEvent);
bool exit_flag = false;
while(true) while(true)
{ {
/* Wait until an event is triggered. */ /* Wait until an event is triggered. */
rc = waitMulti(&idx, -1, usb_change_event_waiter, usb_timeout_event_waiter, exit_event_waiter); rc = waitMulti(&idx, -1, usb_change_event_waiter, usb_timeout_event_waiter, exit_event_waiter);
if (R_FAILED(rc)) continue; if (R_FAILED(rc)) continue;
rwlockWriteLock(&g_usbDeviceLock);
rwlockWriteLock(&(g_usbDeviceInterface.lock));
/* Exit event triggered. */ /* Exit event triggered. */
if (idx == 2) break; if (idx == 2) break;
/* Retrieve current USB connection status. */ SCOPED_LOCK(&g_usbInterfaceMutex)
/* Only proceed if we're dealing with a status change. */
g_usbHostAvailable = usbIsHostAvailable();
g_usbSessionStarted = false;
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
g_usbEndpointMaxPacketSize = 0;
/* Start an USB session if we're connected to a host device. */
/* This will essentially hang this thread and all other threads that call USB-related functions until: */
/* a) A session is successfully established. */
/* b) The console is disconnected from the USB host. */
/* c) The thread exit event is triggered. */
if (g_usbHostAvailable)
{ {
/* Wait until a session is established. */ /* Retrieve current USB connection status. */
g_usbSessionStarted = usbStartSession(); /* Only proceed if we're dealing with a status change. */
if (g_usbSessionStarted) g_usbHostAvailable = usbIsHostAvailable();
g_usbSessionStarted = false;
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
g_usbEndpointMaxPacketSize = 0;
/* Start a USB session if we're connected to a host device. */
/* This will essentially hang this thread and all other threads that call USB-related functions until: */
/* a) A session is successfully established. */
/* b) The console is disconnected from the USB host. */
/* c) The thread exit event is triggered. */
if (g_usbHostAvailable)
{ {
LOG_MSG("USB session successfully established. Endpoint max packet size: 0x%04X.", g_usbEndpointMaxPacketSize); /* Wait until a session is established. */
} else { g_usbSessionStarted = usbStartSession();
/* Check if the exit event was triggered while waiting for a session to be established. */ if (g_usbSessionStarted)
if (g_usbDetectionThreadExitFlag) break; {
LOG_MSG("USB session successfully established. Endpoint max packet size: 0x%04X.", g_usbEndpointMaxPacketSize);
} else {
/* Update exit flag. */
exit_flag = g_usbDetectionThreadExitFlag;
}
} }
} }
rwlockWriteUnlock(&(g_usbDeviceInterface.lock)); /* Check if the exit event was triggered while waiting for a session to be established. */
rwlockWriteUnlock(&g_usbDeviceLock); if (exit_flag) break;
} }
/* Close USB session if needed. */ SCOPED_LOCK(&g_usbInterfaceMutex)
if (g_usbHostAvailable && g_usbSessionStarted) usbEndSession(); {
g_usbHostAvailable = g_usbSessionStarted = g_usbDetectionThreadExitFlag = false; /* Close USB session if needed. */
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0; if (g_usbHostAvailable && g_usbSessionStarted) usbEndSession();
g_usbEndpointMaxPacketSize = 0; g_usbHostAvailable = g_usbSessionStarted = g_usbDetectionThreadExitFlag = false;
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
rwlockWriteUnlock(&(g_usbDeviceInterface.lock)); g_usbEndpointMaxPacketSize = 0;
rwlockWriteUnlock(&g_usbDeviceLock); }
threadExit(); threadExit();
} }
@ -607,7 +583,7 @@ static bool usbStartSession(void)
UsbCommandStartSession *cmd_block = NULL; UsbCommandStartSession *cmd_block = NULL;
bool ret = false; bool ret = false;
if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInit || !g_usbDeviceInterface.initialized) if (!g_usbInterfaceInit || !g_usbTransferBuffer)
{ {
LOG_MSG("Invalid parameters!"); LOG_MSG("Invalid parameters!");
goto end; goto end;
@ -630,8 +606,7 @@ static bool usbStartSession(void)
/* Get the endpoint max packet size from the response sent by the USB host. */ /* Get the endpoint max packet size from the response sent by the USB host. */
/* This is done to accurately know when and where to enable Zero Length Termination (ZLT) packets during bulk transfers. */ /* This is done to accurately know when and where to enable Zero Length Termination (ZLT) packets during bulk transfers. */
/* As much as I'd like to avoid this, usb:ds doesn't disclose information such as the exact device descriptor and/or speed used by the USB host. */ /* As much as I'd like to avoid this, usb:ds doesn't disclose information such as the exact device descriptor and/or speed used by the USB host. */
UsbStatus *cmd_status = (UsbStatus*)g_usbTransferBuffer; g_usbEndpointMaxPacketSize = ((UsbStatus*)g_usbTransferBuffer)->max_packet_size;
g_usbEndpointMaxPacketSize = cmd_status->max_packet_size;
if (g_usbEndpointMaxPacketSize != USB_FS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_HS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_SS_EP_MAX_PACKET_SIZE) if (g_usbEndpointMaxPacketSize != USB_FS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_HS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_SS_EP_MAX_PACKET_SIZE)
{ {
LOG_MSG("Invalid endpoint max packet size value received from USB host: 0x%04X.", g_usbEndpointMaxPacketSize); LOG_MSG("Invalid endpoint max packet size value received from USB host: 0x%04X.", g_usbEndpointMaxPacketSize);
@ -648,7 +623,7 @@ end:
static void usbEndSession(void) static void usbEndSession(void)
{ {
if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInit || !g_usbDeviceInterface.initialized || !g_usbHostAvailable || !g_usbSessionStarted) if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted)
{ {
LOG_MSG("Invalid parameters!"); LOG_MSG("Invalid parameters!");
return; return;
@ -792,6 +767,7 @@ NX_INLINE void usbFreeTransferBuffer(void)
static bool usbInitializeComms(void) static bool usbInitializeComms(void)
{ {
Result rc = 0; Result rc = 0;
bool ret = false, init_dev_if = false;
/* Used on HOS >= 5.0.0. */ /* Used on HOS >= 5.0.0. */
struct usb_device_descriptor device_descriptor = { struct usb_device_descriptor device_descriptor = {
@ -849,9 +825,6 @@ static bool usbInitializeComms(void)
.SerialNumber = APP_VERSION .SerialNumber = APP_VERSION
}; };
bool ret = (g_usbDeviceInterfaceInit && g_usbDeviceInterface.initialized);
if (ret) goto end;
rc = usbDsInitialize(); rc = usbDsInitialize();
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
@ -928,17 +901,8 @@ static bool usbInitializeComms(void)
if (R_FAILED(rc)) goto end; if (R_FAILED(rc)) goto end;
/* Initialize USB device interface. */ /* Initialize USB device interface. */
rwlockWriteLock(&(g_usbDeviceInterface.lock)); init_dev_if = (hosversionAtLeast(5, 0, 0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x());
rwlockWriteLock(&(g_usbDeviceInterface.lock_in)); if (!init_dev_if)
rwlockWriteLock(&(g_usbDeviceInterface.lock_out));
bool dev_iface_init = usbInitializeDeviceInterface();
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_out));
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_in));
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
if (!dev_iface_init)
{ {
LOG_MSG("Failed to initialize USB device interface!"); LOG_MSG("Failed to initialize USB device interface!");
goto end; goto end;
@ -954,7 +918,7 @@ static bool usbInitializeComms(void)
} }
} }
ret = g_usbDeviceInterfaceInit = true; ret = true;
end: end:
if (!ret) usbCloseComms(); if (!ret) usbCloseComms();
@ -965,37 +929,10 @@ end:
static void usbCloseComms(void) static void usbCloseComms(void)
{ {
usbDsExit(); usbDsExit();
g_usbDeviceInterfaceInit = false;
usbFreeDeviceInterface();
}
static void usbFreeDeviceInterface(void)
{
rwlockWriteLock(&(g_usbDeviceInterface.lock));
if (!g_usbDeviceInterface.initialized) { g_usbInterface = NULL;
rwlockWriteUnlock(&(g_usbDeviceInterface.lock)); g_usbEndpointIn = NULL;
return; g_usbEndpointOut = NULL;
}
rwlockWriteLock(&(g_usbDeviceInterface.lock_in));
rwlockWriteLock(&(g_usbDeviceInterface.lock_out));
g_usbDeviceInterface.initialized = false;
g_usbDeviceInterface.interface = NULL;
g_usbDeviceInterface.endpoint_in = NULL;
g_usbDeviceInterface.endpoint_out = NULL;
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_out));
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_in));
rwlockWriteUnlock(&(g_usbDeviceInterface.lock));
}
NX_INLINE bool usbInitializeDeviceInterface(void)
{
return (hosversionAtLeast(5, 0, 0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x());
} }
static bool usbInitializeDeviceInterface5x(void) static bool usbInitializeDeviceInterface5x(void)
@ -1040,37 +977,34 @@ static bool usbInitializeDeviceInterface5x(void)
.wBytesPerInterval = 0 .wBytesPerInterval = 0
}; };
/* Enable device interface. */
g_usbDeviceInterface.initialized = true;
/* Setup interface. */ /* Setup interface. */
rc = usbDsRegisterInterface(&(g_usbDeviceInterface.interface)); rc = usbDsRegisterInterface(&g_usbInterface);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsRegisterInterface failed! (0x%08X).", rc); LOG_MSG("usbDsRegisterInterface failed! (0x%08X).", rc);
return false; return false;
} }
interface_descriptor.bInterfaceNumber = g_usbDeviceInterface.interface->interface_index; interface_descriptor.bInterfaceNumber = g_usbInterface->interface_index;
endpoint_descriptor_in.bEndpointAddress += (interface_descriptor.bInterfaceNumber + 1); endpoint_descriptor_in.bEndpointAddress += (interface_descriptor.bInterfaceNumber + 1);
endpoint_descriptor_out.bEndpointAddress += (interface_descriptor.bInterfaceNumber + 1); endpoint_descriptor_out.bEndpointAddress += (interface_descriptor.bInterfaceNumber + 1);
/* Full Speed config (USB 1.1). */ /* Full Speed config (USB 1.1). */
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (interface).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (interface).", rc);
return false; return false;
} }
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (in endpoint).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (in endpoint).", rc);
return false; return false;
} }
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (out endpoint).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (out endpoint).", rc);
@ -1081,21 +1015,21 @@ static bool usbInitializeDeviceInterface5x(void)
endpoint_descriptor_in.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE; endpoint_descriptor_in.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE;
endpoint_descriptor_out.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE; endpoint_descriptor_out.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE;
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (interface).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (interface).", rc);
return false; return false;
} }
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (in endpoint).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (in endpoint).", rc);
return false; return false;
} }
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (out endpoint).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (out endpoint).", rc);
@ -1106,35 +1040,35 @@ static bool usbInitializeDeviceInterface5x(void)
endpoint_descriptor_in.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE; endpoint_descriptor_in.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE;
endpoint_descriptor_out.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE; endpoint_descriptor_out.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE;
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (interface).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (interface).", rc);
return false; return false;
} }
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (in endpoint).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (in endpoint).", rc);
return false; return false;
} }
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (in endpoint companion).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (in endpoint companion).", rc);
return false; return false;
} }
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (out endpoint).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (out endpoint).", rc);
return false; return false;
} }
rc = usbDsInterface_AppendConfigurationData(g_usbDeviceInterface.interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE); rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (out endpoint companion).", rc); LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (out endpoint companion).", rc);
@ -1142,21 +1076,21 @@ static bool usbInitializeDeviceInterface5x(void)
} }
/* Setup endpoints. */ /* Setup endpoints. */
rc = usbDsInterface_RegisterEndpoint(g_usbDeviceInterface.interface, &(g_usbDeviceInterface.endpoint_in), endpoint_descriptor_in.bEndpointAddress); rc = usbDsInterface_RegisterEndpoint(g_usbInterface, &g_usbEndpointIn, endpoint_descriptor_in.bEndpointAddress);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_RegisterEndpoint failed! (0x%08X) (in endpoint).", rc); LOG_MSG("usbDsInterface_RegisterEndpoint failed! (0x%08X) (in endpoint).", rc);
return false; return false;
} }
rc = usbDsInterface_RegisterEndpoint(g_usbDeviceInterface.interface, &(g_usbDeviceInterface.endpoint_out), endpoint_descriptor_out.bEndpointAddress); rc = usbDsInterface_RegisterEndpoint(g_usbInterface, &g_usbEndpointOut, endpoint_descriptor_out.bEndpointAddress);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_RegisterEndpoint failed! (0x%08X) (out endpoint).", rc); LOG_MSG("usbDsInterface_RegisterEndpoint failed! (0x%08X) (out endpoint).", rc);
return false; return false;
} }
rc = usbDsInterface_EnableInterface(g_usbDeviceInterface.interface); rc = usbDsInterface_EnableInterface(g_usbInterface);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_EnableInterface failed! (0x%08X).", rc); LOG_MSG("usbDsInterface_EnableInterface failed! (0x%08X).", rc);
@ -1199,11 +1133,8 @@ static bool usbInitializeDeviceInterface1x(void)
.bInterval = 0 .bInterval = 0
}; };
/* Enable device interface. */
g_usbDeviceInterface.initialized = true;
/* Setup interface. */ /* Setup interface. */
rc = usbDsGetDsInterface(&(g_usbDeviceInterface.interface), &interface_descriptor, "usb"); rc = usbDsGetDsInterface(&g_usbInterface, &interface_descriptor, "usb");
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsGetDsInterface failed! (0x%08X).", rc); LOG_MSG("usbDsGetDsInterface failed! (0x%08X).", rc);
@ -1211,21 +1142,21 @@ static bool usbInitializeDeviceInterface1x(void)
} }
/* Setup endpoints. */ /* Setup endpoints. */
rc = usbDsInterface_GetDsEndpoint(g_usbDeviceInterface.interface, &(g_usbDeviceInterface.endpoint_in), &endpoint_descriptor_in); rc = usbDsInterface_GetDsEndpoint(g_usbInterface, &g_usbEndpointIn, &endpoint_descriptor_in);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_GetDsEndpoint failed! (0x%08X) (in endpoint).", rc); LOG_MSG("usbDsInterface_GetDsEndpoint failed! (0x%08X) (in endpoint).", rc);
return false; return false;
} }
rc = usbDsInterface_GetDsEndpoint(g_usbDeviceInterface.interface, &(g_usbDeviceInterface.endpoint_out), &endpoint_descriptor_out); rc = usbDsInterface_GetDsEndpoint(g_usbInterface, &g_usbEndpointOut, &endpoint_descriptor_out);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_GetDsEndpoint failed! (0x%08X) (out endpoint).", rc); LOG_MSG("usbDsInterface_GetDsEndpoint failed! (0x%08X) (out endpoint).", rc);
return false; return false;
} }
rc = usbDsInterface_EnableInterface(g_usbDeviceInterface.interface); rc = usbDsInterface_EnableInterface(g_usbInterface);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
LOG_MSG("usbDsInterface_EnableInterface failed! (0x%08X).", rc); LOG_MSG("usbDsInterface_EnableInterface failed! (0x%08X).", rc);
@ -1244,25 +1175,17 @@ NX_INLINE bool usbIsHostAvailable(void)
NX_INLINE void usbSetZltPacket(bool enable) NX_INLINE void usbSetZltPacket(bool enable)
{ {
rwlockWriteLock(&(g_usbDeviceInterface.lock_in)); usbDsEndpoint_SetZlt(g_usbEndpointIn, enable);
usbDsEndpoint_SetZlt(g_usbDeviceInterface.endpoint_in, enable);
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_in));
} }
NX_INLINE bool usbRead(void *buf, u64 size) NX_INLINE bool usbRead(void *buf, u64 size)
{ {
rwlockWriteLock(&(g_usbDeviceInterface.lock_out)); return usbTransferData(buf, size, g_usbEndpointOut);
bool ret = usbTransferData(buf, size, g_usbDeviceInterface.endpoint_out);
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_out));
return ret;
} }
NX_INLINE bool usbWrite(void *buf, u64 size) NX_INLINE bool usbWrite(void *buf, u64 size)
{ {
rwlockWriteLock(&(g_usbDeviceInterface.lock_in)); return usbTransferData(buf, size, g_usbEndpointIn);
bool ret = usbTransferData(buf, size, g_usbDeviceInterface.endpoint_in);
rwlockWriteUnlock(&(g_usbDeviceInterface.lock_in));
return ret;
} }
static bool usbTransferData(void *buf, u64 size, UsbDsEndpoint *endpoint) static bool usbTransferData(void *buf, u64 size, UsbDsEndpoint *endpoint)
@ -1284,7 +1207,7 @@ static bool usbTransferData(void *buf, u64 size, UsbDsEndpoint *endpoint)
u32 urb_id = 0, transferred_size = 0; u32 urb_id = 0, transferred_size = 0;
bool thread_exit = false; bool thread_exit = false;
/* Start an USB transfer using the provided endpoint. */ /* Start a USB transfer using the provided endpoint. */
rc = usbDsEndpoint_PostBufferAsync(endpoint, buf, size, &urb_id); rc = usbDsEndpoint_PostBufferAsync(endpoint, buf, size, &urb_id);
if (R_FAILED(rc)) if (R_FAILED(rc))
{ {
@ -1295,10 +1218,10 @@ static bool usbTransferData(void *buf, u64 size, UsbDsEndpoint *endpoint)
/* Wait for the transfer to finish. */ /* Wait for the transfer to finish. */
if (g_usbSessionStarted) if (g_usbSessionStarted)
{ {
/* If the USB transfer session has already been started, then use a regular timeout value. */ /* If the USB session has already been established, then use a regular timeout value. */
rc = eventWait(&(endpoint->CompletionEvent), USB_TRANSFER_TIMEOUT * (u64)1000000000); rc = eventWait(&(endpoint->CompletionEvent), USB_TRANSFER_TIMEOUT * (u64)1000000000);
} else { } else {
/* If we're starting an USB transfer session, wait indefinitely inside a loop to let the user start the companion app. */ /* If we're starting a USB session, wait indefinitely inside a loop to let the user start the companion app. */
int idx = 0; int idx = 0;
Waiter completion_event_waiter = waiterForEvent(&(endpoint->CompletionEvent)); Waiter completion_event_waiter = waiterForEvent(&(endpoint->CompletionEvent));
Waiter exit_event_waiter = waiterForUEvent(&g_usbDetectionThreadExitEvent); Waiter exit_event_waiter = waiterForUEvent(&g_usbDetectionThreadExitEvent);

View file

@ -1,5 +1,5 @@
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2019 */ /* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */ /* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */ /* attached to the FatFs via a glue function rather than modifying it. */
@ -23,12 +23,9 @@ DSTATUS disk_status (
) )
{ {
(void)pdrv; (void)pdrv;
return 0; return 0;
} }
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
/* Inidialize a Drive */ /* Inidialize a Drive */
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
@ -38,12 +35,9 @@ DSTATUS disk_initialize (
) )
{ {
(void)pdrv; (void)pdrv;
return 0; return 0;
} }
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
/* Read Sector(s) */ /* Read Sector(s) */
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
@ -66,8 +60,6 @@ DRESULT disk_read (
return (R_SUCCEEDED(rc) ? RES_OK : RES_ERROR); return (R_SUCCEEDED(rc) ? RES_OK : RES_ERROR);
} }
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
/* Write Sector(s) */ /* Write Sector(s) */
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
@ -85,13 +77,11 @@ DRESULT disk_write (
(void)buff; (void)buff;
(void)sector; (void)sector;
(void)count; (void)count;
return RES_OK; return RES_OK;
} }
#endif #endif
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */ /* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
@ -105,6 +95,5 @@ DRESULT disk_ioctl (
(void)pdrv; (void)pdrv;
(void)cmd; (void)cmd;
(void)buff; (void)buff;
return RES_OK; return RES_OK;
} }

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
/* Sample Code of OS Dependent Functions for FatFs */ /* Sample Code of OS Dependent Functions for FatFs */
/* (C)ChaN, 2018 */ /* (C)ChaN, 2018 */
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
#include <stdlib.h> #include <stdlib.h>
@ -19,7 +19,6 @@ void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if no
return malloc(msize); /* Allocate a new memory block with POSIX API */ return malloc(msize); /* Allocate a new memory block with POSIX API */
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
/* Free a memory block */ /* Free a memory block */
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
@ -32,3 +31,136 @@ void ff_memfree (
} }
#endif #endif
#if FF_FS_REENTRANT /* Mutal exclusion */
/*------------------------------------------------------------------------*/
/* Create a Synchronization Object */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount() function to create a new
/ synchronization object for the volume, such as semaphore and mutex.
/ When a 0 is returned, the f_mount() function fails with FR_INT_ERR.
*/
//const osMutexDef_t Mutex[FF_VOLUMES]; /* Table of CMSIS-RTOS mutex */
int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object */
BYTE vol, /* Corresponding volume (logical drive number) */
FF_SYNC_t* sobj /* Pointer to return the created sync object */
)
{
/* Win32 */
*sobj = CreateMutex(NULL, FALSE, NULL);
return (int)(*sobj != INVALID_HANDLE_VALUE);
/* uITRON */
// T_CSEM csem = {TA_TPRI,1,1};
// *sobj = acre_sem(&csem);
// return (int)(*sobj > 0);
/* uC/OS-II */
// OS_ERR err;
// *sobj = OSMutexCreate(0, &err);
// return (int)(err == OS_NO_ERR);
/* FreeRTOS */
// *sobj = xSemaphoreCreateMutex();
// return (int)(*sobj != NULL);
/* CMSIS-RTOS */
// *sobj = osMutexCreate(&Mutex[vol]);
// return (int)(*sobj != NULL);
}
/*------------------------------------------------------------------------*/
/* Delete a Synchronization Object */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount() function to delete a synchronization
/ object that created with ff_cre_syncobj() function. When a 0 is returned,
/ the f_mount() function fails with FR_INT_ERR.
*/
int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to an error */
FF_SYNC_t sobj /* Sync object tied to the logical drive to be deleted */
)
{
/* Win32 */
return (int)CloseHandle(sobj);
/* uITRON */
// return (int)(del_sem(sobj) == E_OK);
/* uC/OS-II */
// OS_ERR err;
// OSMutexDel(sobj, OS_DEL_ALWAYS, &err);
// return (int)(err == OS_NO_ERR);
/* FreeRTOS */
// vSemaphoreDelete(sobj);
// return 1;
/* CMSIS-RTOS */
// return (int)(osMutexDelete(sobj) == osOK);
}
/*------------------------------------------------------------------------*/
/* Request Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on entering file functions to lock the volume.
/ When a 0 is returned, the file function fails with FR_TIMEOUT.
*/
int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a grant */
FF_SYNC_t sobj /* Sync object to wait */
)
{
/* Win32 */
return (int)(WaitForSingleObject(sobj, FF_FS_TIMEOUT) == WAIT_OBJECT_0);
/* uITRON */
// return (int)(wai_sem(sobj) == E_OK);
/* uC/OS-II */
// OS_ERR err;
// OSMutexPend(sobj, FF_FS_TIMEOUT, &err));
// return (int)(err == OS_NO_ERR);
/* FreeRTOS */
// return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE);
/* CMSIS-RTOS */
// return (int)(osMutexWait(sobj, FF_FS_TIMEOUT) == osOK);
}
/*------------------------------------------------------------------------*/
/* Release Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on leaving file functions to unlock the volume.
*/
void ff_rel_grant (
FF_SYNC_t sobj /* Sync object to be signaled */
)
{
/* Win32 */
ReleaseMutex(sobj);
/* uITRON */
// sig_sem(sobj);
/* uC/OS-II */
// OSMutexPost(sobj);
/* FreeRTOS */
// xSemaphoreGive(sobj);
/* CMSIS-RTOS */
// osMutexRelease(sobj);
}
#endif

View file

@ -28,10 +28,6 @@
#include "sample_installer_page.hpp" #include "sample_installer_page.hpp"
#include "sample_loading_page.hpp" #include "sample_loading_page.hpp"
int g_argc = 0;
char **g_argv = NULL;
const char *g_appLaunchPath = NULL;
namespace i18n = brls::i18n; // for loadTranslations() and getStr() namespace i18n = brls::i18n; // for loadTranslations() and getStr()
using namespace i18n::literals; // for _i18n using namespace i18n::literals; // for _i18n
@ -48,10 +44,7 @@ std::vector<std::string> NOTIFICATIONS = {
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
g_argc = argc; if (!utilsInitializeResources(argc, (const char**)argv))
g_argv = argv;
if (!utilsInitializeResources())
{ {
utilsCloseResources(); utilsCloseResources();
return EXIT_FAILURE; return EXIT_FAILURE;

View file

@ -1,3 +1,33 @@
todo:
log: verbosity levels
log: nxlink output for advanced users
nca: signature verification
nca: support for compressed fs sections?
nca: support for sparse sections?
usb: improve usbIsReady
title: orphan system titles
title: come up with a better way to handle gamecard titles
title: more functions for title lookup? (filters, patches / aoc, etc.)
title: more functions for content lookup? (based on id)
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
gamecard: functions to display filelist
pfs0: functions to display filelist
romfs: functions to display filelist
bktr: functions to display filelist (wrappers for romfs functions tbh)
others: config load/save using json
others: dump verification via nswdb / no-intro
others: update application feature
others: fatfs browser for emmc partitions
reminder: reminder:
list of top level functions designed to alter nca data in order of (possible) usage: list of top level functions designed to alter nca data in order of (possible) usage:
@ -48,34 +78,3 @@ minor steps to take into account:
* check if rights_id_available == true and titlekey_retrieved == false (preload handling) * check if rights_id_available == true and titlekey_retrieved == false (preload handling)
* actually, just inform the user about it - this is being handled * actually, just inform the user about it - this is being handled
todo:
nca: support for compressed fs sections?
nca: support for sparse sections?
gamecard: functions to display filelist
pfs0: functions to display filelist
romfs: functions to display filelist
bktr: functions to display filelist (wrappers for romfs functions tbh)
title: more functions for title lookup? (filters, patches / aoc, etc.)
title: more functions for content lookup? (based on id)
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
others: config load/save using json
others: dump verification via nswdb / no-intro
others: update application feature
others: fatfs browser for emmc partitions