1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 18:26:39 +00:00

nxdt_utils: add more helpers.

Implement utilsParseHexString() and utilsAppletLoopDelay().

Other changes include:

* defines: add THIRTY_FPS_DELAY define.
* defines: remove unused CERT_PATH define.

* gamecard: restore original reserved_2 field in GameCardInfo struct. The last 16 bytes weren't being properly due to an issue within libnx's AES-CBC implementation.
* gamecard: rename GAMECARD_ACCESS_WAIT_TIME -> GAMECARD_ACCESS_DELAY.

* host: improve ABI documentation (thanks @v1993).

* keys: remove keysConvertHexDigitToBinary().
* keys: change keysParseHexKey() function signature + update it to use utilsParseHexString() instead.

* nso: change "NRO" to "NSO" in some misleading log error messages (thanks @liamadvance).

* nxdt_rw_poc: replace svcSleepThread() calls with utilsAppletLoopDelay().

* nxdt_utils: rename utilsGenerateHexStringFromData() -> utilsGenerateHexString().
* nxdt_utils: add static utilsConvertHexDigitToBinary() function, used by utilsParseHexString().
* nxdt_utils: update utilsAppendFormattedStringToBuffer() to avoid logging an error string if resources haven't been initialized.
* nxdt_utils: use a single call to sprintf() with a variable precision argument in utilsGenerateFormattedSizeString().
* nxdt_utils: call utilsAppletLoopDelay() in the while loop from utilsPrintConsoleError().
* nxdt_utils: remove unused utilsCloseFileDescriptor() function.

* tik: log input rights ID at the start of tikRetrieveTicketByRightsId().
This commit is contained in:
Pablo Curiel 2023-10-23 00:44:40 +02:00
parent bbe11be667
commit 32bb5fd46e
16 changed files with 179 additions and 141 deletions

View file

