diff --git a/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c index da6d90e..898bef9 100644 --- a/code_templates/nxdt_rw_poc.c +++ b/code_templates/nxdt_rw_poc.c @@ -1072,7 +1072,7 @@ int main(int argc, char *argv[]) break; } - svcSleepThread(10000000); // 10 ms + utilsAppletLoopDelay(); } if (!g_appletStatus) break; @@ -1383,7 +1383,7 @@ int main(int argc, char *argv[]) break; } - if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp | HidNpadButton_ZL | HidNpadButton_ZR)) svcSleepThread(40000000); // 40 ms + utilsAppletLoopDelay(); } freeNcaFsSectionsList(); @@ -1432,7 +1432,7 @@ static void utilsWaitForButtonPress(u64 flag) { utilsScanPads(); if (utilsGetButtonsDown() & flag) break; - svcSleepThread(10000000); // 10 ms + utilsAppletLoopDelay(); } } @@ -1677,7 +1677,7 @@ void updateNcaList(TitleInfo *title_info) continue; } - utilsGenerateHexStringFromData(nca_id_str, sizeof(nca_id_str), cur_content_info->content_id.c, sizeof(cur_content_info->content_id.c), false); + utilsGenerateHexString(nca_id_str, sizeof(nca_id_str), cur_content_info->content_id.c, sizeof(cur_content_info->content_id.c), false); ncmContentInfoSizeToU64(cur_content_info, &nca_size); utilsGenerateFormattedSizeString((double)nca_size, nca_size_str, sizeof(nca_size_str)); @@ -2771,7 +2771,7 @@ static bool saveNintendoSubmissionPackage(void *userdata) utilsCreateThread(&dump_thread, nspThreadFunc, &nsp_thread_data, 2); /* Wait until the background thread calculates the NSP size. */ - while(!nsp_thread_data.total_size && !nsp_thread_data.error) svcSleepThread(10000000); // 10 ms + while(!nsp_thread_data.total_size && !nsp_thread_data.error) utilsAppletLoopDelay(); if (nsp_thread_data.error) { @@ -2833,7 +2833,7 @@ static bool saveNintendoSubmissionPackage(void *userdata) consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, nsp_thread_data.total_size, percent, (now - start)); consoleRefresh(); - svcSleepThread(10000000); // 10 ms + utilsAppletLoopDelay(); } consolePrint("\nwaiting for thread to join\n"); @@ -4685,7 +4685,7 @@ static bool spanDumpThreads(ThreadFunc read_func, ThreadFunc write_func, void *a consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_thread_data->total_size, percent, (now - start)); consoleRefresh(); - svcSleepThread(10000000); // 10 ms + utilsAppletLoopDelay(); } consolePrint("\nwaiting for threads to join\n"); diff --git a/host/README.md b/host/README.md index 488b02d..f4e57a6 100644 --- a/host/README.md +++ b/host/README.md @@ -1,9 +1,11 @@ # nxdumptool USB Application Binary Interface (ABI) Technical Specification -This Markdown document aims to explain the technical details behind the ABI used by nxdumptool to communicate with a USB host device connected to the console. As of this writing (April 19th, 2023), the current ABI version is `1.1`. +This Markdown document aims to explain the technical details behind the ABI used by nxdumptool to communicate with a USB host device connected to the console. As of this writing (October 22nd, 2023), the current ABI version is `1.1`. In order to avoid unnecessary clutter, this document assumes the reader is already familiar with homebrew launching on the Nintendo Switch, as well as USB concepts such as device/configuration/interface/endpoint descriptors and bulk mode transfers. Shall this not be the case, a small list of helpful resources is available at the end of this document. +Unless stated otherwise, the reader must assume all integer fields in the documented structs follow a little-endian (LE) order. + ## Table of contents * [USB device interface details](#usb-device-interface-details). @@ -16,7 +18,6 @@ In order to avoid unnecessary clutter, this document assumes the reader is alrea * [Command blocks](#command-blocks). * [StartSession](#startsession). * [SendFileProperties](#sendfileproperties). - * [Zero Length Termination (ZLT)](#zero-length-termination-zlt). * [CancelFileTransfer](#cancelfiletransfer). * [SendNspHeader](#sendnspheader). * [EndSession](#endsession). @@ -24,6 +25,7 @@ In order to avoid unnecessary clutter, this document assumes the reader is alrea * [Status codes](#status-codes). * [NSP transfer mode](#nsp-transfer-mode). * [Why is there such thing as a 'NSP transfer mode'?](#why-is-there-such-thing-as-a-nsp-transfer-mode) + * [Zero Length Termination (ZLT)](#zero-length-termination-zlt). * [Additional resources](#additional-resources). ## USB device interface details @@ -56,6 +58,8 @@ Right after launching nxdumptool on the target Nintendo Switch, the application Communication is performed through the bulk input and output endpoints using 5-second timeouts. +Verifying the product string is not required at this moment -- this is because PoC builds of the rewrite branch use a different `APP_TITLE` string. + ## USB driver A USB driver is needed to actually communicate to the target console running nxdumptool. @@ -68,7 +72,7 @@ A package manager can be used to install [libusb](https://libusb.info), which in A tool such as [Zadig](https://zadig.akeo.ie) must be used to manually install a USB driver for the target console. -Even though it's possible to use the `WinUSB` driver, we suggest to use `libusbK` instead - the provided Python script in this directory depends on [PyUSB](https://github.com/pyusb/pyusb), which only provides a backend for `libusb` devices. If you intend to write your own `WinUSB`-based ABI host implementation for Windows based on this document, you may be able to use that driver. +Even though it's possible to use the `WinUSB` driver, we suggest to use `libusbK` instead -- the provided Python script in this directory depends on [PyUSB](https://github.com/pyusb/pyusb), which only provides a backend for `libusb` devices. If you intend to write your own `WinUSB`-based ABI host implementation for Windows based on this document, you may be able to use that driver. Furthermore, even though it's possible for USB devices to work right out of the box using [Microsoft OS descriptors](https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors), the `usb:ds` API available to homebrew applications on the Nintendo Switch doesn't provide any way to set them. Thus, it's not possible to interact with the target console without installing a USB driver first. @@ -76,32 +80,34 @@ Furthermore, even though it's possible for USB devices to work right out of the The USB host device essentially acts as a storage server for nxdumptool. This means all commands are initially issued by the target console, leading to data transfer stages for which status responses are expected to be sent by the USB host device. -This intends to minimize the overhead on the USB host device by letting nxdumptool take care of the full dump process - the host only needs to take care of storing the received data. This also heavily simplifies the work required to write ABI host implementations from scratch, regardless of the programming language being used. +This intends to minimize the overhead on the USB host device by letting nxdumptool take care of the full dump process -- the host only needs to take care of storing the received data. This also heavily simplifies the work required to write ABI host implementations from scratch, regardless of the programming language being used. Command handling can be broken down in three different transfer stages: command header (from nxdumptool), command block (from nxdumptool) and status response (from USB host). Certain commands may lead to an additional data transfer stage after the status response is received from the USB host device. ### Command header -| Offset | Size | Description | -|:------:|:----:|------------------------------| -| 0x00 | 0x04 | Magic word (`NXDT`). | -| 0x04 | 0x04 | Command ID (LE u32). | -| 0x08 | 0x04 | Command block size (LE u32). | -| 0x0C | 0x04 | Reserved. | +Size: 0x10 bytes. -While handling ABI commands, nxdumptool first issues the command header - this way, the USB host device knows both the command ID and the command block size before attempting to receive the command block. +| Offset | Size | Type | Description | +|--------|------|--------------|------------------------------------------------| +| 0x00 | 0x04 | `uint32_t` | Magic word (`NXDT`) (`0x5444584E`). | +| 0x04 | 0x04 | `uint32_t` | [Command ID](#command-ids). | +| 0x08 | 0x04 | `uint32_t` | Command block size. | +| 0x0C | 0x04 | `uint8_t[4]` | Reserved. | -Certain commands yield no command block at all, leading to a command block size of zero - this is considered defined behaviour. Nonetheless, a status response is still expected to be sent by the USB host. +While handling ABI commands, nxdumptool first issues the command header -- this way, the USB host device knows both the command ID and the command block size before attempting to receive the command block. + +Certain commands yield no command block at all, leading to a command block size of zero -- this is considered defined behaviour. Nonetheless, a status response is still expected to be sent by the USB host. #### Command IDs -| Value | Name | Description | -|:-----:|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0 | StartSession. | Starts a USB session between the target console and the USB host device. | -| 1 | SendFileProperties. | Sends file metadata and starts a data transfer process. | -| 2 | CancelFileTransfer. | Cancels an ongoing data transfer process started by a previously issued SendFileProperties command. | -| 3 | SendNspHeader. | Sends the `PFS0` header from a Nintendo Submission Package (NSP). Only issued under NSP transfer mode, and after the data for all NSP file entries has been transferred. | -| 4 | EndSession. | Ends a previously stablished USB session between the target console and the USB host device. | +| Value | Name | Description | +|-------|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| 0 | [`StartSession`](#startsession) | Starts a USB session between the target console and the USB host device. | +| 1 | [`SendFileProperties`](#sendfileproperties) | Sends file metadata and starts a data transfer process. | +| 2 | [`CancelFileTransfer`](#cancelfiletransfer) | Cancels an ongoing data transfer process started by a previously issued [`SendFileProperties`](#sendfileproperties) command. | +| 3 | [`SendNspHeader`](#sendnspheader) | Sends the `PFS0` header from a Nintendo Submission Package (NSP). Only issued under [NSP transfer mode](#nsp-transfer-mode). | +| 4 | [`EndSession`](#endsession) | Ends a previously stablished USB session between the target console and the USB host device. | ### Command blocks @@ -109,42 +115,44 @@ All commands, with the exception of `CancelFileTransfer` and `EndSession`, yield #### StartSession -| Offset | Size | Description | -|:------:|------|---------------------------------------------------------------------| -| 0x00 | 0x01 | nxdumptool version (major). | -| 0x01 | 0x01 | nxdumptool version (minor). | -| 0x02 | 0x01 | nxdumptool version (micro). | -| 0x03 | 0x01 | nxdumptool USB ABI version (high nibble: major, low nibble: minor). | -| 0x04 | 0x08 | Git commit hash (NULL terminated string). | -| 0x0C | 0x04 | Reserved. | +Size: 0x10 bytes. + +| Offset | Size | Type | Description | +|--------|------|--------------|---------------------------------------------------------------------| +| 0x00 | 0x01 | `uint8_t` | nxdumptool version (major). | +| 0x01 | 0x01 | `uint8_t` | nxdumptool version (minor). | +| 0x02 | 0x01 | `uint8_t` | nxdumptool version (micro). | +| 0x03 | 0x01 | `uint8_t` | nxdumptool USB ABI version (high nibble: major, low nibble: minor). | +| 0x04 | 0x08 | `char[8]` | Git commit hash (NULL-terminated string). | +| 0x0C | 0x04 | `uint8_t[4]` | Reserved. | This is the first USB command issued by nxdumptool upon connection to a USB host device. If it succeeds, further USB commands may be sent. #### SendFileProperties -| Offset | Size | Description | -|:------:|:-----:|--------------------------------------------------| -| 0x000 | 0x08 | File size (LE u64). | -| 0x008 | 0x04 | Filename length (LE u32). | -| 0x00C | 0x04 | NSP header size (LE u32). | -| 0x010 | 0x301 | UTF-8 encoded filename (NULL terminated string). | -| 0x311 | 0x0F | Reserved. | +Size: 0x320 bytes. -Sent right before starting a file transfer. If it succeeds, file data will be transferred using 8 MiB (0x800000) chunks. The last chunk will be truncated, if needed. +| Offset | Size | Type | Description | +|--------|-------|---------------|----------------------------------------------| +| 0x000 | 0x08 | `uint64_t` | File size. | +| 0x008 | 0x04 | `uint32_t` | Path length. | +| 0x00C | 0x04 | `uint32_t` | [NSP header size](#nsp-transfer-mode). | +| 0x010 | 0x301 | `char[769]` | UTF-8 encoded path (NULL-terminated string). | +| 0x311 | 0x0F | `uint8_t[15]` | Reserved. | + +Sent right before starting a file transfer. If it succeeds, a data transfer stage will take place using 8 MiB (0x800000) chunks. If needed, the last chunk will be truncated. A status response is expected from the USB host right after receiving this command block, which is also right before starting the file data transfer stage. Furthermore, an additional status response is expected right after the last file data chunk has been sent. -In the case of extracted RomFS transfers, the `filename` field may actually hold an absolute filepath - this will always start with a `/`. The USB host is free to decide how to handle this (e.g. create full directory tree, etc.). +The `path` field uses forward slashes (`/`) as separators, and it will always begin with one. Its contents represent a relative path (e.g. `/NSP/Doki Doki Literature Club Plus 1.0.3 [010086901543E800][v196608][UPD].nsp`) generated by nxdumptool for any of its output storage devices, which is usually appended to an actual output directory path (e.g. `sdmc:/switch/nxdumptool`). -##### Zero Length Termination (ZLT) +Illegal Windows filesystem characters (`\`, `/`, `:`, `*`, `?`, `"`, `<`, `>`, `|`) are replaced by underscores (`_`) in each path element by nxdumptool itself before sending the command block. -As per USB bulk transfer specification, when a USB host/device receives a data packet smaller than the endpoint max packet size, it shall consider the transfer is complete and no more data packets are left. This is called a transaction completion mechanism. +Furthermore, the USB host is free to decide how to handle the relative path (e.g. create full directory tree in a user-defined output directory, entirely disregard the path and only keep the filename, etc.). -However, if the last data chunk is aligned to the endpoint max packet size, an alternate completion mechanism is needed - this is where Zero Length Termination (ZLT) packets come into play. If this condition is met, the USB host device should expect a single ZLT packet from nxdumptool right after the last data chunk has been transferred. +If the last chunk size from the data transfer stage is aligned to the endpoint max packet size, the USB host should expect a [ZLT packet](#zero-length-termination-zlt). -If no ZLT packet were issued, the USB stack from the host device wouldn't be capable of knowing the ongoing transfer has been completed, making it expect further data to be sent by the target console - which in turn leads to a timeout error on the USB host side. Furthermore, if the ZLT packet is left unhandled by the USB host device, a timeout error will be raised on the target console's side. - -Most USB backend implementations require the program to provide a bigger transfer length (+1 byte at least) if a ZLT packet is to be expected. This should be more than enough. +Finally, it should be noted that it's possible for the `filesize` field to be zero, in which case the host device shall only create the file and send a single status response right away. #### CancelFileTransfer @@ -160,6 +168,8 @@ Variable length. The command block size from the command header represents the N If the NSP header size is aligned to the endpoint max packet size, the USB host should expect a [ZLT packet](#zero-length-termination-zlt). +For more information, read the [NSP transfer mode](#nsp-transfer-mode) section of this document. + #### EndSession Yields no command block. Expects a status response, just like the rest of the commands. @@ -168,24 +178,26 @@ This command is only issued while exiting nxdumptool, as long as the target cons ### Status response -| Offset | Size | Description | -|:------:|:----:|------------------------------------| -| 0x00 | 0x04 | Magic word (`NXDT`). | -| 0x04 | 0x04 | Status code (LE u32). | -| 0x08 | 0x02 | Endpoint max packet size (LE u16). | -| 0x0A | 0x06 | Reserved. | +Size: 0x10 bytes. + +| Offset | Size | Type | Description | +|--------|------|--------------|-------------------------------------| +| 0x00 | 0x04 | `uint32_t` | Magic word (`NXDT`) (`0x5444584E`). | +| 0x04 | 0x04 | `uint32_t` | [Status code](#status-codes). | +| 0x08 | 0x02 | `uint16_t` | Endpoint max packet size. | +| 0x0A | 0x06 | `uint8_t[6]` | Reserved. | Status responses are expected by nxdumptool at certain points throughout the command handling steps: -* Right after receiving a command header and/or command block. +* Right after receiving a command header and/or command block (depending on the command ID). * Right after receiving the last file data chunk from a [SendFileProperties](#sendfileproperties) command. -The endpoint max packet size must be sent back to the target console using status responses because `usb:ds` API's `GetUsbDeviceSpeed` cmd is only available under Horizon OS 8.0.0+ -- and we definitely want to provide USB communication support under lower versions. +The endpoint max packet size must be sent back to the target console using status responses because `usb:ds` API's `GetUsbDeviceSpeed` cmd is only available under Horizon OS 8.0.0+. We want to provide USB communication support under lower versions, even if it means we have to resort to measures like this one. #### Status codes | Value | Description | -|:-----:|------------------------------------------------------------------| +|-------|------------------------------------------------------------------| | 0 | Success. | | 1 | Invalid command size. Reserved for internal nxdumptool usage. | | 2 | Failed to write command. Reserved for internal nxdumptool usage. | @@ -210,7 +222,17 @@ Finally, the USB host will receive a [SendNspHeader](#sendnspheader) command wit This is because the `PFS0` header from NSPs holds the filenames for all file entries written into the package, which are mostly [Nintendo Content Archives (NCA)](https://switchbrew.org/wiki/NCA). -NCA filenames represent the first half of the NCA SHA-256 checksum, in lowercase. This fact alone makes it impossible to send a NSP header right from the beginning - SHA-256 checksums are calculated by nxdumptool while dumping each NCA. +NCA filenames represent the first half of the NCA SHA-256 checksum, in lowercase. This fact alone makes it impossible to send a NSP header right from the beginning -- SHA-256 checksums are calculated by nxdumptool while dumping each NCA. + +#### Zero Length Termination (ZLT) + +As per USB bulk transfer specification, when a USB host/device receives a data packet smaller than the endpoint max packet size, it shall consider the transfer is complete and no more data packets are left. This is called a transaction completion mechanism. + +However, if the last data chunk is aligned to the endpoint max packet size, an alternate completion mechanism is needed -- this is where Zero Length Termination (ZLT) packets come into play. If this condition is met, the USB host device should expect a single ZLT packet from nxdumptool right after the last data chunk has been transferred. + +If no ZLT packet were issued, the USB stack from the host device wouldn't be capable of knowing the ongoing transfer has been completed, making it expect further data to be sent by the target console -- which in turn leads to a timeout error on the USB host side. Furthermore, if the ZLT packet is left unhandled by the USB host device, a timeout error will be raised on the target console's side. + +Most USB backend implementations require the host application to provide a bigger read size (+1 byte at least) if a ZLT packet is to be expected from the connected device. This should be more than enough. ## Additional resources diff --git a/include/core/gamecard.h b/include/core/gamecard.h index 50114f5..d6cb7af 100644 --- a/include/core/gamecard.h +++ b/include/core/gamecard.h @@ -191,8 +191,7 @@ typedef struct { u8 reserved_1[0x3]; u64 upp_hash; ///< Checksum for the update partition. The exact way it's calculated is currently unknown. u64 upp_id; ///< Must match GAMECARD_UPDATE_TID. - u8 reserved_2[0x28]; - u8 unknown[0x10]; ///< Unknown purpose. It's not zeroed out in recent (2021+?) gamecards. + u8 reserved_2[0x38]; } GameCardInfo; NXDT_ASSERT(GameCardInfo, 0x70); diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index 696317a..d8305cc 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -121,9 +121,16 @@ void utilsReplaceIllegalCharacters(char *str, bool ascii_only); /// Trims whitespace characters from the provided string. void utilsTrimString(char *str); -/// Generates a hex string representation of the binary data stored in 'src' and stores it in 'dst'. +/// Generates a NULL-terminated hex string representation of the binary data in 'src' and stores it in 'dst'. /// If 'uppercase' is true, uppercase characters will be used to generate the hex string. Otherwise, lowercase characters will be used. -void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, size_t src_size, bool uppercase); +void utilsGenerateHexString(char *dst, size_t dst_size, const void *src, size_t src_size, bool uppercase); + +/// Parses the hex string in 'src' and stores its binary representation in 'dst'. +/// 'src' must match the regex /^(?:[A-Fa-f0-9]{2})+$/. +/// 'src_size' may be zero, in which case strlen() will be used to determine the length of 'src'. Furthermore, 'src_size' must always be a multiple of 2. +/// 'dst_size' must be at least 'src_size / 2'. +/// Returns false if there's an error validating input arguments. +bool utilsParseHexString(void *dst, size_t dst_size, const char *src, size_t src_size); /// Formats the provided 'size' value to a human-readable size string and stores it in 'dst'. void utilsGenerateFormattedSizeString(double size, char *dst, size_t dst_size); @@ -194,6 +201,12 @@ NX_INLINE void utilsSleep(u64 seconds) if (seconds) svcSleepThread(seconds * (u64)1000000000); } +/// Introduces a 33.33 milliseconds delay. Suitable to avoid hitting 100% CPU core usage in appletMainLoop() loops. +NX_INLINE void utilsAppletLoopDelay(void) +{ + svcSleepThread(THIRTY_FPS_DELAY); +} + /// Wrappers used in scoped locks. NX_INLINE UtilsScopedLock utilsLockScope(Mutex *mtx) { diff --git a/include/defines.h b/include/defines.h index 7e55022..5e564be 100644 --- a/include/defines.h +++ b/include/defines.h @@ -60,6 +60,8 @@ /* Global constants used throughout the application. */ +#define THIRTY_FPS_DELAY (u64)33333333 /* 1 / 30 = 33.33 milliseconds. */ + #define FS_SYSMODULE_TID (u64)0x0100000000000000 #define BOOT_SYSMODULE_TID (u64)0x0100000000000005 #define SPL_SYSMODULE_TID (u64)0x0100000000000028 @@ -78,7 +80,6 @@ #define APP_BASE_PATH HBMENU_BASE_PATH APP_TITLE "/" #define GAMECARD_PATH APP_BASE_PATH "Gamecard/" -#define CERT_PATH APP_BASE_PATH "Certificate/" #define HFS_PATH APP_BASE_PATH "HFS/" #define NSP_PATH APP_BASE_PATH "NSP/" #define TICKET_PATH APP_BASE_PATH "Ticket/" diff --git a/source/core/cert.c b/source/core/cert.c index 4bf5cd4..b345770 100644 --- a/source/core/cert.c +++ b/source/core/cert.c @@ -140,7 +140,7 @@ u8 *certRetrieveRawCertificateChainFromGameCardByRightsId(const FsRightsId *id, bool success = false; /* Generate certificate chain filename. */ - utilsGenerateHexStringFromData(raw_chain_filename, sizeof(raw_chain_filename), id->c, sizeof(id->c), false); + utilsGenerateHexString(raw_chain_filename, sizeof(raw_chain_filename), id->c, sizeof(id->c), false); strcat(raw_chain_filename, ".cert"); /* Get certificate chain entry info. */ diff --git a/source/core/cnmt.c b/source/core/cnmt.c index 09444f4..38499f4 100644 --- a/source/core/cnmt.c +++ b/source/core/cnmt.c @@ -445,7 +445,7 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_ cur_nca_ctx->id_offset)) goto end; } - utilsGenerateHexStringFromData(digest_str, sizeof(digest_str), cnmt_ctx->digest, CNMT_DIGEST_SIZE, false); + utilsGenerateHexString(digest_str, sizeof(digest_str), cnmt_ctx->digest, CNMT_DIGEST_SIZE, false); /* ContentMeta, Digest, KeyGenerationMin, KeepGeneration and KeepGenerationSpecified. */ if (!CNMT_ADD_FMT_STR(" \n" \ diff --git a/source/core/gamecard.c b/source/core/gamecard.c index 66c1b24..8d4de3b 100644 --- a/source/core/gamecard.c +++ b/source/core/gamecard.c @@ -28,7 +28,7 @@ #define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB. */ -#define GAMECARD_ACCESS_WAIT_TIME 3 /* Seconds. */ +#define GAMECARD_ACCESS_DELAY 3 /* Seconds. */ #define GAMECARD_UNUSED_AREA_BLOCK_SIZE 0x24 #define GAMECARD_UNUSED_AREA_SIZE(x) (((x) / GAMECARD_PAGE_SIZE) * GAMECARD_UNUSED_AREA_BLOCK_SIZE) @@ -692,7 +692,7 @@ static void gamecardDetectionThreadFunc(void *arg) if (gamecardIsInserted()) { /* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules. */ - utilsSleep(GAMECARD_ACCESS_WAIT_TIME); + utilsSleep(GAMECARD_ACCESS_DELAY); /* Load gamecard info. */ gamecardLoadInfo(); diff --git a/source/core/keys.c b/source/core/keys.c index 9168e6b..9b7312e 100644 --- a/source/core/keys.c +++ b/source/core/keys.c @@ -87,8 +87,7 @@ NXDT_ASSERT(EticketRsaDeviceKey, 0x240); static bool keysIsKeyEmpty(const void *key); static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **value); -static char keysConvertHexDigitToBinary(char c); -static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size); +static bool keysParseHexKey(u8 *out, size_t out_size, const char *key, const char *value); static bool keysReadKeysFromFile(void); static bool keysDeriveMasterKeys(void); @@ -519,47 +518,18 @@ end: return ret; } -static char keysConvertHexDigitToBinary(char c) +static bool keysParseHexKey(u8 *out, size_t out_size, const char *key, const char *value) { - if ('a' <= c && c <= 'f') return (c - 'a' + 0xA); - if ('A' <= c && c <= 'F') return (c - 'A' + 0xA); - if ('0' <= c && c <= '9') return (c - '0'); - return 'z'; -} - -static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size) -{ - u32 hex_str_len = (2 * size); - size_t value_len = 0; - - if (!out || !key || !*key || !value || !(value_len = strlen(value)) || !size) + if (!out || !out_size || !key || !*key || !value || !*value) { LOG_MSG_ERROR("Invalid parameters!"); return false; } - if (value_len != hex_str_len) - { - LOG_MSG_ERROR("Key \"%s\" must be %u hex digits long!", key, hex_str_len); - return false; - } + bool success = utilsParseHexString(out, out_size, value, 0); + if (!success) LOG_MSG_ERROR("Failed to parse key \"%s\"!", key); - memset(out, 0, size); - - for(u32 i = 0; i < hex_str_len; i++) - { - char val = keysConvertHexDigitToBinary(value[i]); - if (val == 'z') - { - LOG_MSG_ERROR("Invalid hex character in key \"%s\" at position %u!", key, i); - return false; - } - - if ((i & 1) == 0) val <<= 4; - out[i >> 1] |= val; - } - - return true; + return success; } static bool keysReadKeysFromFile(void) @@ -584,7 +554,7 @@ static bool keysReadKeysFromFile(void) } #define PARSE_HEX_KEY(name, out, decl) \ - if (!strcasecmp(key, name) && keysParseHexKey(out, key, value, sizeof(out))) { \ + if (!strcasecmp(key, name) && keysParseHexKey(out, sizeof(out), key, value)) { \ key_count++; \ decl; \ } diff --git a/source/core/nacp.c b/source/core/nacp.c index 309269c..f195bd8 100644 --- a/source/core/nacp.c +++ b/source/core/nacp.c @@ -618,7 +618,7 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir sha256CalculateHash(icon_hash, icon_ctx->icon_data, icon_ctx->icon_size); /* Generate icon hash string. Only the first half from the hash is used. */ - utilsGenerateHexStringFromData(icon_hash_str, sizeof(icon_hash_str), icon_hash, sizeof(icon_hash) / 2, false); + utilsGenerateHexString(icon_hash_str, sizeof(icon_hash_str), icon_hash, sizeof(icon_hash) / 2, false); /* Add XML element. */ if (!NACP_ADD_FMT_STR_T1(" \n" \ @@ -726,7 +726,7 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir if (!NACP_ADD_FMT_STR_T1(" \n")) goto end; /* SendGroupConfiguration. */ - utilsGenerateHexStringFromData(key_str, sizeof(key_str), ndcc->send_group_configuration.key, sizeof(ndcc->send_group_configuration.key), false); + utilsGenerateHexString(key_str, sizeof(key_str), ndcc->send_group_configuration.key, sizeof(ndcc->send_group_configuration.key), false); if (!NACP_ADD_FMT_STR_T1(" \n" \ " 0x%016lx\n" \ @@ -740,7 +740,7 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir { NacpApplicationNeighborDetectionGroupConfiguration *rgc = &(ndcc->receivable_group_configurations[i]); - utilsGenerateHexStringFromData(key_str, sizeof(key_str), rgc->key, sizeof(rgc->key), false); + utilsGenerateHexString(key_str, sizeof(key_str), rgc->key, sizeof(rgc->key), false); if (!NACP_ADD_FMT_STR_T1(" \n" \ " 0x%016lx\n" \ diff --git a/source/core/nca.c b/source/core/nca.c index 1f9820b..fd0e5dd 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -200,9 +200,9 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, out->title_type = meta_key->type; memcpy(&(out->content_id), &(content_info->content_id), sizeof(NcmContentId)); - utilsGenerateHexStringFromData(out->content_id_str, sizeof(out->content_id_str), out->content_id.c, sizeof(out->content_id.c), false); + utilsGenerateHexString(out->content_id_str, sizeof(out->content_id_str), out->content_id.c, sizeof(out->content_id.c), false); - utilsGenerateHexStringFromData(out->hash_str, sizeof(out->hash_str), out->hash, sizeof(out->hash), false); /* Placeholder, needs to be manually calculated. */ + utilsGenerateHexString(out->hash_str, sizeof(out->hash_str), out->hash, sizeof(out->hash), false); /* Placeholder, needs to be manually calculated. */ out->content_type = content_info->content_type; out->id_offset = content_info->id_offset; @@ -537,11 +537,11 @@ void ncaUpdateContentIdAndHash(NcaContext *ctx, u8 hash[SHA256_HASH_SIZE]) /* Update content ID. */ memcpy(ctx->content_id.c, hash, sizeof(ctx->content_id.c)); - utilsGenerateHexStringFromData(ctx->content_id_str, sizeof(ctx->content_id_str), ctx->content_id.c, sizeof(ctx->content_id.c), false); + utilsGenerateHexString(ctx->content_id_str, sizeof(ctx->content_id_str), ctx->content_id.c, sizeof(ctx->content_id.c), false); /* Update content hash. */ memcpy(ctx->hash, hash, sizeof(ctx->hash)); - utilsGenerateHexStringFromData(ctx->hash_str, sizeof(ctx->hash_str), ctx->hash, sizeof(ctx->hash), false); + utilsGenerateHexString(ctx->hash_str, sizeof(ctx->hash_str), ctx->hash, sizeof(ctx->hash), false); } const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx) diff --git a/source/core/nso.c b/source/core/nso.c index b5a6992..8cf6799 100644 --- a/source/core/nso.c +++ b/source/core/nso.c @@ -233,7 +233,7 @@ static u8 *nsoGetRodataSegment(NsoContext *nso_ctx) /* Read .rodata segment data. */ if (!pfsReadEntryData(nso_ctx->pfs_ctx, nso_ctx->pfs_entry, rodata_read_ptr, rodata_read_size, nso_ctx->nso_header.rodata_segment_info.file_offset)) { - LOG_MSG_ERROR("Failed to read .rodata segment in NRO \"%s\"!", nso_ctx->nso_filename); + LOG_MSG_ERROR("Failed to read .rodata segment in NSO \"%s\"!", nso_ctx->nso_filename); goto end; } @@ -243,7 +243,7 @@ static u8 *nsoGetRodataSegment(NsoContext *nso_ctx) if ((lz4_res = LZ4_decompress_safe((char*)rodata_read_ptr, (char*)rodata_buf, (int)nso_ctx->nso_header.rodata_file_size, (int)rodata_buf_size)) != \ (int)nso_ctx->nso_header.rodata_segment_info.size) { - LOG_MSG_ERROR("LZ4 decompression failed for NRO \"%s\"! (%d).", nso_ctx->nso_filename, lz4_res); + LOG_MSG_ERROR("LZ4 decompression failed for NSO \"%s\"! (%d).", nso_ctx->nso_filename, lz4_res); goto end; } } @@ -254,7 +254,7 @@ static u8 *nsoGetRodataSegment(NsoContext *nso_ctx) sha256CalculateHash(rodata_hash, rodata_buf, nso_ctx->nso_header.rodata_segment_info.size); if (memcmp(rodata_hash, nso_ctx->nso_header.rodata_segment_hash, SHA256_HASH_SIZE) != 0) { - LOG_MSG_ERROR(".rodata segment checksum mismatch for NRO \"%s\"!", nso_ctx->nso_filename); + LOG_MSG_ERROR(".rodata segment checksum mismatch for NSO \"%s\"!", nso_ctx->nso_filename); goto end; } } diff --git a/source/core/nxdt_log.c b/source/core/nxdt_log.c index c78a45c..52cd90e 100644 --- a/source/core/nxdt_log.c +++ b/source/core/nxdt_log.c @@ -149,7 +149,7 @@ __attribute__((format(printf, 7, 8))) void logWriteBinaryDataToLogFile(const voi if (!data_str) goto end; /* Generate hex string representation. */ - utilsGenerateHexStringFromData(data_str, data_str_size, data, data_size, true); + utilsGenerateHexString(data_str, data_str_size, data, data_size, true); strcat(data_str, CRLF); SCOPED_LOCK(&g_logMutex) diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 6c456e5..1db1595 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -82,7 +82,6 @@ static const char *g_outputDirs[] = { HBMENU_BASE_PATH, APP_BASE_PATH, GAMECARD_PATH, - CERT_PATH, HFS_PATH, NSP_PATH, TICKET_PATH, @@ -114,6 +113,8 @@ static void utilsChangeHomeButtonBlockStatus(bool block); static size_t utilsGetUtf8StringLimit(const char *str, size_t str_size, size_t byte_limit); +static char utilsConvertHexDigitToBinary(char c); + bool utilsInitializeResources(const int program_argc, const char **program_argv) { Result rc = 0; @@ -503,9 +504,12 @@ void utilsJoinThread(Thread *thread) __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...) { + bool use_log = false; + SCOPED_LOCK(&g_resourcesMutex) use_log = g_resourcesInit; + if (!dst || !dst_size || !fmt || !*fmt) { - LOG_MSG_ERROR("Invalid parameters!"); + if (use_log) LOG_MSG_ERROR("Invalid parameters!"); return false; } @@ -522,7 +526,7 @@ __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(ch /* Sanity check. */ if (dst_cur_size && dst_str_len >= dst_cur_size) { - LOG_MSG_ERROR("String length is equal to or greater than the provided buffer size! (0x%lX >= 0x%lX).", dst_str_len, dst_cur_size); + if (use_log) LOG_MSG_ERROR("String length is equal to or greater than the provided buffer size! (0x%lX >= 0x%lX).", dst_str_len, dst_cur_size); return false; } @@ -532,7 +536,7 @@ __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(ch formatted_str_len = vsnprintf(NULL, 0, fmt, args); if (formatted_str_len <= 0) { - LOG_MSG_ERROR("Failed to retrieve formatted string length!"); + if (use_log) LOG_MSG_ERROR("Failed to retrieve formatted string length!"); goto end; } @@ -547,7 +551,7 @@ __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(ch tmp_str = realloc(dst_ptr, dst_cur_size); if (!tmp_str) { - LOG_MSG_ERROR("Failed to resize buffer to 0x%lX byte(s).", dst_cur_size); + if (use_log) LOG_MSG_ERROR("Failed to resize buffer to 0x%lX byte(s).", dst_cur_size); goto end; } @@ -626,7 +630,7 @@ void utilsTrimString(char *str) if (start != str) memmove(str, start, end - start + 1); } -void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, size_t src_size, bool uppercase) +void utilsGenerateHexString(char *dst, size_t dst_size, const void *src, size_t src_size, bool uppercase) { if (!src || !src_size || !dst || dst_size < ((src_size * 2) + 1)) return; @@ -645,6 +649,36 @@ void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, dst[j] = '\0'; } +bool utilsParseHexString(void *dst, size_t dst_size, const char *src, size_t src_size) +{ + u8 *dst_u8 = (u8*)dst; + bool success = true; + + if (!dst || !dst_size || !src || !*src || (!src_size && !(src_size = strlen(src))) || (src_size % 2) != 0 || dst_size < (src_size / 2)) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + memset(dst, 0, dst_size); + + for(size_t i = 0; i < src_size; i++) + { + char val = utilsConvertHexDigitToBinary(src[i]); + if (val == 'z') + { + LOG_MSG_ERROR("Invalid hex character in string \"%s\" at position %lu!", src, i); + success = false; + break; + } + + if ((i & 1) == 0) val <<= 4; + dst_u8[i >> 1] |= val; + } + + return success; +} + void utilsGenerateFormattedSizeString(double size, char *dst, size_t dst_size) { if (!dst || dst_size < 2) return; @@ -657,13 +691,8 @@ void utilsGenerateFormattedSizeString(double size, char *dst, size_t dst_size) size /= pow(1024.0, i); - if (i == 0) - { - /* Don't display decimal places if we're dealing with plain bytes. */ - snprintf(dst, dst_size, "%.0f %s", size, g_sizeSuffixes[i]); - } else { - snprintf(dst, dst_size, "%.2f %s", size, g_sizeSuffixes[i]); - } + /* Don't display decimal places if we're dealing with plain bytes. */ + snprintf(dst, dst_size, "%.*f %s", i == 0 ? 0 : 2, size, g_sizeSuffixes[i]); break; } @@ -1001,6 +1030,7 @@ void utilsPrintConsoleError(const char *msg) { padUpdate(&pad); if (padGetButtonsDown(&pad) & flag) break; + utilsAppletLoopDelay(); } /* Deinitialize console output. */ @@ -1271,13 +1301,6 @@ static void utilsChangeHomeButtonBlockStatus(bool block) } } -NX_INLINE void utilsCloseFileDescriptor(int *fd) -{ - if (!fd || *fd < 0) return; - close(*fd); - *fd = -1; -} - static size_t utilsGetUtf8StringLimit(const char *str, size_t str_size, size_t byte_limit) { if (!str || !*str || !str_size || !byte_limit) return 0; @@ -1301,3 +1324,11 @@ static size_t utilsGetUtf8StringLimit(const char *str, size_t str_size, size_t b return last_cp_pos; } + +static char utilsConvertHexDigitToBinary(char c) +{ + if ('a' <= c && c <= 'f') return (c - 'a' + 0xA); + if ('A' <= c && c <= 'F') return (c - 'A' + 0xA); + if ('0' <= c && c <= '9') return (c - '0'); + return 'z'; +} diff --git a/source/core/tik.c b/source/core/tik.c index a30e2d6..ff8b4a1 100644 --- a/source/core/tik.c +++ b/source/core/tik.c @@ -121,6 +121,8 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, u8 key_gener TikCommonBlock *tik_common_block = NULL; bool success = false, tik_retrieved = false; + LOG_DATA_INFO(id->c, sizeof(id->c), "Input rights ID:"); + /* Check if this ticket has already been retrieved. */ tik_common_block = tikGetCommonBlockFromTicket(dst); if (tik_common_block && !memcmp(tik_common_block->rights_id.c, id->c, sizeof(id->c))) @@ -179,9 +181,9 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, u8 key_gener /* Generate hex strings. */ tik_common_block = tikGetCommonBlockFromSignedTicketBlob(dst->data); - utilsGenerateHexStringFromData(dst->enc_titlekey_str, sizeof(dst->enc_titlekey_str), dst->enc_titlekey, sizeof(dst->enc_titlekey), false); - utilsGenerateHexStringFromData(dst->dec_titlekey_str, sizeof(dst->dec_titlekey_str), dst->dec_titlekey, sizeof(dst->dec_titlekey), false); - utilsGenerateHexStringFromData(dst->rights_id_str, sizeof(dst->rights_id_str), tik_common_block->rights_id.c, sizeof(tik_common_block->rights_id.c), false); + utilsGenerateHexString(dst->enc_titlekey_str, sizeof(dst->enc_titlekey_str), dst->enc_titlekey, sizeof(dst->enc_titlekey), false); + utilsGenerateHexString(dst->dec_titlekey_str, sizeof(dst->dec_titlekey_str), dst->dec_titlekey, sizeof(dst->dec_titlekey), false); + utilsGenerateHexString(dst->rights_id_str, sizeof(dst->rights_id_str), tik_common_block->rights_id.c, sizeof(tik_common_block->rights_id.c), false); end: return success; @@ -277,7 +279,7 @@ static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsI u64 tik_offset = 0, tik_size = 0; bool success = false; - utilsGenerateHexStringFromData(tik_filename, sizeof(tik_filename), id->c, sizeof(id->c), false); + utilsGenerateHexString(tik_filename, sizeof(tik_filename), id->c, sizeof(id->c), false); strcat(tik_filename, ".tik"); /* Get ticket entry info. */ diff --git a/source/core/title.c b/source/core/title.c index a8344cc..d9edc5d 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -1241,7 +1241,7 @@ fallback: { strcat(app_name, "_"); cur_filename_len = strlen(app_name); - utilsGenerateHexStringFromData(app_name + cur_filename_len, sizeof(app_name) - cur_filename_len, &(gc_header.package_id), sizeof(gc_header.package_id), false); + utilsGenerateHexString(app_name + cur_filename_len, sizeof(app_name) - cur_filename_len, &(gc_header.package_id), sizeof(gc_header.package_id), false); } filename = strdup(app_name);