@ -1072,7 +1072,7 @@ int main(int argc, char *argv[])
break; break;
} }
svcSleepThread(10000000); // 10 ms utilsAppletLoopDelay();
} }
if (!g_appletStatus) break; if (!g_appletStatus) break;
@ -1383,7 +1383,7 @@ int main(int argc, char *argv[])
break; break;
} }
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp | HidNpadButton_ZL | HidNpadButton_ZR)) svcSleepThread(40000000); // 40 ms utilsAppletLoopDelay();
} }
freeNcaFsSectionsList(); freeNcaFsSectionsList();
@ -1432,7 +1432,7 @@ static void utilsWaitForButtonPress(u64 flag)
{ {
utilsScanPads(); utilsScanPads();
if (utilsGetButtonsDown() & flag) break; if (utilsGetButtonsDown() & flag) break;
svcSleepThread(10000000); // 10 ms utilsAppletLoopDelay();
} }
} }
@ -1677,7 +1677,7 @@ void updateNcaList(TitleInfo *title_info)
continue; 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); ncmContentInfoSizeToU64(cur_content_info, &nca_size);
utilsGenerateFormattedSizeString((double)nca_size, nca_size_str, sizeof(nca_size_str)); 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); utilsCreateThread(&dump_thread, nspThreadFunc, &nsp_thread_data, 2);
/* Wait until the background thread calculates the NSP size. */ /* 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) 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)); consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, nsp_thread_data.total_size, percent, (now - start));
consoleRefresh(); consoleRefresh();
svcSleepThread(10000000); // 10 ms utilsAppletLoopDelay();
} }
consolePrint("\nwaiting for thread to join\n"); 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)); consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_thread_data->total_size, percent, (now - start));
consoleRefresh(); consoleRefresh();
svcSleepThread(10000000); // 10 ms utilsAppletLoopDelay();
} }
consolePrint("\nwaiting for threads to join\n"); consolePrint("\nwaiting for threads to join\n");

View file

@ -1,9 +1,11 @@
# nxdumptool USB Application Binary Interface (ABI) Technical Specification # 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. 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 ## Table of contents
* [USB device interface details](#usb-device-interface-details). * [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). * [Command blocks](#command-blocks).
* [StartSession](#startsession). * [StartSession](#startsession).
* [SendFileProperties](#sendfileproperties). * [SendFileProperties](#sendfileproperties).
* [Zero Length Termination (ZLT)](#zero-length-termination-zlt).
* [CancelFileTransfer](#cancelfiletransfer). * [CancelFileTransfer](#cancelfiletransfer).
* [SendNspHeader](#sendnspheader). * [SendNspHeader](#sendnspheader).
* [EndSession](#endsession). * [EndSession](#endsession).
@ -24,6 +25,7 @@ In order to avoid unnecessary clutter, this document assumes the reader is alrea
* [Status codes](#status-codes). * [Status codes](#status-codes).
* [NSP transfer mode](#nsp-transfer-mode). * [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) * [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). * [Additional resources](#additional-resources).
## USB device interface details ## 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. 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 ## USB driver
A USB driver is needed to actually communicate to the target console running nxdumptool. 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. 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. 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. 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 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 ### Command header
| Offset | Size | Description | Size: 0x10 bytes.
|:------:|:----:|------------------------------|
| 0x00 | 0x04 | Magic word (`NXDT`). |
| 0x04 | 0x04 | Command ID (LE u32). |
| 0x08 | 0x04 | Command block size (LE u32). |
| 0x0C | 0x04 | Reserved. |
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 #### Command IDs
| Value | Name | Description | | Value | Name | Description |
|:-----:|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-------|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
| 0 | StartSession. | Starts a USB session between the target console and the USB host device. | | 0 | [`StartSession`](#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. | | 1 | [`SendFileProperties`](#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. | | 2 | [`CancelFileTransfer`](#cancelfiletransfer) | Cancels an ongoing data transfer process started by a previously issued [`SendFileProperties`](#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. | | 3 | [`SendNspHeader`](#sendnspheader) | Sends the `PFS0` header from a Nintendo Submission Package (NSP). Only issued under [NSP transfer mode](#nsp-transfer-mode). |
| 4 | EndSession. | Ends a previously stablished USB session between the target console and the USB host device. | | 4 | [`EndSession`](#endsession) | Ends a previously stablished USB session between the target console and the USB host device. |
### Command blocks ### Command blocks
@ -109,42 +115,44 @@ All commands, with the exception of `CancelFileTransfer` and `EndSession`, yield
#### StartSession #### StartSession
| Offset | Size | Description | Size: 0x10 bytes.
|:------:|------|---------------------------------------------------------------------|
| 0x00 | 0x01 | nxdumptool version (major). | | Offset | Size | Type | Description |
| 0x01 | 0x01 | nxdumptool version (minor). | |--------|------|--------------|---------------------------------------------------------------------|
| 0x02 | 0x01 | nxdumptool version (micro). | | 0x00 | 0x01 | `uint8_t` | nxdumptool version (major). |
| 0x03 | 0x01 | nxdumptool USB ABI version (high nibble: major, low nibble: minor). | | 0x01 | 0x01 | `uint8_t` | nxdumptool version (minor). |
| 0x04 | 0x08 | Git commit hash (NULL terminated string). | | 0x02 | 0x01 | `uint8_t` | nxdumptool version (micro). |
| 0x0C | 0x04 | Reserved. | | 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. 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 #### SendFileProperties
| Offset | Size | Description | Size: 0x320 bytes.
|:------:|:-----:|--------------------------------------------------|
| 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. |
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. 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. 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.
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.
#### CancelFileTransfer #### 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). 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 #### EndSession
Yields no command block. Expects a status response, just like the rest of the commands. 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 ### Status response
| Offset | Size | Description | Size: 0x10 bytes.
|:------:|:----:|------------------------------------|
| 0x00 | 0x04 | Magic word (`NXDT`). | | Offset | Size | Type | Description |
| 0x04 | 0x04 | Status code (LE u32). | |--------|------|--------------|-------------------------------------|
| 0x08 | 0x02 | Endpoint max packet size (LE u16). | | 0x00 | 0x04 | `uint32_t` | Magic word (`NXDT`) (`0x5444584E`). |
| 0x0A | 0x06 | Reserved. | | 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: 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. * 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 #### Status codes
| Value | Description | | Value | Description |
|:-----:|------------------------------------------------------------------| |-------|------------------------------------------------------------------|
| 0 | Success. | | 0 | Success. |
| 1 | Invalid command size. Reserved for internal nxdumptool usage. | | 1 | Invalid command size. Reserved for internal nxdumptool usage. |
| 2 | Failed to write command. 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). 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 ## Additional resources

View file

@ -191,8 +191,7 @@ typedef struct {
u8 reserved_1[0x3]; u8 reserved_1[0x3];
u64 upp_hash; ///< Checksum for the update partition. The exact way it's calculated is currently unknown. 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. u64 upp_id; ///< Must match GAMECARD_UPDATE_TID.
u8 reserved_2[0x28]; u8 reserved_2[0x38];
u8 unknown[0x10]; ///< Unknown purpose. It's not zeroed out in recent (2021+?) gamecards.
} GameCardInfo; } GameCardInfo;
NXDT_ASSERT(GameCardInfo, 0x70); NXDT_ASSERT(GameCardInfo, 0x70);

View file

@ -121,9 +121,16 @@ void utilsReplaceIllegalCharacters(char *str, bool ascii_only);
/// Trims whitespace characters from the provided string. /// Trims whitespace characters from the provided string.
void utilsTrimString(char *str); 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. /// 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'. /// 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); 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); 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. /// Wrappers used in scoped locks.
NX_INLINE UtilsScopedLock utilsLockScope(Mutex *mtx) NX_INLINE UtilsScopedLock utilsLockScope(Mutex *mtx)
{ {

View file

@ -60,6 +60,8 @@
/* Global constants used throughout the application. */ /* Global constants used throughout the application. */
#define THIRTY_FPS_DELAY (u64)33333333 /* 1 / 30 = 33.33 milliseconds. */
#define FS_SYSMODULE_TID (u64)0x0100000000000000 #define FS_SYSMODULE_TID (u64)0x0100000000000000
#define BOOT_SYSMODULE_TID (u64)0x0100000000000005 #define BOOT_SYSMODULE_TID (u64)0x0100000000000005
#define SPL_SYSMODULE_TID (u64)0x0100000000000028 #define SPL_SYSMODULE_TID (u64)0x0100000000000028
@ -78,7 +80,6 @@
#define APP_BASE_PATH HBMENU_BASE_PATH APP_TITLE "/" #define APP_BASE_PATH HBMENU_BASE_PATH APP_TITLE "/"
#define GAMECARD_PATH APP_BASE_PATH "Gamecard/" #define GAMECARD_PATH APP_BASE_PATH "Gamecard/"
#define CERT_PATH APP_BASE_PATH "Certificate/"
#define HFS_PATH APP_BASE_PATH "HFS/" #define HFS_PATH APP_BASE_PATH "HFS/"
#define NSP_PATH APP_BASE_PATH "NSP/" #define NSP_PATH APP_BASE_PATH "NSP/"
#define TICKET_PATH APP_BASE_PATH "Ticket/" #define TICKET_PATH APP_BASE_PATH "Ticket/"

View file

@ -140,7 +140,7 @@ u8 *certRetrieveRawCertificateChainFromGameCardByRightsId(const FsRightsId *id,
bool success = false; bool success = false;
/* Generate certificate chain filename. */ /* 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"); strcat(raw_chain_filename, ".cert");
/* Get certificate chain entry info. */ /* Get certificate chain entry info. */

View file

@ -445,7 +445,7 @@ bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_
cur_nca_ctx->id_offset)) goto end; 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. */ /* ContentMeta, Digest, KeyGenerationMin, KeepGeneration and KeepGenerationSpecified. */
if (!CNMT_ADD_FMT_STR(" <ContentMeta />\n" \ if (!CNMT_ADD_FMT_STR(" <ContentMeta />\n" \

View file

@ -28,7 +28,7 @@
#define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB. */ #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_BLOCK_SIZE 0x24
#define GAMECARD_UNUSED_AREA_SIZE(x) (((x) / GAMECARD_PAGE_SIZE) * GAMECARD_UNUSED_AREA_BLOCK_SIZE) #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()) if (gamecardIsInserted())
{ {
/* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules. */ /* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules. */
utilsSleep(GAMECARD_ACCESS_WAIT_TIME); utilsSleep(GAMECARD_ACCESS_DELAY);
/* Load gamecard info. */ /* Load gamecard info. */
gamecardLoadInfo(); gamecardLoadInfo();

View file

@ -87,8 +87,7 @@ NXDT_ASSERT(EticketRsaDeviceKey, 0x240);
static bool keysIsKeyEmpty(const void *key); static bool keysIsKeyEmpty(const void *key);
static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **value); static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **value);
static char keysConvertHexDigitToBinary(char c); static bool keysParseHexKey(u8 *out, size_t out_size, const char *key, const char *value);
static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size);
static bool keysReadKeysFromFile(void); static bool keysReadKeysFromFile(void);
static bool keysDeriveMasterKeys(void); static bool keysDeriveMasterKeys(void);
@ -519,47 +518,18 @@ end:
return ret; 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 (!out || !out_size || !key || !*key || !value || !*value)
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)
{ {
LOG_MSG_ERROR("Invalid parameters!"); LOG_MSG_ERROR("Invalid parameters!");
return false; return false;
} }
if (value_len != hex_str_len) bool success = utilsParseHexString(out, out_size, value, 0);
{ if (!success) LOG_MSG_ERROR("Failed to parse key \"%s\"!", key);
LOG_MSG_ERROR("Key \"%s\" must be %u hex digits long!", key, hex_str_len);
return false;
}
memset(out, 0, size); return success;
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;
} }
static bool keysReadKeysFromFile(void) static bool keysReadKeysFromFile(void)
@ -584,7 +554,7 @@ static bool keysReadKeysFromFile(void)
} }
#define PARSE_HEX_KEY(name, out, decl) \ #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++; \ key_count++; \
decl; \ decl; \
} }

View file

@ -618,7 +618,7 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
sha256CalculateHash(icon_hash, icon_ctx->icon_data, icon_ctx->icon_size); sha256CalculateHash(icon_hash, icon_ctx->icon_data, icon_ctx->icon_size);
/* Generate icon hash string. Only the first half from the hash is used. */ /* 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. */ /* Add XML element. */
if (!NACP_ADD_FMT_STR_T1(" <Icon>\n" \ if (!NACP_ADD_FMT_STR_T1(" <Icon>\n" \
@ -726,7 +726,7 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
if (!NACP_ADD_FMT_STR_T1(" <NeighborDetectionClientConfiguration>\n")) goto end; if (!NACP_ADD_FMT_STR_T1(" <NeighborDetectionClientConfiguration>\n")) goto end;
/* SendGroupConfiguration. */ /* 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(" <SendGroupConfiguration>\n" \ if (!NACP_ADD_FMT_STR_T1(" <SendGroupConfiguration>\n" \
" <GroupId>0x%016lx</GroupId>\n" \ " <GroupId>0x%016lx</GroupId>\n" \
@ -740,7 +740,7 @@ bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 requir
{ {
NacpApplicationNeighborDetectionGroupConfiguration *rgc = &(ndcc->receivable_group_configurations[i]); 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(" <ReceivableGroupConfiguration>\n" \ if (!NACP_ADD_FMT_STR_T1(" <ReceivableGroupConfiguration>\n" \
" <GroupId>0x%016lx</GroupId>\n" \ " <GroupId>0x%016lx</GroupId>\n" \

View file

@ -200,9 +200,9 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
out->title_type = meta_key->type; out->title_type = meta_key->type;
memcpy(&(out->content_id), &(content_info->content_id), sizeof(NcmContentId)); 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->content_type = content_info->content_type;
out->id_offset = content_info->id_offset; out->id_offset = content_info->id_offset;
@ -537,11 +537,11 @@ void ncaUpdateContentIdAndHash(NcaContext *ctx, u8 hash[SHA256_HASH_SIZE])
/* Update content ID. */ /* Update content ID. */
memcpy(ctx->content_id.c, hash, sizeof(ctx->content_id.c)); 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. */ /* Update content hash. */
memcpy(ctx->hash, hash, sizeof(ctx->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) const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx)

View file

@ -233,7 +233,7 @@ static u8 *nsoGetRodataSegment(NsoContext *nso_ctx)
/* Read .rodata segment data. */ /* 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)) 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; 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)) != \ 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) (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; 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); 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) 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; goto end;
} }
} }

View file

@ -149,7 +149,7 @@ __attribute__((format(printf, 7, 8))) void logWriteBinaryDataToLogFile(const voi
if (!data_str) goto end; if (!data_str) goto end;
/* Generate hex string representation. */ /* 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); strcat(data_str, CRLF);
SCOPED_LOCK(&g_logMutex) SCOPED_LOCK(&g_logMutex)

View file

@ -82,7 +82,6 @@ static const char *g_outputDirs[] = {
HBMENU_BASE_PATH, HBMENU_BASE_PATH,
APP_BASE_PATH, APP_BASE_PATH,
GAMECARD_PATH, GAMECARD_PATH,
CERT_PATH,
HFS_PATH, HFS_PATH,
NSP_PATH, NSP_PATH,
TICKET_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 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) bool utilsInitializeResources(const int program_argc, const char **program_argv)
{ {
Result rc = 0; 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, ...) __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) if (!dst || !dst_size || !fmt || !*fmt)
{ {
LOG_MSG_ERROR("Invalid parameters!"); if (use_log) LOG_MSG_ERROR("Invalid parameters!");
return false; return false;
} }
@ -522,7 +526,7 @@ __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(ch
/* Sanity check. */ /* Sanity check. */
if (dst_cur_size && dst_str_len >= dst_cur_size) 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; return false;
} }
@ -532,7 +536,7 @@ __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(ch
formatted_str_len = vsnprintf(NULL, 0, fmt, args); formatted_str_len = vsnprintf(NULL, 0, fmt, args);
if (formatted_str_len <= 0) 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; goto end;
} }
@ -547,7 +551,7 @@ __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(ch
tmp_str = realloc(dst_ptr, dst_cur_size); tmp_str = realloc(dst_ptr, dst_cur_size);
if (!tmp_str) 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; goto end;
} }
@ -626,7 +630,7 @@ void utilsTrimString(char *str)
if (start != str) memmove(str, start, end - start + 1); 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; 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'; 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) void utilsGenerateFormattedSizeString(double size, char *dst, size_t dst_size)
{ {
if (!dst || dst_size < 2) return; 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); size /= pow(1024.0, i);
if (i == 0)
{
/* Don't display decimal places if we're dealing with plain bytes. */ /* Don't display decimal places if we're dealing with plain bytes. */
snprintf(dst, dst_size, "%.0f %s", size, g_sizeSuffixes[i]); snprintf(dst, dst_size, "%.*f %s", i == 0 ? 0 : 2, size, g_sizeSuffixes[i]);
} else {
snprintf(dst, dst_size, "%.2f %s", size, g_sizeSuffixes[i]);
}
break; break;
} }
@ -1001,6 +1030,7 @@ void utilsPrintConsoleError(const char *msg)
{ {
padUpdate(&pad); padUpdate(&pad);
if (padGetButtonsDown(&pad) & flag) break; if (padGetButtonsDown(&pad) & flag) break;
utilsAppletLoopDelay();
} }
/* Deinitialize console output. */ /* 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) static size_t utilsGetUtf8StringLimit(const char *str, size_t str_size, size_t byte_limit)
{ {
if (!str || !*str || !str_size || !byte_limit) return 0; 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; 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';
}

View file

@ -121,6 +121,8 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, u8 key_gener
TikCommonBlock *tik_common_block = NULL; TikCommonBlock *tik_common_block = NULL;
bool success = false, tik_retrieved = false; 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. */ /* Check if this ticket has already been retrieved. */
tik_common_block = tikGetCommonBlockFromTicket(dst); tik_common_block = tikGetCommonBlockFromTicket(dst);
if (tik_common_block && !memcmp(tik_common_block->rights_id.c, id->c, sizeof(id->c))) 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. */ /* Generate hex strings. */
tik_common_block = tikGetCommonBlockFromSignedTicketBlob(dst->data); tik_common_block = tikGetCommonBlockFromSignedTicketBlob(dst->data);
utilsGenerateHexStringFromData(dst->enc_titlekey_str, sizeof(dst->enc_titlekey_str), dst->enc_titlekey, sizeof(dst->enc_titlekey), false); utilsGenerateHexString(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); utilsGenerateHexString(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->rights_id_str, sizeof(dst->rights_id_str), tik_common_block->rights_id.c, sizeof(tik_common_block->rights_id.c), false);
end: end:
return success; return success;
@ -277,7 +279,7 @@ static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsI
u64 tik_offset = 0, tik_size = 0; u64 tik_offset = 0, tik_size = 0;
bool success = false; 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"); strcat(tik_filename, ".tik");
/* Get ticket entry info. */ /* Get ticket entry info. */

View file

@ -1241,7 +1241,7 @@ fallback:
{ {
strcat(app_name, "_"); strcat(app_name, "_");
cur_filename_len = strlen(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); filename = strdup(app_name);