mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-08 11:51:48 +00:00
I'm a terrible person and an even worse developer.
And I don't need anyone to tell me so, thank you very much. * PoC: remove gc_dumper and nsp_dumper PoC; create nxdt_rw_poc with all gc_dumper and nsp_dumper capabilities + standalone ticket dumping + raw NCA dumping; use ftruncate() to set output file sizes whenever possible. PoC code is a mess, as always. Expect the features from the rest of the PoCs to be implemented into nxdt_rw_poc soon. * workflow: temporarily disable borealis build generation; comment out manual installation of up-to-date packages from Leseratte's mirrors because the latest devkitA64 Docker image has them all. * borealis: update to fix building issues with latest devkitA64. * bfttf: error out on invalid NCA signatures. * config: save configuration to the current working directory; parse and validate new "gamecard/write_raw_hfs_partition" flag. * defines: remove CONFIG_PATH macro; rename CONFIG_FILE_NAME. * gamecard: rename fs_ctx -> hfs_ctx everywhere; use HFS function calls to retrieve partition names. * hfs: move GameCardHashFileSystemPartitionType enum from gamecard.h and rename it to HashFileSystemPartitionType; add hfsIsValidContext(); add hfsGetPartitionNameString(). * nca/npdm: update comments to reflect latest HOS version. * nxdt_bfsar: always generate absolute SD card paths with the device name; error out on an invalid NCA signature. * nxdt_includes: include dirent.h; refactor Version struct to make it a union of all known *Version structs. * nxdt_log: don't write session separator if the logfile is empty. * nxdt_utils: log appletIsGamePlayRecordingSupported() errors; add utilsDeleteDirectoryRecursively(). * rsa: provide clearer function descriptions in header file. * services: handle usb:ds initialization. * tik: update tikConvertPersonalizedTicketToCommonTicket() to allow NULL input pointers as raw certificate chain arguments (much needed for standalone ticket dumping). * title: add titleGetApplicationIdByMetaKey(). * usb: refactor interface (de)initialization code; slightly improve ABI usage (console-side only); redefine ABI version field in StartSession command blocks; upgrade ABI to v1.1. * FatFs: rename DIR -> FDIR to avoid conflicts with definitions from stdlib's dirent.h. * gamecard_tab: display package ID from the inserted gamecard; fix displayed version numbers from bundled system updates below 3.0.0. * todo: add notes about creating devoptab devices for HFS/PFS/RomFS file tree dumping.
This commit is contained in:
parent
2b8dcec7f4
commit
fb58d20fe3
41 changed files with 5329 additions and 3472 deletions
40
.github/workflows/rewrite.yml
vendored
40
.github/workflows/rewrite.yml
vendored
|
@ -32,12 +32,12 @@ jobs:
|
|||
run: |
|
||||
sudo -n apt-get update
|
||||
sudo -n apt-get upgrade -y patch autoconf automake tar bzip2 diffutils pkgconf fakeroot p7zip-full git file
|
||||
sudo -n dkp-pacman --noconfirm -U \
|
||||
"https://wii.leseratte10.de/devkitPro/switch/switch-libjson-c-0.16-1-any.pkg.tar.xz" \
|
||||
"https://wii.leseratte10.de/devkitPro/switch/switch-libpng-1.6.39-1-any.pkg.tar.xz" \
|
||||
"https://wii.leseratte10.de/devkitPro/switch/switch-zlib-1.2.13-1-any.pkg.tar.xz" \
|
||||
"https://wii.leseratte10.de/devkitPro/other-stuff/dkp-toolchain-vars-1.0.2-1-any.pkg.tar.xz" \
|
||||
"https://wii.leseratte10.de/devkitPro/cmake/switch-cmake-1.5.0-1-any.pkg.tar.xz"
|
||||
# sudo -n dkp-pacman --noconfirm -U \
|
||||
# "https://wii.leseratte10.de/devkitPro/switch/switch-libjson-c-0.16-1-any.pkg.tar.xz" \
|
||||
# "https://wii.leseratte10.de/devkitPro/switch/switch-libpng-1.6.39-2-any.pkg.tar.zst" \
|
||||
# "https://wii.leseratte10.de/devkitPro/switch/switch-zlib-1.2.13-1-any.pkg.tar.xz" \
|
||||
# "https://wii.leseratte10.de/devkitPro/other-stuff/dkp-toolchain-vars-1.0.3-2-any.pkg.tar.zst" \
|
||||
# "https://wii.leseratte10.de/devkitPro/cmake/switch-cmake-1.5.0-1-any.pkg.tar.xz"
|
||||
|
||||
- name: Silence all git safe directory warnings
|
||||
run: git config --system --add safe.directory '*'
|
||||
|
@ -76,10 +76,10 @@ jobs:
|
|||
echo "nxdt_commit=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
./build.sh
|
||||
|
||||
- name: Build nxdumptool-rewrite GUI binary
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE/nxdumptool"
|
||||
make -j$(nproc)
|
||||
#- name: Build nxdumptool-rewrite GUI binary
|
||||
# run: |
|
||||
# cd "$GITHUB_WORKSPACE/nxdumptool"
|
||||
# make -j$(nproc)
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
@ -93,14 +93,14 @@ jobs:
|
|||
path: nxdumptool/nxdumptool-rewrite_poc_${{ env.nxdt_commit }}-Debug_ELFs.7z
|
||||
if-no-files-found: error
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: nxdumptool-rewrite-${{ env.nxdt_commit }}-WIP_UI.nro
|
||||
path: nxdumptool/nxdumptool.nro
|
||||
if-no-files-found: error
|
||||
#- uses: actions/upload-artifact@v3
|
||||
# with:
|
||||
# name: nxdumptool-rewrite-${{ env.nxdt_commit }}-WIP_UI.nro
|
||||
# path: nxdumptool/nxdumptool.nro
|
||||
# if-no-files-found: error
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: nxdumptool-rewrite-${{ env.nxdt_commit }}-WIP_UI.elf
|
||||
path: nxdumptool/nxdumptool.elf
|
||||
if-no-files-found: error
|
||||
#- uses: actions/upload-artifact@v3
|
||||
# with:
|
||||
# name: nxdumptool-rewrite-${{ env.nxdt_commit }}-WIP_UI.elf
|
||||
# path: nxdumptool/nxdumptool.elf
|
||||
# if-no-files-found: error
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
4197
code_templates/nxdt_rw_poc.c
Normal file
4197
code_templates/nxdt_rw_poc.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -158,6 +158,9 @@ static void read_thread_func(void *arg)
|
|||
break;
|
||||
}
|
||||
|
||||
/* Set file size. */
|
||||
ftruncate(fileno(shared_data->fd), (off_t)file_entry->size);
|
||||
|
||||
for(u64 offset = 0, blksize = BLOCK_SIZE; offset < file_entry->size; offset += blksize)
|
||||
{
|
||||
if (blksize > (file_entry->size - offset)) blksize = (file_entry->size - offset);
|
||||
|
@ -585,7 +588,7 @@ int main(int argc, char *argv[])
|
|||
consoleClear();
|
||||
consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id + program_id_offset);
|
||||
|
||||
if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
|
||||
if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \
|
||||
titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), user_app_data.app_info->version.value, NULL))
|
||||
{
|
||||
consolePrint("nca initialize base ctx failed\n");
|
||||
|
@ -611,7 +614,7 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
consolePrint("using patch romfs with update v%u\n", latest_patch->version.value);
|
||||
|
||||
if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
|
||||
if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \
|
||||
titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), latest_patch->version.value, NULL))
|
||||
{
|
||||
consolePrint("nca initialize update ctx failed\n");
|
||||
|
|
|
@ -122,6 +122,9 @@ static void dumpPartitionFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
|
|||
goto end;
|
||||
}
|
||||
|
||||
/* Set file size. */
|
||||
ftruncate(fileno(filefd), (off_t)pfs_entry->size);
|
||||
|
||||
consolePrint("dumping \"%s\"...\n", pfs_entry_name);
|
||||
|
||||
u64 blksize = BLOCK_SIZE;
|
||||
|
@ -195,6 +198,9 @@ static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
|
|||
goto end;
|
||||
}
|
||||
|
||||
/* Set file size. */
|
||||
ftruncate(fileno(filefd), (off_t)romfs_file_entry->size);
|
||||
|
||||
consolePrint("dumping \"%s\"...\n", path + path_len);
|
||||
|
||||
u64 blksize = BLOCK_SIZE;
|
||||
|
|
|
@ -161,7 +161,7 @@ static void read_thread_func(void *arg)
|
|||
if (shared_data->write_error) break;
|
||||
|
||||
/* Send current file properties */
|
||||
shared_data->read_error = !usbSendFilePropertiesCommon(file_entry->size, path);
|
||||
shared_data->read_error = !usbSendFileProperties(file_entry->size, path);
|
||||
if (shared_data->read_error)
|
||||
{
|
||||
condvarWakeAll(&g_writeCondvar);
|
||||
|
@ -579,7 +579,7 @@ int main(int argc, char *argv[])
|
|||
consoleClear();
|
||||
consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id + program_id_offset);
|
||||
|
||||
if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
|
||||
if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \
|
||||
titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), user_app_data.app_info->version.value, NULL))
|
||||
{
|
||||
consolePrint("nca initialize base ctx failed\n");
|
||||
|
@ -605,7 +605,7 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
consolePrint("using patch romfs with update v%u\n", latest_patch->version.value);
|
||||
|
||||
if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
|
||||
if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \
|
||||
titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), latest_patch->version.value, NULL))
|
||||
{
|
||||
consolePrint("nca initialize update ctx failed\n");
|
||||
|
|
|
@ -280,7 +280,7 @@ int main(int argc, char *argv[])
|
|||
NcmContentInfo *content_info = &(user_app_data.app_info->content_infos[i]);
|
||||
if (content_info->content_type == NcmContentType_Meta) continue;
|
||||
|
||||
if (!ncaInitializeContext(&(nca_ctx[j]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
|
||||
if (!ncaInitializeContext(&(nca_ctx[j]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \
|
||||
content_info, user_app_data.app_info->version.value, &tik))
|
||||
{
|
||||
consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
|
||||
|
@ -330,7 +330,7 @@ int main(int argc, char *argv[])
|
|||
j++;
|
||||
}
|
||||
|
||||
if (!ncaInitializeContext(&(nca_ctx[meta_idx]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \
|
||||
if (!ncaInitializeContext(&(nca_ctx[meta_idx]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \
|
||||
titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Meta, 0), user_app_data.app_info->version.value, &tik))
|
||||
{
|
||||
consolePrint("Meta nca initialize ctx failed\n");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 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 (May 11th, 2021), the current ABI version is `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 (April 19th, 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.
|
||||
|
||||
|
@ -109,14 +109,14 @@ 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. |
|
||||
| 0x04 | 0x08 | Git commit hash (NULL terminated string). |
|
||||
| 0x0C | 0x04 | Reserved. |
|
||||
| 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. |
|
||||
|
||||
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.
|
||||
|
||||
|
@ -180,7 +180,7 @@ Status responses are expected by nxdumptool at certain points throughout the com
|
|||
* Right after receiving a command header and/or command block.
|
||||
* 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 the `usb:ds` API provides no way for homebrew applications to know which device descriptor and/or speed was selected by the USB host device.
|
||||
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.
|
||||
|
||||
#### Status codes
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ import struct
|
|||
import usb.core
|
||||
import usb.util
|
||||
import warnings
|
||||
import base64
|
||||
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
|
@ -52,10 +53,11 @@ from tkinter import filedialog, messagebox, font, scrolledtext
|
|||
|
||||
from tqdm import tqdm
|
||||
|
||||
import base64
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from io import BufferedWriter
|
||||
from typing import List, Tuple, Any, Callable, Optional
|
||||
|
||||
# Scaling factors.
|
||||
WINDOWS_SCALING_FACTOR = 96.0
|
||||
SCALE = 1.0
|
||||
|
@ -65,7 +67,7 @@ WINDOW_WIDTH = 500
|
|||
WINDOW_HEIGHT = 470
|
||||
|
||||
# Application version.
|
||||
APP_VERSION = '0.3'
|
||||
APP_VERSION = '0.4'
|
||||
|
||||
# Copyright year.
|
||||
COPYRIGHT_YEAR = '2020-2023'
|
||||
|
@ -91,7 +93,8 @@ USB_TRANSFER_THRESHOLD = (USB_TRANSFER_BLOCK_SIZE * 4)
|
|||
USB_MAGIC_WORD = b'NXDT'
|
||||
|
||||
# Supported USB ABI version.
|
||||
USB_ABI_VERSION = 1
|
||||
USB_ABI_VERSION_MAJOR = 1
|
||||
USB_ABI_VERSION_MINOR = 1
|
||||
|
||||
# USB command header size.
|
||||
USB_CMD_HEADER_SIZE = 0x10
|
||||
|
@ -119,14 +122,14 @@ USB_STATUS_MALFORMED_CMD = 7
|
|||
USB_STATUS_HOST_IO_ERROR = 8
|
||||
|
||||
# Script title.
|
||||
SCRIPT_TITLE = "{} host script v{}".format(USB_DEV_PRODUCT, APP_VERSION)
|
||||
SCRIPT_TITLE = f'{USB_DEV_PRODUCT} host script v{APP_VERSION}'
|
||||
|
||||
# Copyright text.
|
||||
COPYRIGHT_TEXT = "Copyright (c) {}, {}".format(COPYRIGHT_YEAR, USB_DEV_MANUFACTURER)
|
||||
COPYRIGHT_TEXT = f'Copyright (c) {COPYRIGHT_YEAR}, {USB_DEV_MANUFACTURER}'
|
||||
|
||||
# Messages displayed as labels.
|
||||
SERVER_START_MSG = 'Please connect a Nintendo Switch console running {}.'.format(USB_DEV_PRODUCT)
|
||||
SERVER_STOP_MSG = 'Exit {} on your console or disconnect it at any time to stop the server.'.format(USB_DEV_PRODUCT)
|
||||
SERVER_START_MSG = f'Please connect a Nintendo Switch console running {USB_DEV_PRODUCT}.'
|
||||
SERVER_STOP_MSG = f'Exit {USB_DEV_PRODUCT} on your console or disconnect it at any time to stop the server.'
|
||||
|
||||
# Default directory paths.
|
||||
INITIAL_DIR = os.path.abspath(os.path.dirname(sys.executable if getattr(sys, 'frozen', False) else __file__))
|
||||
|
@ -292,13 +295,57 @@ TASKBAR_LIB = b'TVNGVAIAAQAAAAAACQQAAAAAAABBAAAAAQAAAAAAAAAOAAAA/////wAAAAAAAAAA
|
|||
b'AAAAABQAAQADAAOAAAAAAAAAJAAEAAAAFAACAAMAA4AAAAAAAAAkAAgAAAAUAAMAAwADgAAAAAAAACQADAAAAAAAAEABAABAAgAAQAMAAEC0BgAAxAYAANQGAADoBgAA' + \
|
||||
b'AAAAABQAAAAoAAAAPAAAAA=='
|
||||
|
||||
# Global variables used throughout the code.
|
||||
g_cliMode: bool = False
|
||||
g_outputDir: str = ''
|
||||
|
||||
g_osType: str = ''
|
||||
g_osVersion: str = ''
|
||||
|
||||
g_isWindows: bool = False
|
||||
g_isWindowsVista: bool = False
|
||||
g_isWindows7: bool = False
|
||||
|
||||
g_tkRoot: Optional[tk.Tk] = None
|
||||
g_tkCanvas: Optional[tk.Canvas] = None
|
||||
g_tkDirText: Optional[tk.Text] = None
|
||||
g_tkChooseDirButton: Optional[tk.Button] = None
|
||||
g_tkServerButton: Optional[tk.Button] = None
|
||||
g_tkTipMessage: Any = None
|
||||
g_tkScrolledTextLog: Optional[scrolledtext.ScrolledText] = None
|
||||
|
||||
g_logger: Optional[logging.Logger] = None
|
||||
|
||||
g_stopEvent: Optional[threading.Event] = None
|
||||
|
||||
g_tlb: Any = None
|
||||
g_taskbar: Any = None
|
||||
|
||||
g_usbEpIn: Any = None
|
||||
g_usbEpOut: Any = None
|
||||
g_usbEpMaxPacketSize: int = 0
|
||||
|
||||
g_nxdtVersionMajor: int = 0
|
||||
g_nxdtVersionMinor: int = 0
|
||||
g_nxdtVersionMicro: int = 0
|
||||
g_nxdtAbiVersionMajor: int = 0
|
||||
g_nxdtAbiVersionMinor: int = 0
|
||||
g_nxdtGitCommit: str = ''
|
||||
|
||||
g_nspTransferMode: bool = False
|
||||
g_nspSize: int = 0
|
||||
g_nspHeaderSize: int = 0
|
||||
g_nspRemainingSize: int = 0
|
||||
g_nspFile: Optional[BufferedWriter] = None
|
||||
g_nspFilePath: str = ''
|
||||
|
||||
# Reference: https://beenje.github.io/blog/posts/logging-to-a-tkinter-scrolledtext-widget.
|
||||
class LogQueueHandler(logging.Handler):
|
||||
def __init__(self, log_queue):
|
||||
def __init__(self, log_queue: queue.Queue):
|
||||
super().__init__()
|
||||
self.log_queue = log_queue
|
||||
|
||||
def emit(self, record):
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
if g_cliMode:
|
||||
msg = self.format(record)
|
||||
print(msg)
|
||||
|
@ -307,12 +354,14 @@ class LogQueueHandler(logging.Handler):
|
|||
|
||||
# Reference: https://beenje.github.io/blog/posts/logging-to-a-tkinter-scrolledtext-widget.
|
||||
class LogConsole:
|
||||
def __init__(self, scrolled_text=None):
|
||||
def __init__(self, scrolled_text: Optional[scrolledtext.ScrolledText] = None):
|
||||
#assert g_logger is not None
|
||||
|
||||
self.scrolled_text = scrolled_text
|
||||
self.frame = (self.scrolled_text.winfo_toplevel() if self.scrolled_text else None)
|
||||
|
||||
# Create a logging handler using a queue.
|
||||
self.log_queue = queue.Queue()
|
||||
self.log_queue: queue.Queue = queue.Queue()
|
||||
self.queue_handler = LogQueueHandler(self.log_queue)
|
||||
#formatter = logging.Formatter('[%(asctime)s] -> %(message)s')
|
||||
formatter = logging.Formatter('%(message)s')
|
||||
|
@ -320,9 +369,10 @@ class LogConsole:
|
|||
g_logger.addHandler(self.queue_handler)
|
||||
|
||||
# Start polling messages from the queue.
|
||||
if self.frame: self.frame.after(100, self.poll_log_queue)
|
||||
if self.frame:
|
||||
self.frame.after(100, self.poll_log_queue)
|
||||
|
||||
def display(self, record):
|
||||
def display(self, record: logging.LogRecord) -> None:
|
||||
if self.scrolled_text:
|
||||
msg = self.queue_handler.format(record)
|
||||
self.scrolled_text.configure(state='normal')
|
||||
|
@ -330,7 +380,7 @@ class LogConsole:
|
|||
self.scrolled_text.configure(state='disabled')
|
||||
self.scrolled_text.yview(tk.END)
|
||||
|
||||
def poll_log_queue(self):
|
||||
def poll_log_queue(self) -> None:
|
||||
# Check every 100 ms if there is a new message in the queue to display.
|
||||
while True:
|
||||
try:
|
||||
|
@ -340,40 +390,45 @@ class LogConsole:
|
|||
else:
|
||||
self.display(record)
|
||||
|
||||
if self.frame: self.frame.after(100, self.poll_log_queue)
|
||||
if self.frame:
|
||||
self.frame.after(100, self.poll_log_queue)
|
||||
|
||||
# Loosely based on tk.py from tqdm.
|
||||
class ProgressBarWindow:
|
||||
global g_tlb, g_taskbar
|
||||
|
||||
def __init__(self, bar_format=None, tk_parent=None, window_title='', window_resize=False, window_protocol=None):
|
||||
self.n = 0
|
||||
self.total = 0
|
||||
self.divider = 1
|
||||
self.total_div = 0
|
||||
self.prefix = ''
|
||||
self.unit = 'B'
|
||||
def __init__(self, bar_format: str = '', tk_parent: Any = None, window_title: str = '', window_resize: bool = False, window_protocol: Optional[Callable] = None):
|
||||
self.n: int = 0
|
||||
self.total: int = 0
|
||||
self.divider: float = 1.0
|
||||
self.total_div: float = 0
|
||||
self.prefix: str = ''
|
||||
self.unit: str = 'B'
|
||||
self.bar_format = bar_format
|
||||
self.start_time = 0
|
||||
self.elapsed_time = 0
|
||||
self.hwnd = 0
|
||||
self.start_time: float = 0
|
||||
self.elapsed_time: float = 0
|
||||
self.hwnd: int = 0
|
||||
|
||||
self.tk_parent = tk_parent
|
||||
self.tk_window = (tk.Toplevel(self.tk_parent) if self.tk_parent else None)
|
||||
self.withdrawn = False
|
||||
self.tk_text_var = None
|
||||
self.tk_n_var = None
|
||||
self.tk_pbar = None
|
||||
self.tk_text_var: Optional[tk.StringVar] = None
|
||||
self.tk_n_var: Optional[tk.DoubleVar] = None
|
||||
self.tk_pbar: Optional[ttk.Progressbar] = None
|
||||
|
||||
self.pbar = None
|
||||
self.pbar: Optional[tqdm] = None
|
||||
|
||||
if self.tk_window:
|
||||
self.tk_window.withdraw()
|
||||
self.withdrawn = True
|
||||
|
||||
if window_title: self.tk_window.title(window_title)
|
||||
if window_title:
|
||||
self.tk_window.title(window_title)
|
||||
|
||||
self.tk_window.resizable(window_resize, window_resize)
|
||||
if window_protocol: self.tk_window.protocol('WM_DELETE_WINDOW', window_protocol)
|
||||
|
||||
if window_protocol:
|
||||
self.tk_window.protocol('WM_DELETE_WINDOW', window_protocol)
|
||||
|
||||
pbar_frame = ttk.Frame(self.tk_window, padding=5)
|
||||
pbar_frame.pack()
|
||||
|
@ -388,10 +443,12 @@ class ProgressBarWindow:
|
|||
self.tk_pbar.pack()
|
||||
|
||||
def __del__(self):
|
||||
if self.tk_parent: self.tk_parent.after(0, self.tk_window.destroy)
|
||||
if self.tk_parent:
|
||||
self.tk_parent.after(0, self.tk_window.destroy)
|
||||
|
||||
def start(self, total, n=0, divider=1, prefix='', unit='B'):
|
||||
if (total <= 0) or (n < 0) or (divider < 1): raise Exception('Invalid arguments!')
|
||||
def start(self, total: int, n: int = 0, divider: int = 1, prefix: str = '', unit: str = 'B') -> None:
|
||||
if (total <= 0) or (n < 0) or (divider < 1):
|
||||
raise Exception('Invalid arguments!')
|
||||
|
||||
self.n = n
|
||||
self.total = total
|
||||
|
@ -407,11 +464,15 @@ class ProgressBarWindow:
|
|||
n_div = (float(self.n) / self.divider)
|
||||
self.pbar = tqdm(initial=n_div, total=self.total_div, unit=self.unit, dynamic_ncols=True, desc=self.prefix, bar_format=self.bar_format)
|
||||
|
||||
def update(self, n):
|
||||
def update(self, n: int) -> None:
|
||||
cur_n = (self.n + n)
|
||||
if cur_n > self.total: return
|
||||
if cur_n > self.total:
|
||||
return
|
||||
|
||||
if self.tk_window:
|
||||
#assert self.tk_text_var is not None
|
||||
#assert self.tk_n_var is not None
|
||||
|
||||
cur_n_div = (float(cur_n) / self.divider)
|
||||
self.elapsed_time = (time.time() - self.start_time)
|
||||
|
||||
|
@ -421,7 +482,7 @@ class ProgressBarWindow:
|
|||
self.tk_n_var.set(cur_n_div)
|
||||
|
||||
if self.withdrawn:
|
||||
self.tk_window.geometry("+{}+{}".format(self.tk_parent.winfo_x(), self.tk_parent.winfo_y()))
|
||||
self.tk_window.geometry(f'+{self.tk_parent.winfo_x()}+{self.tk_parent.winfo_y()}')
|
||||
self.tk_window.deiconify()
|
||||
self.tk_window.grab_set()
|
||||
|
||||
|
@ -432,14 +493,16 @@ class ProgressBarWindow:
|
|||
|
||||
self.withdrawn = False
|
||||
|
||||
if g_taskbar: g_taskbar.SetProgressValue(self.hwnd, cur_n, self.total)
|
||||
if g_taskbar:
|
||||
g_taskbar.SetProgressValue(self.hwnd, cur_n, self.total)
|
||||
else:
|
||||
#assert self.pbar is not None
|
||||
n_div = (float(n) / self.divider)
|
||||
self.pbar.update(n_div)
|
||||
|
||||
self.n = cur_n
|
||||
|
||||
def end(self):
|
||||
def end(self) -> None:
|
||||
self.n = 0
|
||||
self.total = 0
|
||||
self.divider = 1
|
||||
|
@ -450,6 +513,8 @@ class ProgressBarWindow:
|
|||
self.elapsed_time = 0
|
||||
|
||||
if self.tk_window:
|
||||
#assert self.tk_pbar is not None
|
||||
|
||||
if g_taskbar:
|
||||
g_taskbar.SetProgressState(self.hwnd, g_tlb.TBPF_NOPROGRESS)
|
||||
g_taskbar.UnregisterTab(self.hwnd)
|
||||
|
@ -461,27 +526,31 @@ class ProgressBarWindow:
|
|||
|
||||
self.tk_pbar.configure(maximum=100, mode='indeterminate')
|
||||
else:
|
||||
#assert self.pbar is not None
|
||||
self.pbar.close()
|
||||
self.pbar = None
|
||||
print()
|
||||
|
||||
def set_prefix(self, prefix):
|
||||
def set_prefix(self, prefix) -> None:
|
||||
self.prefix = prefix
|
||||
|
||||
def utilsGetPath(path_arg, fallback_path, is_file, create=False):
|
||||
g_progressBarWindow: Optional[ProgressBarWindow] = None
|
||||
|
||||
def utilsGetPath(path_arg: str, fallback_path: str, is_file: bool, create: bool = False) -> str:
|
||||
path = os.path.abspath(os.path.expanduser(os.path.expandvars(path_arg if path_arg else fallback_path)))
|
||||
|
||||
if not is_file and create: os.makedirs(path, exist_ok=True)
|
||||
if not is_file and create:
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
if not os.path.exists(path) or (is_file and os.path.isdir(path)) or (not is_file and os.path.isfile(path)):
|
||||
raise Exception("Error: '%s' points to an invalid file/directory." % (path))
|
||||
raise Exception(f'Error: "{path}" points to an invalid file/directory.')
|
||||
|
||||
return path
|
||||
|
||||
def utilsIsValueAlignedToEndpointPacketSize(value):
|
||||
def utilsIsValueAlignedToEndpointPacketSize(value: int) -> bool:
|
||||
return bool((value & (g_usbEpMaxPacketSize - 1)) == 0)
|
||||
|
||||
def utilsResetNspInfo():
|
||||
def utilsResetNspInfo() -> None:
|
||||
global g_nspTransferMode, g_nspSize, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath
|
||||
|
||||
# Reset NSP transfer mode info.
|
||||
|
@ -490,14 +559,14 @@ def utilsResetNspInfo():
|
|||
g_nspHeaderSize = 0
|
||||
g_nspRemainingSize = 0
|
||||
g_nspFile = None
|
||||
g_nspFilePath = None
|
||||
g_nspFilePath = ''
|
||||
|
||||
def utilsGetSizeUnitAndDivisor(size):
|
||||
def utilsGetSizeUnitAndDivisor(size: int) -> Tuple[str, int]:
|
||||
size_suffixes = [ 'B', 'KiB', 'MiB', 'GiB' ]
|
||||
size_suffixes_count = len(size_suffixes)
|
||||
|
||||
float_size = float(size)
|
||||
ret = None
|
||||
ret = (size_suffixes[0], 1)
|
||||
|
||||
for i in range(size_suffixes_count):
|
||||
if (float_size < pow(1024, i + 1)) or ((i + 1) >= size_suffixes_count):
|
||||
|
@ -506,15 +575,19 @@ def utilsGetSizeUnitAndDivisor(size):
|
|||
|
||||
return ret
|
||||
|
||||
def usbGetDeviceEndpoints():
|
||||
def usbGetDeviceEndpoints() -> bool:
|
||||
global g_usbEpIn, g_usbEpOut, g_usbEpMaxPacketSize
|
||||
|
||||
#assert g_logger is not None
|
||||
#assert g_stopEvent is not None
|
||||
|
||||
prev_dev = cur_dev = None
|
||||
usb_ep_in_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN
|
||||
usb_ep_out_lambda = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_OUT
|
||||
usb_version = 0
|
||||
|
||||
if g_cliMode: g_logger.info('Please connect a Nintendo Switch console running nxdumptool.')
|
||||
if g_cliMode:
|
||||
g_logger.info(f'Please connect a Nintendo Switch console running {USB_DEV_PRODUCT}.')
|
||||
|
||||
while True:
|
||||
# Check if the user decided to stop the server.
|
||||
|
@ -523,8 +596,9 @@ def usbGetDeviceEndpoints():
|
|||
return False
|
||||
|
||||
# Find a connected USB device with a matching VID/PID pair.
|
||||
# Using == here to compare both device instances would also compare the backend, so we'll just compare certain elements manually.
|
||||
cur_dev = usb.core.find(idVendor=USB_DEV_VID, idProduct=USB_DEV_PID)
|
||||
if (cur_dev is None) or ((prev_dev is not None) and (cur_dev.bus == prev_dev.bus) and (cur_dev.address == prev_dev.address)): # Using == here would also compare the backend.
|
||||
if (cur_dev is None) or ((prev_dev is not None) and (cur_dev.bus == prev_dev.bus) and (cur_dev.address == prev_dev.address)):
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
|
@ -532,9 +606,10 @@ def usbGetDeviceEndpoints():
|
|||
prev_dev = cur_dev
|
||||
|
||||
# Check if the product and manufacturer strings match the ones used by nxdumptool.
|
||||
# TODO: enable product string check whenever we're ready for a release.
|
||||
#if (cur_dev.manufacturer != USB_DEV_MANUFACTURER) or (cur_dev.product != USB_DEV_PRODUCT):
|
||||
if cur_dev.manufacturer != USB_DEV_MANUFACTURER:
|
||||
g_logger.error('Invalid manufacturer/product strings! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address))
|
||||
g_logger.error(f'Invalid manufacturer/product strings! (bus {cur_dev.bus}, address {cur_dev.address}).')
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
|
@ -553,7 +628,7 @@ def usbGetDeviceEndpoints():
|
|||
g_usbEpOut = usb.util.find_descriptor(intf, custom_match=usb_ep_out_lambda)
|
||||
|
||||
if (g_usbEpIn is None) or (g_usbEpOut is None):
|
||||
g_logger.error('Invalid endpoint addresses! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address))
|
||||
g_logger.error(f'Invalid endpoint addresses! (bus {cur_dev.bus}, address {cur_dev.address}).')
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
|
@ -563,81 +638,103 @@ def usbGetDeviceEndpoints():
|
|||
|
||||
break
|
||||
|
||||
g_logger.debug('Successfully retrieved USB endpoints! (bus %u, address %u).' % (cur_dev.bus, cur_dev.address))
|
||||
g_logger.debug('Max packet size: 0x%X (USB %u.%u).\n' % (g_usbEpMaxPacketSize, usb_version >> 8, (usb_version & 0xFF) >> 4))
|
||||
g_logger.debug(f'Successfully retrieved USB endpoints! (bus {cur_dev.bus}, address {cur_dev.address}).')
|
||||
g_logger.debug(f'Max packet size: 0x{g_usbEpMaxPacketSize:X} (USB {usb_version >> 8}.{(usb_version & 0xFF) >> 4}).\n')
|
||||
|
||||
if g_cliMode: g_logger.info('Exit nxdumptool or disconnect your console at any time to close this script.')
|
||||
if g_cliMode:
|
||||
g_logger.info(f'Exit {USB_DEV_PRODUCT} or disconnect your console at any time to close this script.')
|
||||
|
||||
return True
|
||||
|
||||
def usbRead(size, timeout=-1):
|
||||
rd = None
|
||||
def usbRead(size: int, timeout: int = -1) -> bytes:
|
||||
#assert g_logger is not None
|
||||
|
||||
rd = b''
|
||||
|
||||
try:
|
||||
# Convert read data to a bytes object for easier handling.
|
||||
rd = bytes(g_usbEpIn.read(size, timeout))
|
||||
except usb.core.USBError:
|
||||
if not g_cliMode: traceback.print_exc()
|
||||
if not g_cliMode:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
g_logger.error('\nUSB timeout triggered or console disconnected.')
|
||||
|
||||
return rd
|
||||
|
||||
def usbWrite(data, timeout=-1):
|
||||
def usbWrite(data: bytes, timeout: int = -1) -> int:
|
||||
#assert g_logger is not None
|
||||
|
||||
wr = 0
|
||||
|
||||
try:
|
||||
wr = g_usbEpOut.write(data, timeout)
|
||||
except usb.core.USBError:
|
||||
if not g_cliMode: traceback.print_exc()
|
||||
if not g_cliMode:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
g_logger.error('\nUSB timeout triggered or console disconnected.')
|
||||
|
||||
return wr
|
||||
|
||||
def usbSendStatus(code):
|
||||
def usbSendStatus(code: int) -> bool:
|
||||
status = struct.pack('<4sIH6p', USB_MAGIC_WORD, code, g_usbEpMaxPacketSize, b'')
|
||||
return usbWrite(status, USB_TRANSFER_TIMEOUT) == len(status)
|
||||
return bool(usbWrite(status, USB_TRANSFER_TIMEOUT) == len(status))
|
||||
|
||||
def usbHandleStartSession(cmd_block):
|
||||
global g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersion, g_nxdtGitCommit
|
||||
def usbHandleStartSession(cmd_block: bytes) -> int:
|
||||
global g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersionMajor, g_nxdtAbiVersionMinor, g_nxdtGitCommit
|
||||
|
||||
if g_cliMode: print()
|
||||
g_logger.debug('Received StartSession (%02X) command.' % (USB_CMD_START_SESSION))
|
||||
#assert g_logger is not None
|
||||
|
||||
if g_cliMode:
|
||||
print()
|
||||
|
||||
g_logger.debug(f'Received StartSession ({USB_CMD_START_SESSION:02X}) command.')
|
||||
|
||||
# Parse command block.
|
||||
(g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersion, g_nxdtGitCommit) = struct.unpack_from('<BBBB8s', cmd_block, 0)
|
||||
g_nxdtGitCommit = g_nxdtGitCommit.decode('utf-8').strip('\x00')
|
||||
(g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, abi_version, git_commit) = struct.unpack_from('<BBBB8s', cmd_block, 0)
|
||||
g_nxdtGitCommit = git_commit.decode('utf-8').strip('\x00')
|
||||
|
||||
# Unpack ABI version.
|
||||
g_nxdtAbiVersionMajor = ((abi_version >> 4) & 0x0F)
|
||||
g_nxdtAbiVersionMinor = (abi_version & 0x0F)
|
||||
|
||||
# Print client info.
|
||||
g_logger.info('Client info: nxdumptool v%u.%u.%u, ABI v%u (commit %s).\n' % (g_nxdtVersionMajor, g_nxdtVersionMinor, g_nxdtVersionMicro, g_nxdtAbiVersion, g_nxdtGitCommit))
|
||||
g_logger.info(f'Client info: {USB_DEV_PRODUCT} v{g_nxdtVersionMajor}.{g_nxdtVersionMinor}.{g_nxdtVersionMicro}, USB ABI v{g_nxdtAbiVersionMajor}.{g_nxdtAbiVersionMinor} (commit {g_nxdtGitCommit}).\n')
|
||||
|
||||
# Check if we support this ABI version.
|
||||
if g_nxdtAbiVersion != USB_ABI_VERSION:
|
||||
if (g_nxdtAbiVersionMajor != USB_ABI_VERSION_MAJOR) or (g_nxdtAbiVersionMinor != USB_ABI_VERSION_MINOR):
|
||||
g_logger.error('Unsupported ABI version!')
|
||||
return USB_STATUS_UNSUPPORTED_ABI_VERSION
|
||||
|
||||
# Return status code
|
||||
# Return status code.
|
||||
return USB_STATUS_SUCCESS
|
||||
|
||||
def usbHandleSendFileProperties(cmd_block):
|
||||
def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
||||
global g_nspTransferMode, g_nspSize, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath, g_outputDir, g_tkRoot, g_progressBarWindow
|
||||
|
||||
if g_cliMode and not g_nspTransferMode: print()
|
||||
g_logger.debug('Received SendFileProperties (%02X) command.' % (USB_CMD_SEND_FILE_PROPERTIES))
|
||||
#assert g_logger is not None
|
||||
#assert g_progressBarWindow is not None
|
||||
|
||||
if g_cliMode and not g_nspTransferMode:
|
||||
print()
|
||||
|
||||
g_logger.debug(f'Received SendFileProperties ({USB_CMD_SEND_FILE_PROPERTIES:02X}) command.')
|
||||
|
||||
# Parse command block.
|
||||
(file_size, filename_length, nsp_header_size, raw_filename) = struct.unpack_from('<QII%ds' % (USB_FILE_PROPERTIES_MAX_NAME_LENGTH), cmd_block, 0)
|
||||
(file_size, filename_length, nsp_header_size, raw_filename) = struct.unpack_from(f'<QII{USB_FILE_PROPERTIES_MAX_NAME_LENGTH}s', cmd_block, 0)
|
||||
filename = raw_filename.decode('utf-8').strip('\x00')
|
||||
|
||||
# Print info.
|
||||
dbg_str = ('File size: 0x%X | Filename length: 0x%X' % (file_size, filename_length))
|
||||
if nsp_header_size > 0: dbg_str += (' | NSP header size: 0x%X' % (nsp_header_size))
|
||||
dbg_str += '.'
|
||||
g_logger.debug(dbg_str)
|
||||
dbg_str = f'File size: 0x{file_size:X} | Filename length: 0x{filename_length:X}'
|
||||
if nsp_header_size > 0:
|
||||
dbg_str += f' | NSP header size: 0x{nsp_header_size:X}'
|
||||
g_logger.debug(dbg_str + '.')
|
||||
|
||||
file_type_str = ('file' if (not g_nspTransferMode) else 'NSP file entry')
|
||||
if not g_cliMode or (g_cliMode and not g_nspTransferMode): g_logger.info('Receiving %s: "%s".' % (file_type_str, filename))
|
||||
|
||||
# Perform integrity checks
|
||||
if not g_cliMode or (g_cliMode and not g_nspTransferMode):
|
||||
g_logger.info(f'Receiving {file_type_str}: "{filename}".')
|
||||
|
||||
# Perform validity checks.
|
||||
if (not g_nspTransferMode) and file_size and (nsp_header_size >= file_size):
|
||||
g_logger.error('NSP header size must be smaller than the full NSP size!\n')
|
||||
return USB_STATUS_MALFORMED_CMD
|
||||
|
@ -657,10 +754,10 @@ def usbHandleSendFileProperties(cmd_block):
|
|||
g_nspHeaderSize = nsp_header_size
|
||||
g_nspRemainingSize = (file_size - nsp_header_size)
|
||||
g_nspFile = None
|
||||
g_nspFilePath = None
|
||||
g_nspFilePath = ''
|
||||
g_logger.debug('NSP transfer mode enabled!\n')
|
||||
|
||||
# Perform additional integrity checks and get a file object to work with.
|
||||
# Perform additional validity checks and get a file object to work with.
|
||||
if (not g_nspTransferMode) or (g_nspFile is None):
|
||||
# Generate full, absolute path to the destination file.
|
||||
fullpath = os.path.abspath(g_outputDir + os.path.sep + filename)
|
||||
|
@ -674,7 +771,7 @@ def usbHandleSendFileProperties(cmd_block):
|
|||
# Make sure the output filepath doesn't point to an existing directory.
|
||||
if os.path.exists(fullpath) and (not os.path.isfile(fullpath)):
|
||||
utilsResetNspInfo()
|
||||
g_logger.error('Output filepath points to an existing directory! ("%s").\n' % (fullpath))
|
||||
g_logger.error(f'Output filepath points to an existing directory! ("{fullpath}").\n')
|
||||
return USB_STATUS_HOST_IO_ERROR
|
||||
|
||||
# Make sure we have enough free space.
|
||||
|
@ -705,7 +802,8 @@ def usbHandleSendFileProperties(cmd_block):
|
|||
# Check if we're dealing with an empty file or with the first SendFileProperties command from a NSP.
|
||||
if (not file_size) or (g_nspTransferMode and file_size == g_nspSize):
|
||||
# Close file (if needed).
|
||||
if not g_nspTransferMode: file.close()
|
||||
if not g_nspTransferMode:
|
||||
file.close()
|
||||
|
||||
# Let the command handler take care of sending the status response for us.
|
||||
return USB_STATUS_SUCCESS
|
||||
|
@ -714,7 +812,7 @@ def usbHandleSendFileProperties(cmd_block):
|
|||
usbSendStatus(USB_STATUS_SUCCESS)
|
||||
|
||||
# Start data transfer stage.
|
||||
g_logger.debug('Data transfer started. Saving %s to: "%s".' % (file_type_str, fullpath))
|
||||
g_logger.debug(f'Data transfer started. Saving {file_type_str} to: "{fullpath}".')
|
||||
|
||||
offset = 0
|
||||
blksize = USB_TRANSFER_BLOCK_SIZE
|
||||
|
@ -729,7 +827,7 @@ def usbHandleSendFileProperties(cmd_block):
|
|||
idx = filename.rfind(os.path.sep)
|
||||
prefix_filename = (filename[idx+1:] if (idx >= 0) else filename)
|
||||
|
||||
prefix = ('Current %s: "%s".\n' % (file_type_str, prefix_filename))
|
||||
prefix = f'Current {file_type_str}: "{prefix_filename}".\n'
|
||||
prefix += 'Use your console to cancel the file transfer if you wish to do so.'
|
||||
|
||||
if (not g_nspTransferMode) or g_nspRemainingSize == (g_nspSize - g_nspHeaderSize):
|
||||
|
@ -756,7 +854,8 @@ def usbHandleSendFileProperties(cmd_block):
|
|||
file.close()
|
||||
os.remove(fullpath)
|
||||
utilsResetNspInfo()
|
||||
if use_pbar: g_progressBarWindow.end()
|
||||
if use_pbar:
|
||||
g_progressBarWindow.end()
|
||||
|
||||
# Start transfer process.
|
||||
start_time = time.time()
|
||||
|
@ -768,12 +867,13 @@ def usbHandleSendFileProperties(cmd_block):
|
|||
|
||||
# Set block size and handle Zero-Length Termination packet (if needed).
|
||||
rd_size = blksize
|
||||
if ((offset + blksize) >= file_size) and utilsIsValueAlignedToEndpointPacketSize(blksize): rd_size += 1
|
||||
if ((offset + blksize) >= file_size) and utilsIsValueAlignedToEndpointPacketSize(blksize):
|
||||
rd_size += 1
|
||||
|
||||
# Read current chunk.
|
||||
chunk = usbRead(rd_size, USB_TRANSFER_TIMEOUT)
|
||||
if chunk is None:
|
||||
g_logger.error('Failed to read 0x%X-byte long data chunk!' % (rd_size))
|
||||
if not chunk:
|
||||
g_logger.error(f'Failed to read 0x{rd_size:X}-byte long data chunk!')
|
||||
|
||||
# Cancel file transfer.
|
||||
cancelTransfer()
|
||||
|
@ -790,7 +890,7 @@ def usbHandleSendFileProperties(cmd_block):
|
|||
# Cancel file transfer.
|
||||
cancelTransfer()
|
||||
|
||||
g_logger.debug('Received CancelFileTransfer (%02X) command.' % (USB_CMD_CANCEL_FILE_TRANSFER))
|
||||
g_logger.debug(f'Received CancelFileTransfer ({USB_CMD_CANCEL_FILE_TRANSFER:02X}) command.')
|
||||
g_logger.warning('Transfer cancelled.')
|
||||
|
||||
# Let the command handler take care of sending the status response for us.
|
||||
|
@ -804,40 +904,47 @@ def usbHandleSendFileProperties(cmd_block):
|
|||
offset = (offset + chunk_size)
|
||||
|
||||
# Update remaining NSP data size.
|
||||
if g_nspTransferMode: g_nspRemainingSize -= chunk_size
|
||||
if g_nspTransferMode:
|
||||
g_nspRemainingSize -= chunk_size
|
||||
|
||||
# Update progress bar window (if needed).
|
||||
if use_pbar: g_progressBarWindow.update(chunk_size)
|
||||
if use_pbar:
|
||||
g_progressBarWindow.update(chunk_size)
|
||||
|
||||
elapsed_time = round(time.time() - start_time)
|
||||
g_logger.debug('File transfer successfully completed in %s!\n' % (tqdm.format_interval(elapsed_time)))
|
||||
g_logger.debug(f'File transfer successfully completed in {tqdm.format_interval(elapsed_time)}!\n')
|
||||
|
||||
# Close file handle (if needed).
|
||||
if not g_nspTransferMode: file.close()
|
||||
if not g_nspTransferMode:
|
||||
file.close()
|
||||
|
||||
# Hide progress bar window (if needed).
|
||||
if use_pbar and ((not g_nspTransferMode) or (not g_nspRemainingSize)): g_progressBarWindow.end()
|
||||
if use_pbar and ((not g_nspTransferMode) or (not g_nspRemainingSize)):
|
||||
g_progressBarWindow.end()
|
||||
|
||||
return USB_STATUS_SUCCESS
|
||||
|
||||
def usbHandleSendNspHeader(cmd_block):
|
||||
def usbHandleSendNspHeader(cmd_block: bytes) -> int:
|
||||
global g_nspTransferMode, g_nspHeaderSize, g_nspRemainingSize, g_nspFile, g_nspFilePath
|
||||
|
||||
#assert g_logger is not None
|
||||
#assert g_nspFile is not None
|
||||
|
||||
nsp_header_size = len(cmd_block)
|
||||
|
||||
g_logger.debug('Received SendNspHeader (%02X) command.' % (USB_CMD_SEND_NSP_HEADER))
|
||||
g_logger.debug(f'Received SendNspHeader ({USB_CMD_SEND_NSP_HEADER:02X}) command.')
|
||||
|
||||
# Integrity checks.
|
||||
# Validity checks.
|
||||
if not g_nspTransferMode:
|
||||
g_logger.error('Received NSP header out of NSP transfer mode!\n')
|
||||
return USB_STATUS_MALFORMED_CMD
|
||||
|
||||
if g_nspRemainingSize:
|
||||
g_logger.error('Received NSP header before receiving all NSP data! (missing 0x%X byte[s]).\n' % (g_nspRemainingSize))
|
||||
g_logger.error(f'Received NSP header before receiving all NSP data! (missing 0x{g_nspRemainingSize:X} byte[s]).\n')
|
||||
return USB_STATUS_MALFORMED_CMD
|
||||
|
||||
if nsp_header_size != g_nspHeaderSize:
|
||||
g_logger.error('NSP header size mismatch! (0x%X != 0x%X).\n' % (nsp_header_size, g_nspHeaderSize))
|
||||
g_logger.error(f'NSP header size mismatch! (0x{nsp_header_size:X} != 0x{g_nspHeaderSize:X}).\n')
|
||||
return USB_STATUS_MALFORMED_CMD
|
||||
|
||||
# Write NSP header.
|
||||
|
@ -845,18 +952,21 @@ def usbHandleSendNspHeader(cmd_block):
|
|||
g_nspFile.write(cmd_block)
|
||||
g_nspFile.close()
|
||||
|
||||
g_logger.debug('Successfully wrote 0x%X-byte long NSP header to "%s".\n' % (nsp_header_size, g_nspFilePath))
|
||||
g_logger.debug(f'Successfully wrote 0x{nsp_header_size:X}-byte long NSP header to "{g_nspFilePath}".\n')
|
||||
|
||||
# Disable NSP transfer mode.
|
||||
utilsResetNspInfo()
|
||||
|
||||
return USB_STATUS_SUCCESS
|
||||
|
||||
def usbHandleEndSession(cmd_block):
|
||||
g_logger.debug('Received EndSession (%02X) command.' % (USB_CMD_END_SESSION))
|
||||
def usbHandleEndSession(cmd_block: bytes) -> int:
|
||||
#assert g_logger is not None
|
||||
g_logger.debug(f'Received EndSession ({USB_CMD_END_SESSION:02X}) command.')
|
||||
return USB_STATUS_SUCCESS
|
||||
|
||||
def usbCommandHandler():
|
||||
def usbCommandHandler() -> None:
|
||||
#assert g_logger is not None
|
||||
|
||||
# CancelFileTransfer is handled in usbHandleSendFileProperties().
|
||||
cmd_dict = {
|
||||
USB_CMD_START_SESSION: usbHandleStartSession,
|
||||
|
@ -874,6 +984,8 @@ def usbCommandHandler():
|
|||
|
||||
if not g_cliMode:
|
||||
# Update UI.
|
||||
#assert g_tkCanvas is not None
|
||||
#assert g_tkServerButton is not None
|
||||
g_tkCanvas.itemconfigure(g_tkTipMessage, state='normal', text=SERVER_STOP_MSG)
|
||||
g_tkServerButton.configure(state='disabled')
|
||||
|
||||
|
@ -883,8 +995,8 @@ def usbCommandHandler():
|
|||
while True:
|
||||
# Read command header.
|
||||
cmd_header = usbRead(USB_CMD_HEADER_SIZE)
|
||||
if (cmd_header is None) or (len(cmd_header) != USB_CMD_HEADER_SIZE):
|
||||
g_logger.error('Failed to read 0x%X-byte long command header!' % (USB_CMD_HEADER_SIZE))
|
||||
if (not cmd_header) or (len(cmd_header) != USB_CMD_HEADER_SIZE):
|
||||
g_logger.error(f'Failed to read 0x{USB_CMD_HEADER_SIZE:X}-byte long command header!')
|
||||
break
|
||||
|
||||
# Parse command header.
|
||||
|
@ -892,7 +1004,7 @@ def usbCommandHandler():
|
|||
|
||||
# Read command block right away (if needed).
|
||||
# nxdumptool expects us to read it right after sending the command header.
|
||||
cmd_block = None
|
||||
cmd_block: bytes = b''
|
||||
if cmd_block_size:
|
||||
# Handle Zero-Length Termination packet (if needed).
|
||||
if utilsIsValueAlignedToEndpointPacketSize(cmd_block_size):
|
||||
|
@ -901,8 +1013,8 @@ def usbCommandHandler():
|
|||
rd_size = cmd_block_size
|
||||
|
||||
cmd_block = usbRead(rd_size, USB_TRANSFER_TIMEOUT)
|
||||
if (cmd_block is None) or (len(cmd_block) != cmd_block_size):
|
||||
g_logger.error('Failed to read 0x%X-byte long command block for command ID %02X!' % (cmd_block_size, cmd_id))
|
||||
if (not cmd_block) or (len(cmd_block) != cmd_block_size):
|
||||
g_logger.error(f'Failed to read 0x{cmd_block_size:X}-byte long command block for command ID {cmd_id:02X}!')
|
||||
break
|
||||
|
||||
# Verify magic word.
|
||||
|
@ -914,7 +1026,7 @@ def usbCommandHandler():
|
|||
# Get command handler function.
|
||||
cmd_func = cmd_dict.get(cmd_id, None)
|
||||
if cmd_func is None:
|
||||
g_logger.error('Received command header with unsupported ID %02X.\n' % (cmd_id))
|
||||
g_logger.error(f'Received command header with unsupported ID {cmd_id:02X}.\n')
|
||||
usbSendStatus(USB_STATUS_UNSUPPORTED_CMD)
|
||||
continue
|
||||
|
||||
|
@ -922,7 +1034,7 @@ def usbCommandHandler():
|
|||
if (cmd_id == USB_CMD_START_SESSION and cmd_block_size != USB_CMD_BLOCK_SIZE_START_SESSION) or \
|
||||
(cmd_id == USB_CMD_SEND_FILE_PROPERTIES and cmd_block_size != USB_CMD_BLOCK_SIZE_SEND_FILE_PROPERTIES) or \
|
||||
(cmd_id == USB_CMD_SEND_NSP_HEADER and not cmd_block_size):
|
||||
g_logger.error('Invalid command block size for command ID %02X! (0x%X).\n' % (cmd_id, cmd_block_size))
|
||||
g_logger.error(f'Invalid command block size for command ID {cmd_id:02X}! (0x{cmd_block_size:X}).\n')
|
||||
usbSendStatus(USB_STATUS_MALFORMED_CMD)
|
||||
continue
|
||||
|
||||
|
@ -938,13 +1050,16 @@ def usbCommandHandler():
|
|||
# Update UI.
|
||||
uiToggleElements(True)
|
||||
|
||||
def uiStopServer():
|
||||
def uiStopServer() -> None:
|
||||
# Signal the shared stop event.
|
||||
#assert g_stopEvent is not None
|
||||
g_stopEvent.set()
|
||||
|
||||
def uiStartServer():
|
||||
def uiStartServer() -> None:
|
||||
global g_outputDir
|
||||
|
||||
#assert g_tkDirText is not None
|
||||
|
||||
g_outputDir = g_tkDirText.get('1.0', tk.END).strip()
|
||||
if not g_outputDir:
|
||||
# We should never reach this, honestly.
|
||||
|
@ -955,7 +1070,7 @@ def uiStartServer():
|
|||
try:
|
||||
os.makedirs(g_outputDir, exist_ok=True)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
messagebox.showerror('Error', 'Unable to create full output directory tree!', parent=g_tkRoot)
|
||||
return
|
||||
|
||||
|
@ -966,14 +1081,21 @@ def uiStartServer():
|
|||
server_thread = threading.Thread(target=usbCommandHandler, daemon=True)
|
||||
server_thread.start()
|
||||
|
||||
def uiToggleElements(enable):
|
||||
if enable:
|
||||
def uiToggleElements(flag: bool) -> None:
|
||||
#assert g_tkRoot is not None
|
||||
#assert g_tkChooseDirButton is not None
|
||||
#assert g_tkServerButton is not None
|
||||
#assert g_tkCanvas is not None
|
||||
|
||||
if flag:
|
||||
g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol)
|
||||
|
||||
g_tkChooseDirButton.configure(state='normal')
|
||||
g_tkServerButton.configure(text='Start server', command=uiStartServer, state='normal')
|
||||
g_tkCanvas.itemconfigure(g_tkTipMessage, state='hidden', text='')
|
||||
else:
|
||||
#assert g_tkScrolledTextLog is not None
|
||||
|
||||
g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocolStub)
|
||||
|
||||
g_tkChooseDirButton.configure(state='disabled')
|
||||
|
@ -984,26 +1106,29 @@ def uiToggleElements(enable):
|
|||
g_tkScrolledTextLog.delete('1.0', tk.END)
|
||||
g_tkScrolledTextLog.configure(state='disabled')
|
||||
|
||||
def uiChooseDirectory():
|
||||
def uiChooseDirectory() -> None:
|
||||
dir = filedialog.askdirectory(parent=g_tkRoot, title='Select an output directory', initialdir=INITIAL_DIR, mustexist=True)
|
||||
if dir: uiUpdateDirectoryField(os.path.abspath(dir))
|
||||
if dir:
|
||||
uiUpdateDirectoryField(os.path.abspath(dir))
|
||||
|
||||
def uiUpdateDirectoryField(dir):
|
||||
def uiUpdateDirectoryField(path: str) -> None:
|
||||
#assert g_tkDirText is not None
|
||||
g_tkDirText.configure(state='normal')
|
||||
g_tkDirText.delete('1.0', tk.END)
|
||||
g_tkDirText.insert('1.0', dir)
|
||||
g_tkDirText.insert('1.0', path)
|
||||
g_tkDirText.configure(state='disabled')
|
||||
|
||||
def uiHandleExitProtocol():
|
||||
def uiHandleExitProtocol() -> None:
|
||||
#assert g_tkRoot is not None
|
||||
g_tkRoot.destroy()
|
||||
|
||||
def uiHandleExitProtocolStub():
|
||||
def uiHandleExitProtocolStub() -> None:
|
||||
pass
|
||||
|
||||
def uiScaleMeasure(measure):
|
||||
def uiScaleMeasure(measure: int) -> int:
|
||||
return round(float(measure) * SCALE)
|
||||
|
||||
def uiInitialize():
|
||||
def uiInitialize() -> None:
|
||||
global SCALE
|
||||
global g_tkRoot, g_tkCanvas, g_tkDirText, g_tkChooseDirButton, g_tkServerButton, g_tkTipMessage, g_tkScrolledTextLog
|
||||
global g_stopEvent, g_tlb, g_taskbar, g_progressBarWindow
|
||||
|
@ -1012,17 +1137,18 @@ def uiInitialize():
|
|||
g_stopEvent = threading.Event()
|
||||
|
||||
# Enable high DPI scaling under Windows (if possible).
|
||||
# This will remove the blur caused by bilineal filtering when automatic scaling is carried out by Windows itself.
|
||||
dpi_aware = False
|
||||
if g_isWindowsVista:
|
||||
try:
|
||||
import ctypes
|
||||
dpi_aware = (ctypes.windll.user32.SetProcessDPIAware() == 1)
|
||||
if not dpi_aware: dpi_aware = (ctypes.windll.shcore.SetProcessDpiAwareness(1) == 0)
|
||||
if not dpi_aware:
|
||||
dpi_aware = (ctypes.windll.shcore.SetProcessDpiAwareness(1) == 0)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
# Enable taskbar features under Windows (if possible).
|
||||
g_tlb = g_taskbar = None
|
||||
del_tlb = False
|
||||
|
||||
if g_isWindows7:
|
||||
|
@ -1039,22 +1165,23 @@ def uiInitialize():
|
|||
g_taskbar = cc.CreateObject('{56FDF344-FD6D-11D0-958A-006097C9A090}', interface=g_tlb.ITaskbarList3)
|
||||
g_taskbar.HrInit()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
if del_tlb: os.remove(TASKBAR_LIB_PATH)
|
||||
if del_tlb:
|
||||
os.remove(TASKBAR_LIB_PATH)
|
||||
|
||||
# Create root Tkinter object.
|
||||
g_tkRoot = tk.Tk()
|
||||
g_tkRoot = tk.Tk(className=SCRIPT_TITLE)
|
||||
g_tkRoot.title(SCRIPT_TITLE)
|
||||
g_tkRoot.protocol('WM_DELETE_WINDOW', uiHandleExitProtocol)
|
||||
g_tkRoot.resizable(False, False)
|
||||
|
||||
# Set window icon.
|
||||
try:
|
||||
icon_image = tk.PhotoImage(data=APP_ICON)
|
||||
icon_image = tk.PhotoImage(data=base64.b64decode(APP_ICON))
|
||||
g_tkRoot.wm_iconphoto(True, icon_image)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
# Get screen resolution.
|
||||
screen_width_px = g_tkRoot.winfo_screenwidth()
|
||||
|
@ -1064,16 +1191,28 @@ def uiInitialize():
|
|||
screen_dpi = round(g_tkRoot.winfo_fpixels('1i'))
|
||||
|
||||
# Update scaling factor (if needed).
|
||||
if g_isWindowsVista and dpi_aware: SCALE = (float(screen_dpi) / WINDOWS_SCALING_FACTOR)
|
||||
if g_isWindowsVista and dpi_aware:
|
||||
SCALE = (float(screen_dpi) / WINDOWS_SCALING_FACTOR)
|
||||
|
||||
# Determine window size.
|
||||
window_width_px = uiScaleMeasure(WINDOW_WIDTH)
|
||||
window_height_px = uiScaleMeasure(WINDOW_HEIGHT)
|
||||
|
||||
# Retrieve and configure the default font.
|
||||
default_font = font.nametofont('TkDefaultFont')
|
||||
default_font_family = ('Segoe UI' if g_isWindows else 'sans-serif')
|
||||
default_font_size = (-12 if g_isWindows else -10) # Measured in pixels. Reference: https://docs.python.org/3/library/tkinter.font.html
|
||||
default_font.configure(family=default_font_family, size=uiScaleMeasure(default_font_size), weight=font.NORMAL)
|
||||
|
||||
"""print(screen_width_px, screen_height_px)
|
||||
print(screen_dpi)
|
||||
print(window_width_px, window_height_px)
|
||||
print(default_font.cget('family'), default_font.cget('size'))"""
|
||||
|
||||
# Center window.
|
||||
pos_hor = int((screen_width_px / 2) - (window_width_px / 2))
|
||||
pos_ver = int((screen_height_px / 2) - (window_height_px / 2))
|
||||
g_tkRoot.geometry("{}x{}+{}+{}".format(window_width_px, window_height_px, pos_hor, pos_ver))
|
||||
g_tkRoot.geometry(f'{window_width_px}x{window_height_px}+{pos_hor}+{pos_ver}')
|
||||
|
||||
# Create canvas and fill it with window elements.
|
||||
g_tkCanvas = tk.Canvas(g_tkRoot, width=window_width_px, height=window_height_px)
|
||||
|
@ -1081,7 +1220,7 @@ def uiInitialize():
|
|||
|
||||
g_tkCanvas.create_text(uiScaleMeasure(60), uiScaleMeasure(30), text='Output directory:', anchor=tk.CENTER)
|
||||
|
||||
g_tkDirText = tk.Text(g_tkRoot, height=1, width=45, font=font.nametofont('TkDefaultFont'), wrap='none', state='disabled', bg='#F0F0F0')
|
||||
g_tkDirText = tk.Text(g_tkRoot, height=1, width=45, font=default_font, wrap='none', state='disabled', bg='#F0F0F0')
|
||||
uiUpdateDirectoryField(g_outputDir)
|
||||
g_tkCanvas.create_window(uiScaleMeasure(260), uiScaleMeasure(30), window=g_tkDirText, anchor=tk.CENTER)
|
||||
|
||||
|
@ -1089,18 +1228,18 @@ def uiInitialize():
|
|||
g_tkCanvas.create_window(uiScaleMeasure(450), uiScaleMeasure(30), window=g_tkChooseDirButton, anchor=tk.CENTER)
|
||||
|
||||
g_tkServerButton = tk.Button(g_tkRoot, text='Start server', width=15, command=uiStartServer)
|
||||
g_tkCanvas.create_window(uiScaleMeasure(WINDOW_WIDTH / 2), uiScaleMeasure(70), window=g_tkServerButton, anchor=tk.CENTER)
|
||||
g_tkCanvas.create_window(uiScaleMeasure(int(WINDOW_WIDTH / 2)), uiScaleMeasure(70), window=g_tkServerButton, anchor=tk.CENTER)
|
||||
|
||||
g_tkTipMessage = g_tkCanvas.create_text(uiScaleMeasure(WINDOW_WIDTH / 2), uiScaleMeasure(100), anchor=tk.CENTER)
|
||||
g_tkTipMessage = g_tkCanvas.create_text(uiScaleMeasure(int(WINDOW_WIDTH / 2)), uiScaleMeasure(100), anchor=tk.CENTER)
|
||||
g_tkCanvas.itemconfigure(g_tkTipMessage, state='hidden', text='')
|
||||
|
||||
g_tkScrolledTextLog = scrolledtext.ScrolledText(g_tkRoot, height=20, width=65, font=font.nametofont('TkDefaultFont'), wrap=tk.WORD, state='disabled')
|
||||
g_tkScrolledTextLog = scrolledtext.ScrolledText(g_tkRoot, height=20, width=65, font=default_font, wrap=tk.WORD, state='disabled')
|
||||
g_tkScrolledTextLog.tag_config('DEBUG', foreground='gray')
|
||||
g_tkScrolledTextLog.tag_config('INFO', foreground='black')
|
||||
g_tkScrolledTextLog.tag_config('WARNING', foreground='orange')
|
||||
g_tkScrolledTextLog.tag_config('ERROR', foreground='red')
|
||||
g_tkScrolledTextLog.tag_config('CRITICAL', foreground='red', underline=1)
|
||||
g_tkCanvas.create_window(uiScaleMeasure(WINDOW_WIDTH / 2), uiScaleMeasure(280), window=g_tkScrolledTextLog, anchor=tk.CENTER)
|
||||
g_tkScrolledTextLog.tag_config('CRITICAL', foreground='red', underline=True)
|
||||
g_tkCanvas.create_window(uiScaleMeasure(int(WINDOW_WIDTH / 2)), uiScaleMeasure(280), window=g_tkScrolledTextLog, anchor=tk.CENTER)
|
||||
|
||||
g_tkCanvas.create_text(uiScaleMeasure(5), uiScaleMeasure(WINDOW_HEIGHT - 10), text=COPYRIGHT_TEXT, anchor=tk.W)
|
||||
|
||||
|
@ -1115,9 +1254,11 @@ def uiInitialize():
|
|||
g_tkRoot.lift()
|
||||
g_tkRoot.mainloop()
|
||||
|
||||
def cliInitialize():
|
||||
def cliInitialize() -> None:
|
||||
global g_progressBarWindow
|
||||
|
||||
#assert g_logger is not None
|
||||
|
||||
# Initialize console logger.
|
||||
console = LogConsole()
|
||||
|
||||
|
@ -1132,7 +1273,7 @@ def cliInitialize():
|
|||
# Start USB command handler directly.
|
||||
usbCommandHandler()
|
||||
|
||||
def main():
|
||||
def main() -> int:
|
||||
global g_cliMode, g_outputDir, g_osType, g_osVersion, g_isWindows, g_isWindowsVista, g_isWindows7, g_logger
|
||||
|
||||
# Disable warnings.
|
||||
|
@ -1178,13 +1319,20 @@ def main():
|
|||
# Initialize UI.
|
||||
uiInitialize()
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
ret: int = 1
|
||||
|
||||
try:
|
||||
main()
|
||||
ret = main()
|
||||
except KeyboardInterrupt:
|
||||
if g_cliMode:
|
||||
print('\nScript interrupted.')
|
||||
try:
|
||||
sys.exit(0)
|
||||
except SystemExit:
|
||||
os._exit(0)
|
||||
time.sleep(0.2)
|
||||
print('\nScript interrupted.')
|
||||
except Exception as e:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
try:
|
||||
sys.exit(ret)
|
||||
except SystemExit:
|
||||
os._exit(ret)
|
||||
|
|
|
@ -174,22 +174,19 @@ typedef enum {
|
|||
} GameCardCompatibilityType;
|
||||
|
||||
/// Encrypted using AES-128-CBC with the XCI header key (found in FS program memory under HOS 9.0.0+) and the IV from `GameCardHeader`.
|
||||
/// Key hashes for documentation purposes:
|
||||
/// Production XCI header key hash: 2E36CC55157A351090A73E7AE77CF581F69B0B6E48FB066C984879A6ED7D2E96
|
||||
/// Development XCI header key hash: 61D5C02244188810E2E3DE69341AC0F3C7653D370C6D3F77CA82B0B7E59F39AD
|
||||
typedef struct {
|
||||
u64 fw_version; ///< GameCardFwVersion.
|
||||
u32 acc_ctrl_1; ///< GameCardAccCtrl1.
|
||||
u32 wait_1_time_read; ///< Always 0x1388.
|
||||
u32 wait_2_time_read; ///< Always 0.
|
||||
u32 wait_1_time_write; ///< Always 0.
|
||||
u32 wait_2_time_write; ///< Always 0.
|
||||
SdkAddOnVersion fw_mode; ///< Current SdkAddOnVersion.
|
||||
Version upp_version; ///< Bundled system update version.
|
||||
u8 compatibility_type; ///< GameCardCompatibilityType.
|
||||
u64 fw_version; ///< GameCardFwVersion.
|
||||
u32 acc_ctrl_1; ///< GameCardAccCtrl1.
|
||||
u32 wait_1_time_read; ///< Always 0x1388.
|
||||
u32 wait_2_time_read; ///< Always 0.
|
||||
u32 wait_1_time_write; ///< Always 0.
|
||||
u32 wait_2_time_write; ///< Always 0.
|
||||
Version fw_mode; ///< Current SDK version.
|
||||
Version upp_version; ///< Bundled system update version.
|
||||
u8 compatibility_type; ///< GameCardCompatibilityType.
|
||||
u8 reserved_1[0x3];
|
||||
u64 upp_hash; ///< SHA-256 (?) checksum for the update partition. The exact way it's calculated is currently unknown.
|
||||
u64 upp_id; ///< Must match GAMECARD_UPDATE_TID.
|
||||
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[0x38];
|
||||
} GameCardInfo;
|
||||
|
||||
|
@ -233,16 +230,6 @@ typedef enum {
|
|||
GameCardStatus_InsertedAndInfoLoaded = 5 ///< A gamecard has been inserted and all required information could be successfully retrieved from it.
|
||||
} GameCardStatus;
|
||||
|
||||
typedef enum {
|
||||
GameCardHashFileSystemPartitionType_None = 0, ///< Not a real value.
|
||||
GameCardHashFileSystemPartitionType_Root = 1,
|
||||
GameCardHashFileSystemPartitionType_Update = 2,
|
||||
GameCardHashFileSystemPartitionType_Logo = 3, ///< Only available in GameCardFwVersion_Since400NUP or greater gamecards.
|
||||
GameCardHashFileSystemPartitionType_Normal = 4,
|
||||
GameCardHashFileSystemPartitionType_Secure = 5,
|
||||
GameCardHashFileSystemPartitionType_Count = 6 ///< Total values supported by this enum.
|
||||
} GameCardHashFileSystemPartitionType;
|
||||
|
||||
typedef enum {
|
||||
LotusAsicFirmwareType_ReadFw = 0xFF,
|
||||
LotusAsicFirmwareType_ReadDevFw = 0xFFFF,
|
||||
|
@ -295,7 +282,7 @@ u8 gamecardGetStatus(void);
|
|||
|
||||
/// Fills the provided GameCardSecurityInformation pointer.
|
||||
/// This area can't be read using gamecardReadStorage().
|
||||
bool gamecardGetSecurityInformation(GameCardSecurityInformation* out);
|
||||
bool gamecardGetSecurityInformation(GameCardSecurityInformation *out);
|
||||
|
||||
/// Fills the provided FsGameCardIdSet pointer.
|
||||
/// This area can't be read using gamecardReadStorage().
|
||||
|
|
|
@ -50,10 +50,20 @@ typedef struct {
|
|||
|
||||
NXDT_ASSERT(HashFileSystemEntry, 0x40);
|
||||
|
||||
typedef enum {
|
||||
HashFileSystemPartitionType_None = 0, ///< Not a real value.
|
||||
HashFileSystemPartitionType_Root = 1,
|
||||
HashFileSystemPartitionType_Update = 2,
|
||||
HashFileSystemPartitionType_Logo = 3, ///< Only available in GameCardFwVersion_Since400NUP or greater gamecards.
|
||||
HashFileSystemPartitionType_Normal = 4,
|
||||
HashFileSystemPartitionType_Secure = 5,
|
||||
HashFileSystemPartitionType_Count = 6 ///< Total values supported by this enum.
|
||||
} HashFileSystemPartitionType;
|
||||
|
||||
/// Internally used by gamecard functions.
|
||||
/// Use gamecardGetHashFileSystemContext() to retrieve a Hash FS context.
|
||||
typedef struct {
|
||||
u8 type; ///< GameCardHashFileSystemPartitionType.
|
||||
u8 type; ///< HashFileSystemPartitionType.
|
||||
char *name; ///< Dynamically allocated partition name.
|
||||
u64 offset; ///< Partition offset (relative to the start of gamecard image).
|
||||
u64 size; ///< Partition size.
|
||||
|
@ -61,9 +71,6 @@ typedef struct {
|
|||
u8 *header; ///< HashFileSystemHeader + (HashFileSystemEntry * entry_count) + Name Table.
|
||||
} HashFileSystemContext;
|
||||
|
||||
/// Retrieves a Hash FS entry index by its name.
|
||||
bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *out_idx);
|
||||
|
||||
/// Reads raw partition data using a Hash FS context.
|
||||
/// Input offset must be relative to the start of the Hash FS.
|
||||
bool hfsReadPartitionData(HashFileSystemContext *ctx, void *out, u64 read_size, u64 offset);
|
||||
|
@ -73,8 +80,16 @@ bool hfsReadPartitionData(HashFileSystemContext *ctx, void *out, u64 read_size,
|
|||
bool hfsReadEntryData(HashFileSystemContext *ctx, HashFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Calculates the extracted Hash FS size.
|
||||
/// If the target partition is empty, 'out_size' will be set to zero and true will be returned.
|
||||
bool hfsGetTotalDataSize(HashFileSystemContext *ctx, u64 *out_size);
|
||||
|
||||
/// Retrieves a Hash FS entry index by its name.
|
||||
bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *out_idx);
|
||||
|
||||
/// Takes a HashFileSystemPartitionType value. Returns a pointer to a string that represents the partition name that matches the provided Hash FS partition type.
|
||||
/// Returns NULL if the provided value is out of range.
|
||||
const char *hfsGetPartitionNameString(u8 hfs_partition_type);
|
||||
|
||||
/// Miscellaneous functions.
|
||||
|
||||
NX_INLINE void hfsFreeContext(HashFileSystemContext *ctx)
|
||||
|
@ -85,6 +100,11 @@ NX_INLINE void hfsFreeContext(HashFileSystemContext *ctx)
|
|||
memset(ctx, 0, sizeof(HashFileSystemContext));
|
||||
}
|
||||
|
||||
NX_INLINE bool hfsIsValidContext(HashFileSystemContext *ctx)
|
||||
{
|
||||
return (ctx && ctx->type > HashFileSystemPartitionType_None && ctx->type < HashFileSystemPartitionType_Count && ctx->name && ctx->size && ctx->header_size && ctx->header);
|
||||
}
|
||||
|
||||
NX_INLINE u32 hfsGetEntryCount(HashFileSystemContext *ctx)
|
||||
{
|
||||
if (!ctx || !ctx->header_size || !ctx->header) return 0;
|
||||
|
|
|
@ -92,7 +92,7 @@ typedef enum {
|
|||
NcaKeyGeneration_Since1300NUP = 13, ///< 13.0.0 - 13.2.1.
|
||||
NcaKeyGeneration_Since1400NUP = 14, ///< 14.0.0 - 14.1.2.
|
||||
NcaKeyGeneration_Since1500NUP = 15, ///< 15.0.0 - 15.0.1.
|
||||
NcaKeyGeneration_Since1600NUP = 16, ///< 16.0.0 - 16.0.1.
|
||||
NcaKeyGeneration_Since1600NUP = 16, ///< 16.0.0 - 16.0.3.
|
||||
NcaKeyGeneration_Current = NcaKeyGeneration_Since1600NUP,
|
||||
NcaKeyGeneration_Max = 32
|
||||
} NcaKeyGeneration;
|
||||
|
@ -108,7 +108,7 @@ typedef enum {
|
|||
/// TODO: update on signature keygen changes.
|
||||
typedef enum {
|
||||
NcaSignatureKeyGeneration_Since100NUP = 0, ///< 1.0.0 - 8.1.1.
|
||||
NcaSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 16.0.1.
|
||||
NcaSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 16.0.3.
|
||||
NcaSignatureKeyGeneration_Current = NcaSignatureKeyGeneration_Since900NUP,
|
||||
NcaSignatureKeyGeneration_Max = (NcaSignatureKeyGeneration_Current + 1)
|
||||
} NcaSignatureKeyGeneration;
|
||||
|
@ -158,7 +158,7 @@ typedef struct {
|
|||
u64 content_size;
|
||||
u64 program_id;
|
||||
u32 content_index;
|
||||
SdkAddOnVersion sdk_addon_version;
|
||||
Version sdk_addon_version;
|
||||
u8 key_generation; ///< NcaKeyGeneration. Uses NcaKeyGeneration_Since301NUP or greater values.
|
||||
u8 main_signature_key_generation; ///< NcaSignatureKeyGeneration.
|
||||
u8 reserved[0xE];
|
||||
|
@ -483,7 +483,7 @@ bool ncaAllocateCryptoBuffer(void);
|
|||
void ncaFreeCryptoBuffer(void);
|
||||
|
||||
/// Initializes a NCA context.
|
||||
/// If 'storage_id' == NcmStorageId_GameCard, the 'hfs_partition_type' argument must be a valid GameCardHashFileSystemPartitionType value.
|
||||
/// If 'storage_id' == NcmStorageId_GameCard, the 'hfs_partition_type' argument must be a valid HashFileSystemPartitionType value.
|
||||
/// If the NCA holds a populated Rights ID field, ticket data will need to be retrieved.
|
||||
/// If the 'tik' argument points to a valid Ticket element, it will either be updated (if it's empty) or used to read ticket data that has already been retrieved.
|
||||
/// If the 'tik' argument is NULL, the function will just retrieve the necessary ticket data on its own.
|
||||
|
|
|
@ -43,7 +43,7 @@ extern "C" {
|
|||
/// TODO: update on signature keygen changes.
|
||||
typedef enum {
|
||||
NpdmSignatureKeyGeneration_Since100NUP = 0, ///< 1.0.0 - 8.1.1.
|
||||
NpdmSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 16.0.1.
|
||||
NpdmSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 16.0.3.
|
||||
NpdmSignatureKeyGeneration_Current = NpdmSignatureKeyGeneration_Since900NUP,
|
||||
NpdmSignatureKeyGeneration_Max = (NpdmSignatureKeyGeneration_Current + 1)
|
||||
} NpdmSignatureKeyGeneration;
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <dirent.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
@ -76,10 +77,8 @@
|
|||
#define LZ4_STATIC_LINKING_ONLY /* Required by LZ4 to enable in-place decompression. */
|
||||
#include "lz4.h"
|
||||
|
||||
/// Used to store version numbers expressed in dot notation:
|
||||
/// * System version: "{major}.{minor}.{micro}-{major_relstep}.{minor_relstep}".
|
||||
/// * Application version: "{release}.{private}".
|
||||
/// Referenced by multiple header files.
|
||||
/// Used to store version numbers expressed in dot notation: "{major}.{minor}.{micro}-{major_relstep}.{minor_relstep}".
|
||||
/// Used by system version fields.
|
||||
typedef struct {
|
||||
union {
|
||||
u32 value;
|
||||
|
@ -89,18 +88,28 @@ typedef struct {
|
|||
u32 micro : 4;
|
||||
u32 minor : 6;
|
||||
u32 major : 6;
|
||||
} system_version;
|
||||
struct {
|
||||
u32 private_ver : 16;
|
||||
u32 release_ver : 16;
|
||||
} application_version;
|
||||
};
|
||||
};
|
||||
} Version;
|
||||
} SystemVersion;
|
||||
|
||||
NXDT_ASSERT(Version, 0x4);
|
||||
NXDT_ASSERT(SystemVersion, 0x4);
|
||||
|
||||
/// Used to store version numbers expressed in dot notation: "{release}.{private}".
|
||||
/// Used by application version fields.
|
||||
typedef struct {
|
||||
union {
|
||||
u32 value;
|
||||
struct {
|
||||
u32 private_ver : 16;
|
||||
u32 release_ver : 16;
|
||||
};
|
||||
};
|
||||
} ApplicationVersion;
|
||||
|
||||
NXDT_ASSERT(ApplicationVersion, 0x4);
|
||||
|
||||
/// Used to store version numbers expressed in dot notation: "{major}.{minor}.{micro}-{relstep}".
|
||||
/// Only used by GameCardFwMode and NcaSdkAddOnVersion.
|
||||
/// Used by SDK version fields. This format was also used for system version fields prior to HOS 3.0.0.
|
||||
typedef struct {
|
||||
union {
|
||||
u32 value;
|
||||
|
@ -115,4 +124,16 @@ typedef struct {
|
|||
|
||||
NXDT_ASSERT(SdkAddOnVersion, 0x4);
|
||||
|
||||
/// Convenient wrapper for all version structs.
|
||||
typedef struct {
|
||||
union {
|
||||
u32 value;
|
||||
SystemVersion system_version;
|
||||
ApplicationVersion application_version;
|
||||
SdkAddOnVersion sdk_addon_version;
|
||||
};
|
||||
} Version;
|
||||
|
||||
NXDT_ASSERT(Version, 0x4);
|
||||
|
||||
#endif /* __NXDT_INCLUDES_H__ */
|
||||
|
|
|
@ -149,6 +149,10 @@ bool utilsCreateConcatenationFile(const char *path);
|
|||
/// If 'create_last_element' is true, the last element from the provided path will be created as well.
|
||||
void utilsCreateDirectoryTree(const char *path, bool create_last_element);
|
||||
|
||||
/// Recursively deletes the directory located at the provided path and all of its contents.
|
||||
/// The provided path must be absolute and it must include the virtual device name it belongs to (e.g. "sdmc:/path/to/dir").
|
||||
bool utilsDeleteDirectoryRecursively(const char *path);
|
||||
|
||||
/// Returns a pointer to a dynamically allocated string that holds the full path formed by the provided arguments. Both path prefix and file extension are optional.
|
||||
/// If any elements from the generated path exceed safe filesystem limits, each exceeding element will be truncated. Truncations, if needed, are performed on a per-codepoint basis (UTF-8).
|
||||
/// If an extension is provided, it will always be preserved, regardless of any possible truncations being carried out.
|
||||
|
|
|
@ -193,6 +193,7 @@ NX_INLINE void romfsFreeContext(RomFileSystemContext *ctx)
|
|||
}
|
||||
|
||||
/// Functions to reset the current directory/file entry offset.
|
||||
|
||||
NX_INLINE void romfsResetDirectoryTableOffset(RomFileSystemContext *ctx)
|
||||
{
|
||||
if (ctx) ctx->cur_dir_offset = 0;
|
||||
|
@ -211,6 +212,7 @@ NX_INLINE bool romfsIsValidContext(RomFileSystemContext *ctx)
|
|||
}
|
||||
|
||||
/// Functions to retrieve a directory/file entry.
|
||||
|
||||
NX_INLINE void *romfsGetEntryByOffset(RomFileSystemContext *ctx, void *entry_table, u64 entry_table_size, u64 entry_size, u64 entry_offset)
|
||||
{
|
||||
if (!romfsIsValidContext(ctx) || !entry_table || !entry_table_size || !entry_size || (entry_offset + entry_size) > entry_table_size) return NULL;
|
||||
|
@ -238,6 +240,7 @@ NX_INLINE RomFileSystemFileEntry *romfsGetCurrentFileEntry(RomFileSystemContext
|
|||
}
|
||||
|
||||
/// Functions to check if it's possible to move to the next directory/file entry based on the current directory/file entry offset.
|
||||
|
||||
NX_INLINE bool romfsCanMoveToNextEntry(RomFileSystemContext *ctx, void *entry_table, u64 entry_table_size, u64 entry_size, u64 entry_offset)
|
||||
{
|
||||
if (!romfsIsValidContext(ctx) || !entry_table || !entry_table_size || entry_size < 4 || (entry_offset + entry_size) > entry_table_size) return false;
|
||||
|
@ -256,6 +259,7 @@ NX_INLINE bool romfsCanMoveToNextFileEntry(RomFileSystemContext *ctx)
|
|||
}
|
||||
|
||||
/// Functions to update the current directory/file entry offset to make it point to the next directory/file entry.
|
||||
|
||||
NX_INLINE bool romfsMoveToNextEntry(RomFileSystemContext *ctx, void *entry_table, u64 entry_table_size, u64 entry_size, u64 *entry_offset)
|
||||
{
|
||||
if (!romfsIsValidContext(ctx) || !entry_table || !entry_table_size || entry_size < 4 || !entry_offset || (*entry_offset + entry_size) > entry_table_size) return false;
|
||||
|
@ -275,6 +279,7 @@ NX_INLINE bool romfsMoveToNextFileEntry(RomFileSystemContext *ctx)
|
|||
}
|
||||
|
||||
/// NCA patch management functions.
|
||||
|
||||
NX_INLINE void romfsWriteFileEntryPatchToMemoryBuffer(RomFileSystemContext *ctx, RomFileSystemFileEntryPatch *patch, void *buf, u64 buf_size, u64 buf_offset)
|
||||
{
|
||||
if (!romfsIsValidContext(ctx) || ctx->is_patch || ctx->default_storage_ctx->base_storage_type != NcaStorageBaseStorageType_Regular || !patch || \
|
||||
|
|
|
@ -36,13 +36,13 @@ extern "C" {
|
|||
#define RSA2048_PUBKEY_SIZE RSA2048_BYTES
|
||||
|
||||
/// Verifies a RSA-2048-PSS with SHA-256 signature.
|
||||
/// The provided signature and modulus should have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size);
|
||||
|
||||
/// Performs RSA-2048-OAEP decryption.
|
||||
/// Suitable to decrypt the titlekey block from tickets with personalized crypto.
|
||||
/// The provided signature and modulus should have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
/// The label and label_size arguments are optional - these may be set to NULL and 0 if not needed, respectively.
|
||||
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
/// 'label' and 'label_size' arguments are optional -- if not needed, these may be set to NULL and 0, respectively.
|
||||
bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \
|
||||
size_t private_exponent_size, const void *label, size_t label_size, size_t *out_size);
|
||||
|
||||
|
|
|
@ -34,13 +34,11 @@ extern "C" {
|
|||
#define SIGNED_TIK_MIN_SIZE sizeof(TikSigHmac160) /* Assuming no ESV1/ESV2 records are available. */
|
||||
|
||||
#define GENERATE_TIK_STRUCT(sigtype, tiksize) \
|
||||
\
|
||||
typedef struct { \
|
||||
SignatureBlock##sigtype sig_block; \
|
||||
TikCommonBlock tik_common_block; \
|
||||
u8 es_section_record_data[]; \
|
||||
} TikSig##sigtype; \
|
||||
\
|
||||
NXDT_ASSERT(TikSig##sigtype, tiksize);
|
||||
|
||||
typedef enum {
|
||||
|
@ -178,9 +176,10 @@ typedef struct {
|
|||
/// Titlekey is also RSA-OAEP unwrapped (if needed) and titlekek decrypted right away.
|
||||
bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gamecard);
|
||||
|
||||
/// Converts a TikTitleKeyType_Personalized ticket into a TikTitleKeyType_Common ticket and generates a raw certificate chain for the new signature issuer.
|
||||
/// Converts a TikTitleKeyType_Personalized ticket into a TikTitleKeyType_Common ticket and optionally generates a raw certificate chain for the new signature issuer.
|
||||
/// Bear in mind the 'size' member from the Ticket parameter will be updated by this function to remove any possible references to ESV1/ESV2 records.
|
||||
/// Raw certificate chain data will be saved to the provided pointers. certGenerateRawCertificateChainBySignatureIssuer() is used internally, so the output buffer must be freed by the user.
|
||||
/// If both 'out_raw_cert_chain' and 'out_raw_cert_chain_size' pointers are provided, raw certificate chain data will be saved to them.
|
||||
/// certGenerateRawCertificateChainBySignatureIssuer() is used internally, so the output buffer must be freed by the user.
|
||||
bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_chain, u64 *out_raw_cert_chain_size);
|
||||
|
||||
/// Helper inline functions.
|
||||
|
|
|
@ -54,32 +54,32 @@ void *usbAllocatePageAlignedBuffer(size_t size);
|
|||
/// Returns a value from the UsbHostSpeed enum.
|
||||
u8 usbIsReady(void);
|
||||
|
||||
/// Sends file properties to the host device before starting a file data transfer. Must be called before usbSendFileData().
|
||||
/// If 'nsp_header_size' is greater than zero, NSP transfer mode will be enabled. The file will be treated as a NSP and this value will be taken as its full Partition FS header size.
|
||||
/// Sends file properties to the host device before starting a file data transfer. If needed, it must be called before usbSendFileData().
|
||||
/// 'file_size' may be zero if an empty file shall be created, in which case no file data transfer will be necessary.
|
||||
/// Calling this function before finishing an ongoing file data transfer will result in an error.
|
||||
/// Under NSP transfer mode, this function must be called right before transferring data from each NSP file entry to the host device, which should in turn write it all to the same output file.
|
||||
/// Calling this function after NSP transfer mode has been enabled with a 'nsp_header_size' value greater than zero will result in an error.
|
||||
/// The host device should immediately write 'nsp_header_size' padding at the start of the output file and start listening for further usbSendFileProperties() calls, or a usbSendNspHeader() call.
|
||||
bool usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size);
|
||||
bool usbSendFileProperties(u64 file_size, const char *filename);
|
||||
|
||||
/// Performs a file data transfer. Must be continuously called after usbSendFileProperties() until all file data has been transferred.
|
||||
/// Sends NSP properties to the host device and enables NSP transfer mode. If needed, it must be called before usbSendFileData().
|
||||
/// Both 'nsp_size' and 'nsp_header_size' must be greater than zero. 'nsp_size' must also be greater than 'nsp_header_size'.
|
||||
/// Calling this function after NSP transfer mode has already been enabled will result in an error.
|
||||
/// The host device should immediately write 'nsp_header_size' padding at the start of the output file and start listening for further usbSendFileProperties() calls, or a usbSendNspHeader() call.
|
||||
bool usbSendNspProperties(u64 nsp_size, const char *filename, u32 nsp_header_size);
|
||||
|
||||
/// Performs a file data transfer. Must be continuously called after usbSendFileProperties() / usbSendNspProperties() until all file data has been transferred.
|
||||
/// Data chunk size must not exceed USB_TRANSFER_BUFFER_SIZE.
|
||||
/// If the last file data chunk is aligned to the endpoint max packet size, the host device should expect a Zero Length Termination (ZLT) packet.
|
||||
/// Calling this function if there's no remaining data to transfer will result in an error.
|
||||
bool usbSendFileData(void *data, u64 data_size);
|
||||
|
||||
/// Used to gracefully cancel an ongoing file transfer. The current USB session is kept alive.
|
||||
void usbCancelFileTransfer(void);
|
||||
|
||||
/// Sends NSP header data to the host device, making it rewind the NSP file pointer to write this data, essentially finishing the NSP transfer process.
|
||||
/// Must be called after the data from all NSP file entries has been transferred using both usbSendFileProperties() and usbSendFileData() calls.
|
||||
/// Must be called after the data from all NSP file entries has been transferred using both usbSendNspProperties() and usbSendFileData() calls.
|
||||
/// If the NSP header size is aligned to the endpoint max packet size, the host device should expect a Zero Length Termination (ZLT) packet.
|
||||
bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size);
|
||||
|
||||
/// Nice and small wrapper for non-NSP files.
|
||||
NX_INLINE bool usbSendFilePropertiesCommon(u64 file_size, const char *filename)
|
||||
{
|
||||
return usbSendFileProperties(file_size, filename, 0);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
#define NCA_PATH APP_BASE_PATH "NCA/"
|
||||
#define NCA_FS_PATH APP_BASE_PATH "NCA FS/"
|
||||
|
||||
#define CONFIG_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH "config.json"
|
||||
#define CONFIG_FILE_NAME APP_TITLE "_config.json"
|
||||
#define DEFAULT_CONFIG_PATH "romfs:/default_config.json"
|
||||
|
||||
#define NRO_NAME APP_TITLE ".nro"
|
||||
|
|
|
@ -221,7 +221,7 @@ typedef struct {
|
|||
|
||||
|
||||
|
||||
/* Directory object structure (DIR) */
|
||||
/* Directory object structure (FDIR) */
|
||||
|
||||
typedef struct {
|
||||
FFOBJID obj; /* Object identifier */
|
||||
|
@ -236,7 +236,7 @@ typedef struct {
|
|||
#if FF_USE_FIND
|
||||
const TCHAR* pat; /* Pointer to the name matching pattern */
|
||||
#endif
|
||||
} DIR;
|
||||
} FDIR;
|
||||
|
||||
|
||||
|
||||
|
@ -308,11 +308,11 @@ FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data t
|
|||
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
|
||||
FRESULT f_truncate (FIL* fp); /* Truncate the file */
|
||||
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
|
||||
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
|
||||
FRESULT f_closedir (DIR* dp); /* Close an open directory */
|
||||
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
|
||||
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
|
||||
FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
|
||||
FRESULT f_opendir (FDIR* dp, const TCHAR* path); /* Open a directory */
|
||||
FRESULT f_closedir (FDIR* dp); /* Close an open directory */
|
||||
FRESULT f_readdir (FDIR* dp, FILINFO* fno); /* Read a directory item */
|
||||
FRESULT f_findfirst (FDIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
|
||||
FRESULT f_findnext (FDIR* dp, FILINFO* fno); /* Find next file */
|
||||
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
|
||||
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
|
||||
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit f9f4aa9637f84aa89025d68a8ec15e5ff34d1537
|
||||
Subproject commit 21122223a496b313c2d45cb02cabfc0e4680e7e7
|
|
@ -7,7 +7,8 @@
|
|||
"keep_certificate": false,
|
||||
"trim_dump": false,
|
||||
"calculate_checksum": true,
|
||||
"checksum_lookup_method": 1
|
||||
"checksum_lookup_method": 1,
|
||||
"write_raw_hfs_partition": false
|
||||
},
|
||||
"nsp": {
|
||||
"set_download_distribution": false,
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
"lafw_version": "Required LAFW version",
|
||||
"lafw_version_value": "%lu or greater (%s)",
|
||||
"sdk_version": "SDK version",
|
||||
"compatibility_type": "Compatibility type"
|
||||
"compatibility_type": "Compatibility type",
|
||||
"package_id": "Package ID"
|
||||
},
|
||||
|
||||
"dump_options": "Dump options",
|
||||
|
|
|
@ -82,13 +82,14 @@ bool bfttfInitialize(void)
|
|||
for(u32 i = 0; i < g_fontInfoCount; i++)
|
||||
{
|
||||
BfttfFontInfo *font_info = &(g_fontInfo[i]);
|
||||
TitleInfo *title_info = NULL;
|
||||
RomFileSystemFileEntry *romfs_file_entry = NULL;
|
||||
|
||||
/* Check if the title ID for the current font container matches the one from the previous font container. */
|
||||
/* We won't have to reinitialize both NCA and RomFS contexts if that's the case. */
|
||||
if (font_info->title_id != prev_title_id)
|
||||
{
|
||||
TitleInfo *title_info = NULL;
|
||||
|
||||
/* Get title info. */
|
||||
if (!(title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, font_info->title_id)))
|
||||
{
|
||||
|
@ -98,8 +99,9 @@ bool bfttfInitialize(void)
|
|||
|
||||
/* Initialize NCA context. */
|
||||
/* NCA contexts don't need to be freed beforehand. */
|
||||
bool nca_ctx_init = ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), \
|
||||
title_info->version.value, NULL);
|
||||
/* Don't allow invalid NCA signatures. */
|
||||
bool nca_ctx_init = (ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), \
|
||||
title_info->version.value, NULL) && nca_ctx->valid_main_signature);
|
||||
|
||||
/* Free title info. */
|
||||
titleFreeTitleInfo(&title_info);
|
||||
|
|
|
@ -141,7 +141,7 @@ u8 *certRetrieveRawCertificateChainFromGameCardByRightsId(const FsRightsId *id,
|
|||
utilsGenerateHexStringFromData(raw_chain_filename, sizeof(raw_chain_filename), id->c, sizeof(id->c), false);
|
||||
strcat(raw_chain_filename, ".cert");
|
||||
|
||||
if (!gamecardGetHashFileSystemEntryInfoByName(GameCardHashFileSystemPartitionType_Secure, raw_chain_filename, &raw_chain_offset, &raw_chain_size))
|
||||
if (!gamecardGetHashFileSystemEntryInfoByName(HashFileSystemPartitionType_Secure, raw_chain_filename, &raw_chain_offset, &raw_chain_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!", raw_chain_filename);
|
||||
return NULL;
|
||||
|
|
|
@ -60,6 +60,7 @@ void configSet##functype(const char *path, vartype value) { \
|
|||
static Mutex g_configMutex = 0;
|
||||
static bool g_configInterfaceInit = false;
|
||||
|
||||
static char g_configJsonPath[FS_MAX_PATH] = {0};
|
||||
static struct json_object *g_configJson = NULL;
|
||||
|
||||
/* Function prototypes. */
|
||||
|
@ -118,10 +119,29 @@ CONFIG_SETTER(Integer, int);
|
|||
|
||||
static bool configParseConfigJson(void)
|
||||
{
|
||||
bool use_default_config = true, ret = false;
|
||||
bool use_default_config = true, use_root = true, ret = false;
|
||||
const char *launch_path = utilsGetLaunchPath();
|
||||
char *ptr1 = NULL, *ptr2 = NULL;
|
||||
|
||||
/* Generate config JSON path. */
|
||||
if (launch_path)
|
||||
{
|
||||
ptr1 = strchr(launch_path, '/');
|
||||
ptr2 = strrchr(launch_path, '/');
|
||||
|
||||
if (ptr1 && ptr2 && ptr1 != ptr2)
|
||||
{
|
||||
/* Use config JSON from the current working directory. */
|
||||
snprintf(g_configJsonPath, sizeof(g_configJsonPath), "%.*s" CONFIG_FILE_NAME, (int)((ptr2 - launch_path) + 1), launch_path);
|
||||
use_root = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Use config JSON from the SD card root directory. */
|
||||
if (use_root) sprintf(g_configJsonPath, DEVOPTAB_SDMC_DEVICE "/" CONFIG_FILE_NAME);
|
||||
|
||||
/* Read config JSON. */
|
||||
g_configJson = json_object_from_file(CONFIG_PATH);
|
||||
g_configJson = json_object_from_file(g_configJsonPath);
|
||||
if (g_configJson)
|
||||
{
|
||||
/* Validate configuration. */
|
||||
|
@ -157,7 +177,7 @@ static bool configParseConfigJson(void)
|
|||
static void configWriteConfigJson(void)
|
||||
{
|
||||
if (!g_configJson) return;
|
||||
if (json_object_to_file_ext(CONFIG_PATH, g_configJson, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) != 0) jsonLogLastError();
|
||||
if (json_object_to_file_ext(g_configJsonPath, g_configJson, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) != 0) jsonLogLastError();
|
||||
}
|
||||
|
||||
static void configFreeConfigJson(void)
|
||||
|
@ -194,7 +214,8 @@ end:
|
|||
|
||||
static bool configValidateJsonGameCardObject(const struct json_object *obj)
|
||||
{
|
||||
bool ret = false, prepend_key_area_found = false, keep_certificate_found = false, trim_dump_found = false, calculate_checksum_found = false, checksum_lookup_method_found = false;
|
||||
bool ret = false, prepend_key_area_found = false, keep_certificate_found = false, trim_dump_found = false, calculate_checksum_found = false;
|
||||
bool checksum_lookup_method_found = false, write_raw_hfs_partition_found = false;
|
||||
|
||||
if (!jsonValidateObject(obj)) goto end;
|
||||
|
||||
|
@ -205,10 +226,11 @@ static bool configValidateJsonGameCardObject(const struct json_object *obj)
|
|||
CONFIG_VALIDATE_FIELD(Boolean, trim_dump);
|
||||
CONFIG_VALIDATE_FIELD(Boolean, calculate_checksum);
|
||||
CONFIG_VALIDATE_FIELD(Integer, checksum_lookup_method, ConfigChecksumLookupMethod_None, ConfigChecksumLookupMethod_Count - 1);
|
||||
CONFIG_VALIDATE_FIELD(Boolean, write_raw_hfs_partition);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = (prepend_key_area_found && keep_certificate_found && trim_dump_found && calculate_checksum_found && checksum_lookup_method_found);
|
||||
ret = (prepend_key_area_found && keep_certificate_found && trim_dump_found && calculate_checksum_found && checksum_lookup_method_found && write_raw_hfs_partition_found);
|
||||
|
||||
end:
|
||||
return ret;
|
||||
|
@ -217,7 +239,8 @@ end:
|
|||
static bool configValidateJsonNspObject(const struct json_object *obj)
|
||||
{
|
||||
bool ret = false, set_download_distribution_found = false, remove_console_data_found = false, remove_titlekey_crypto_found = false;
|
||||
bool disable_linked_account_requirement_found = false, enable_screenshots_found = false, enable_video_capture_found = false, disable_hdcp_found = false, append_authoringtool_data_found = false, lookup_checksum_found = false;
|
||||
bool disable_linked_account_requirement_found = false, enable_screenshots_found = false, enable_video_capture_found = false, disable_hdcp_found = false;
|
||||
bool append_authoringtool_data_found = false, lookup_checksum_found = false;
|
||||
|
||||
if (!jsonValidateObject(obj)) goto end;
|
||||
|
||||
|
|
|
@ -35,8 +35,6 @@
|
|||
|
||||
#define GAMECARD_STORAGE_AREA_NAME(x) ((x) == GameCardStorageArea_Normal ? "normal" : ((x) == GameCardStorageArea_Secure ? "secure" : "none"))
|
||||
|
||||
#define GAMECARD_HFS_PARTITION_NAME_INDEX(x) ((x) - 1)
|
||||
|
||||
#define LAFW_MAGIC 0x4C414657 /* "LAFW". */
|
||||
|
||||
/* Type definitions. */
|
||||
|
@ -95,14 +93,6 @@ static MemoryLocation g_fsProgramMemory = {
|
|||
.data_size = 0
|
||||
};
|
||||
|
||||
static const char *g_gameCardHfsPartitionNames[] = {
|
||||
[GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Root)] = "root",
|
||||
[GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Update)] = "update",
|
||||
[GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Logo)] = "logo",
|
||||
[GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Normal)] = "normal",
|
||||
[GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Secure)] = "secure"
|
||||
};
|
||||
|
||||
static const char *g_gameCardHosVersionStrings[GameCardFwVersion_Count] = {
|
||||
[GameCardFwVersion_ForDev] = "1.0.0",
|
||||
[GameCardFwVersion_Since100NUP] = "1.0.0",
|
||||
|
@ -457,7 +447,7 @@ bool gamecardGetBundledFirmwareUpdateVersion(Version *out)
|
|||
|
||||
bool gamecardGetHashFileSystemContext(u8 hfs_partition_type, HashFileSystemContext *out)
|
||||
{
|
||||
if (!hfs_partition_type || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count || !out)
|
||||
if (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count || !out)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -471,30 +461,30 @@ bool gamecardGetHashFileSystemContext(u8 hfs_partition_type, HashFileSystemConte
|
|||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
/* Get pointer to the Hash FS context for the requested partition. */
|
||||
HashFileSystemContext *fs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type);
|
||||
if (!fs_ctx) break;
|
||||
HashFileSystemContext *hfs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type);
|
||||
if (!hfs_ctx) break;
|
||||
|
||||
/* Fill Hash FS context. */
|
||||
out->name = strdup(fs_ctx->name);
|
||||
out->name = strdup(hfs_ctx->name);
|
||||
if (!out->name)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to duplicate Hash FS partition name! (%s).", fs_ctx->name);
|
||||
LOG_MSG_ERROR("Failed to duplicate Hash FS partition name! (%s).", hfs_ctx->name);
|
||||
break;
|
||||
}
|
||||
|
||||
out->type = fs_ctx->type;
|
||||
out->offset = fs_ctx->offset;
|
||||
out->size = fs_ctx->size;
|
||||
out->header_size = fs_ctx->header_size;
|
||||
out->type = hfs_ctx->type;
|
||||
out->offset = hfs_ctx->offset;
|
||||
out->size = hfs_ctx->size;
|
||||
out->header_size = hfs_ctx->header_size;
|
||||
|
||||
out->header = calloc(fs_ctx->header_size, sizeof(u8));
|
||||
out->header = calloc(hfs_ctx->header_size, sizeof(u8));
|
||||
if (!out->header)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to duplicate Hash FS partition header! (%s).", fs_ctx->name);
|
||||
LOG_MSG_ERROR("Failed to duplicate Hash FS partition header! (%s).", hfs_ctx->name);
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(out->header, fs_ctx->header, fs_ctx->header_size);
|
||||
memcpy(out->header, hfs_ctx->header, hfs_ctx->header_size);
|
||||
|
||||
/* Update flag. */
|
||||
ret = true;
|
||||
|
@ -507,7 +497,7 @@ bool gamecardGetHashFileSystemContext(u8 hfs_partition_type, HashFileSystemConte
|
|||
|
||||
bool gamecardGetHashFileSystemEntryInfoByName(u8 hfs_partition_type, const char *entry_name, u64 *out_offset, u64 *out_size)
|
||||
{
|
||||
if (!hfs_partition_type || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count || !entry_name || !*entry_name || (!out_offset && !out_size))
|
||||
if (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count || !entry_name || !*entry_name || (!out_offset && !out_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -518,16 +508,16 @@ bool gamecardGetHashFileSystemEntryInfoByName(u8 hfs_partition_type, const char
|
|||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
{
|
||||
/* Get pointer to the Hash FS context for the requested partition. */
|
||||
HashFileSystemContext *fs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type);
|
||||
if (!fs_ctx) break;
|
||||
HashFileSystemContext *hfs_ctx = _gamecardGetHashFileSystemContext(hfs_partition_type);
|
||||
if (!hfs_ctx) break;
|
||||
|
||||
/* Get Hash FS entry by name. */
|
||||
HashFileSystemEntry *fs_entry = hfsGetEntryByName(fs_ctx, entry_name);
|
||||
if (!fs_entry) break;
|
||||
HashFileSystemEntry *hfs_entry = hfsGetEntryByName(hfs_ctx, entry_name);
|
||||
if (!hfs_entry) break;
|
||||
|
||||
/* Update output variables. */
|
||||
if (out_offset) *out_offset = (fs_ctx->offset + fs_ctx->header_size + fs_entry->offset);
|
||||
if (out_size) *out_size = fs_entry->size;
|
||||
if (out_offset) *out_offset = (hfs_ctx->offset + hfs_ctx->header_size + hfs_entry->offset);
|
||||
if (out_size) *out_size = hfs_entry->size;
|
||||
|
||||
/* Update flag. */
|
||||
ret = true;
|
||||
|
@ -734,9 +724,9 @@ static void gamecardLoadInfo(void)
|
|||
{
|
||||
if (g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded) return;
|
||||
|
||||
HashFileSystemContext *root_fs_ctx = NULL;
|
||||
u32 root_fs_entry_count = 0, root_fs_name_table_size = 0;
|
||||
char *root_fs_name_table = NULL;
|
||||
HashFileSystemContext *root_hfs_ctx = NULL;
|
||||
u32 root_hfs_entry_count = 0, root_hfs_name_table_size = 0;
|
||||
char *root_hfs_name_table = NULL;
|
||||
|
||||
/* Set initial gamecard status. */
|
||||
g_gameCardStatus = GameCardStatus_InsertedAndInfoNotLoaded;
|
||||
|
@ -781,12 +771,12 @@ static void gamecardLoadInfo(void)
|
|||
}
|
||||
|
||||
/* Initialize Hash FS context for the root partition. */
|
||||
root_fs_ctx = gamecardInitializeHashFileSystemContext(NULL, g_gameCardHeader.partition_fs_header_address, 0, g_gameCardHeader.partition_fs_header_hash, 0, g_gameCardHeader.partition_fs_header_size);
|
||||
if (!root_fs_ctx) goto end;
|
||||
root_hfs_ctx = gamecardInitializeHashFileSystemContext(NULL, g_gameCardHeader.partition_fs_header_address, 0, g_gameCardHeader.partition_fs_header_hash, 0, g_gameCardHeader.partition_fs_header_size);
|
||||
if (!root_hfs_ctx) goto end;
|
||||
|
||||
/* Calculate total Hash FS partition count. */
|
||||
root_fs_entry_count = hfsGetEntryCount(root_fs_ctx);
|
||||
g_gameCardHfsCount = (root_fs_entry_count + 1);
|
||||
root_hfs_entry_count = hfsGetEntryCount(root_hfs_ctx);
|
||||
g_gameCardHfsCount = (root_hfs_entry_count + 1);
|
||||
|
||||
/* Allocate Hash FS context pointer array. */
|
||||
g_gameCardHfsCtx = calloc(g_gameCardHfsCount, sizeof(HashFileSystemContext*));
|
||||
|
@ -797,26 +787,26 @@ static void gamecardLoadInfo(void)
|
|||
}
|
||||
|
||||
/* Set root partition context as the first pointer. */
|
||||
g_gameCardHfsCtx[0] = root_fs_ctx;
|
||||
g_gameCardHfsCtx[0] = root_hfs_ctx;
|
||||
|
||||
/* Get root partition name table. */
|
||||
root_fs_name_table_size = ((HashFileSystemHeader*)root_fs_ctx->header)->name_table_size;
|
||||
root_fs_name_table = hfsGetNameTable(root_fs_ctx);
|
||||
root_hfs_name_table_size = ((HashFileSystemHeader*)root_hfs_ctx->header)->name_table_size;
|
||||
root_hfs_name_table = hfsGetNameTable(root_hfs_ctx);
|
||||
|
||||
/* Initialize Hash FS contexts for the child partitions. */
|
||||
for(u32 i = 0; i < root_fs_entry_count; i++)
|
||||
for(u32 i = 0; i < root_hfs_entry_count; i++)
|
||||
{
|
||||
HashFileSystemEntry *fs_entry = hfsGetEntryByIndex(root_fs_ctx, i);
|
||||
char *fs_entry_name = (root_fs_name_table + fs_entry->name_offset);
|
||||
u64 fs_entry_offset = (root_fs_ctx->offset + root_fs_ctx->header_size + fs_entry->offset);
|
||||
HashFileSystemEntry *hfs_entry = hfsGetEntryByIndex(root_hfs_ctx, i);
|
||||
char *hfs_entry_name = (root_hfs_name_table + hfs_entry->name_offset);
|
||||
u64 hfs_entry_offset = (root_hfs_ctx->offset + root_hfs_ctx->header_size + hfs_entry->offset);
|
||||
|
||||
if (fs_entry->name_offset >= root_fs_name_table_size || !*fs_entry_name)
|
||||
if (hfs_entry->name_offset >= root_hfs_name_table_size || !*hfs_entry_name)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid name for root Hash FS partition entry #%u!", i);
|
||||
goto end;
|
||||
}
|
||||
|
||||
g_gameCardHfsCtx[i + 1] = gamecardInitializeHashFileSystemContext(fs_entry_name, fs_entry_offset, fs_entry->size, fs_entry->hash, fs_entry->hash_target_offset, fs_entry->hash_target_size);
|
||||
g_gameCardHfsCtx[i + 1] = gamecardInitializeHashFileSystemContext(hfs_entry_name, hfs_entry_offset, hfs_entry->size, hfs_entry->hash, hfs_entry->hash_target_offset, hfs_entry->hash_target_size);
|
||||
if (!g_gameCardHfsCtx[i + 1]) goto end;
|
||||
}
|
||||
|
||||
|
@ -826,10 +816,10 @@ static void gamecardLoadInfo(void)
|
|||
end:
|
||||
if (g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded)
|
||||
{
|
||||
if (!g_gameCardHfsCtx && root_fs_ctx)
|
||||
if (!g_gameCardHfsCtx && root_hfs_ctx)
|
||||
{
|
||||
hfsFreeContext(root_fs_ctx);
|
||||
free(root_fs_ctx);
|
||||
hfsFreeContext(root_hfs_ctx);
|
||||
free(root_hfs_ctx);
|
||||
}
|
||||
|
||||
gamecardFreeInfo(false);
|
||||
|
@ -850,11 +840,11 @@ static void gamecardFreeInfo(bool clear_status)
|
|||
{
|
||||
for(u32 i = 0; i < g_gameCardHfsCount; i++)
|
||||
{
|
||||
HashFileSystemContext *cur_fs_ctx = g_gameCardHfsCtx[i];
|
||||
if (cur_fs_ctx)
|
||||
HashFileSystemContext *cur_hfs_ctx = g_gameCardHfsCtx[i];
|
||||
if (cur_hfs_ctx)
|
||||
{
|
||||
hfsFreeContext(cur_fs_ctx);
|
||||
free(cur_fs_ctx);
|
||||
hfsFreeContext(cur_hfs_ctx);
|
||||
free(cur_hfs_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1225,9 +1215,9 @@ NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size)
|
|||
static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size)
|
||||
{
|
||||
u32 i = 0, magic = 0;
|
||||
HashFileSystemContext *fs_ctx = NULL;
|
||||
HashFileSystemHeader fs_header = {0};
|
||||
u8 fs_header_hash[SHA256_HASH_SIZE] = {0};
|
||||
HashFileSystemContext *hfs_ctx = NULL;
|
||||
HashFileSystemHeader hfs_header = {0};
|
||||
u8 hfs_header_hash[SHA256_HASH_SIZE] = {0};
|
||||
|
||||
bool success = false, dump_fs_header = false;
|
||||
|
||||
|
@ -1239,149 +1229,150 @@ static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char
|
|||
}
|
||||
|
||||
/* Allocate memory for the output context. */
|
||||
fs_ctx = calloc(1, sizeof(HashFileSystemContext));
|
||||
if (!fs_ctx)
|
||||
hfs_ctx = calloc(1, sizeof(HashFileSystemContext));
|
||||
if (!hfs_ctx)
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to allocate memory for Hash FS context! (offset 0x%lX).", offset);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Duplicate partition name. */
|
||||
fs_ctx->name = (name ? strdup(name) : strdup(g_gameCardHfsPartitionNames[GAMECARD_HFS_PARTITION_NAME_INDEX(GameCardHashFileSystemPartitionType_Root)]));
|
||||
if (!fs_ctx->name)
|
||||
hfs_ctx->name = (name ? strdup(name) : strdup(hfsGetPartitionNameString(HashFileSystemPartitionType_Root)));
|
||||
if (!hfs_ctx->name)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to duplicate Hash FS partition name! (offset 0x%lX).", offset);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Determine Hash FS partition type. */
|
||||
for(i = GameCardHashFileSystemPartitionType_Root; i < GameCardHashFileSystemPartitionType_Count; i++)
|
||||
for(i = HashFileSystemPartitionType_Root; i < HashFileSystemPartitionType_Count; i++)
|
||||
{
|
||||
if (!strcmp(g_gameCardHfsPartitionNames[GAMECARD_HFS_PARTITION_NAME_INDEX(i)], fs_ctx->name)) break;
|
||||
const char *hfs_partition_name = hfsGetPartitionNameString((u8)i);
|
||||
if (hfs_partition_name && !strcmp(hfs_partition_name, hfs_ctx->name)) break;
|
||||
}
|
||||
|
||||
if (i >= GameCardHashFileSystemPartitionType_Count)
|
||||
if (i >= HashFileSystemPartitionType_Count)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to find a matching Hash FS partition type for \"%s\"! (offset 0x%lX).", fs_ctx->name, offset);
|
||||
LOG_MSG_ERROR("Failed to find a matching Hash FS partition type for \"%s\"! (offset 0x%lX).", hfs_ctx->name, offset);
|
||||
goto end;
|
||||
}
|
||||
|
||||
fs_ctx->type = i;
|
||||
hfs_ctx->type = i;
|
||||
|
||||
/* Read partial Hash FS header. */
|
||||
if (!gamecardReadStorageArea(&fs_header, sizeof(HashFileSystemHeader), offset))
|
||||
if (!gamecardReadStorageArea(&hfs_header, sizeof(HashFileSystemHeader), offset))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to read partial Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->name, offset);
|
||||
LOG_MSG_ERROR("Failed to read partial Hash FS header! (\"%s\", offset 0x%lX).", hfs_ctx->name, offset);
|
||||
goto end;
|
||||
}
|
||||
|
||||
magic = __builtin_bswap32(fs_header.magic);
|
||||
magic = __builtin_bswap32(hfs_header.magic);
|
||||
if (magic != HFS0_MAGIC)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid Hash FS magic word! (0x%08X) (\"%s\", offset 0x%lX).", magic, fs_ctx->name, offset);
|
||||
LOG_MSG_ERROR("Invalid Hash FS magic word! (0x%08X) (\"%s\", offset 0x%lX).", magic, hfs_ctx->name, offset);
|
||||
dump_fs_header = true;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Check Hash FS entry count and name table size. */
|
||||
/* Only allow a zero entry count if we're not dealing with the root partition. Never allow a zero-sized name table. */
|
||||
if ((!name && !fs_header.entry_count) || !fs_header.name_table_size)
|
||||
if ((!name && !hfs_header.entry_count) || !hfs_header.name_table_size)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid Hash FS entry count / name table size! (\"%s\", offset 0x%lX).", fs_ctx->name, offset);
|
||||
LOG_MSG_ERROR("Invalid Hash FS entry count / name table size! (\"%s\", offset 0x%lX).", hfs_ctx->name, offset);
|
||||
dump_fs_header = true;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Calculate full Hash FS header size. */
|
||||
fs_ctx->header_size = (sizeof(HashFileSystemHeader) + (fs_header.entry_count * sizeof(HashFileSystemEntry)) + fs_header.name_table_size);
|
||||
fs_ctx->header_size = ALIGN_UP(fs_ctx->header_size, GAMECARD_PAGE_SIZE);
|
||||
hfs_ctx->header_size = (sizeof(HashFileSystemHeader) + (hfs_header.entry_count * sizeof(HashFileSystemEntry)) + hfs_header.name_table_size);
|
||||
hfs_ctx->header_size = ALIGN_UP(hfs_ctx->header_size, GAMECARD_PAGE_SIZE);
|
||||
|
||||
/* Allocate memory for the full Hash FS header. */
|
||||
fs_ctx->header = calloc(fs_ctx->header_size, sizeof(u8));
|
||||
if (!fs_ctx->header)
|
||||
hfs_ctx->header = calloc(hfs_ctx->header_size, sizeof(u8));
|
||||
if (!hfs_ctx->header)
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to allocate 0x%lX bytes buffer for the full Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->header_size, fs_ctx->name, offset);
|
||||
LOG_MSG_ERROR("Unable to allocate 0x%lX bytes buffer for the full Hash FS header! (\"%s\", offset 0x%lX).", hfs_ctx->header_size, hfs_ctx->name, offset);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Read full Hash FS header. */
|
||||
if (!gamecardReadStorageArea(fs_ctx->header, fs_ctx->header_size, offset))
|
||||
if (!gamecardReadStorageArea(hfs_ctx->header, hfs_ctx->header_size, offset))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to read full Hash FS header! (\"%s\", offset 0x%lX).", fs_ctx->name, offset);
|
||||
LOG_MSG_ERROR("Failed to read full Hash FS header! (\"%s\", offset 0x%lX).", hfs_ctx->name, offset);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Verify Hash FS header (if possible). */
|
||||
if (hash && hash_target_size && (hash_target_offset + hash_target_size) <= fs_ctx->header_size)
|
||||
if (hash && hash_target_size && (hash_target_offset + hash_target_size) <= hfs_ctx->header_size)
|
||||
{
|
||||
sha256CalculateHash(fs_header_hash, fs_ctx->header + hash_target_offset, hash_target_size);
|
||||
if (memcmp(fs_header_hash, hash, SHA256_HASH_SIZE) != 0)
|
||||
sha256CalculateHash(hfs_header_hash, hfs_ctx->header + hash_target_offset, hash_target_size);
|
||||
if (memcmp(hfs_header_hash, hash, SHA256_HASH_SIZE) != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("Hash FS header doesn't match expected SHA-256 hash! (\"%s\", offset 0x%lX).", fs_ctx->name, offset);
|
||||
LOG_MSG_ERROR("Hash FS header doesn't match expected SHA-256 hash! (\"%s\", offset 0x%lX).", hfs_ctx->name, offset);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill context. */
|
||||
fs_ctx->offset = offset;
|
||||
hfs_ctx->offset = offset;
|
||||
|
||||
if (name)
|
||||
{
|
||||
/* Use provided partition size. */
|
||||
fs_ctx->size = size;
|
||||
hfs_ctx->size = size;
|
||||
} else {
|
||||
/* Calculate root partition size. */
|
||||
HashFileSystemEntry *fs_entry = hfsGetEntryByIndex(fs_ctx, fs_header.entry_count - 1);
|
||||
fs_ctx->size = (fs_ctx->header_size + fs_entry->offset + fs_entry->size);
|
||||
HashFileSystemEntry *hfs_entry = hfsGetEntryByIndex(hfs_ctx, hfs_header.entry_count - 1);
|
||||
hfs_ctx->size = (hfs_ctx->header_size + hfs_entry->offset + hfs_entry->size);
|
||||
}
|
||||
|
||||
/* Update flag. */
|
||||
success = true;
|
||||
|
||||
end:
|
||||
if (!success && fs_ctx)
|
||||
if (!success && hfs_ctx)
|
||||
{
|
||||
if (dump_fs_header) LOG_DATA_DEBUG(&fs_header, sizeof(HashFileSystemHeader), "Partial Hash FS header dump (\"%s\", offset 0x%lX):", fs_ctx->name, offset);
|
||||
if (dump_fs_header) LOG_DATA_DEBUG(&hfs_header, sizeof(HashFileSystemHeader), "Partial Hash FS header dump (\"%s\", offset 0x%lX):", hfs_ctx->name, offset);
|
||||
|
||||
if (fs_ctx->header) free(fs_ctx->header);
|
||||
if (hfs_ctx->header) free(hfs_ctx->header);
|
||||
|
||||
if (fs_ctx->name) free(fs_ctx->name);
|
||||
if (hfs_ctx->name) free(hfs_ctx->name);
|
||||
|
||||
free(fs_ctx);
|
||||
fs_ctx = NULL;
|
||||
free(hfs_ctx);
|
||||
hfs_ctx = NULL;
|
||||
}
|
||||
|
||||
return fs_ctx;
|
||||
return hfs_ctx;
|
||||
}
|
||||
|
||||
static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition_type)
|
||||
{
|
||||
HashFileSystemContext *fs_ctx = NULL;
|
||||
HashFileSystemContext *hfs_ctx = NULL;
|
||||
|
||||
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || !hfs_partition_type || \
|
||||
hfs_partition_type >= GameCardHashFileSystemPartitionType_Count)
|
||||
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || \
|
||||
hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Return right away if the root partition was requested. */
|
||||
if (hfs_partition_type == GameCardHashFileSystemPartitionType_Root)
|
||||
if (hfs_partition_type == HashFileSystemPartitionType_Root)
|
||||
{
|
||||
fs_ctx = g_gameCardHfsCtx[0];
|
||||
hfs_ctx = g_gameCardHfsCtx[0];
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Try to find the requested partition by looping through our Hash FS contexts. */
|
||||
for(u32 i = 1; i < g_gameCardHfsCount; i++)
|
||||
{
|
||||
fs_ctx = g_gameCardHfsCtx[i];
|
||||
if (fs_ctx->type == hfs_partition_type) break;
|
||||
fs_ctx = NULL;
|
||||
hfs_ctx = g_gameCardHfsCtx[i];
|
||||
if (hfs_ctx->type == hfs_partition_type) break;
|
||||
hfs_ctx = NULL;
|
||||
}
|
||||
|
||||
if (!fs_ctx) LOG_MSG_ERROR("Failed to locate Hash FS partition with type %u!", hfs_partition_type);
|
||||
if (!hfs_ctx) LOG_MSG_ERROR("Failed to locate Hash FS partition with type %u!", hfs_partition_type);
|
||||
|
||||
end:
|
||||
return fs_ctx;
|
||||
return hfs_ctx;
|
||||
}
|
||||
|
|
|
@ -22,9 +22,19 @@
|
|||
#include "nxdt_utils.h"
|
||||
#include "gamecard.h"
|
||||
|
||||
#define HFS_PARTITION_NAME_INDEX(x) ((x) - 1)
|
||||
|
||||
static const char *g_hfsPartitionNames[] = {
|
||||
[HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Root)] = "root",
|
||||
[HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Update)] = "update",
|
||||
[HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Logo)] = "logo",
|
||||
[HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Normal)] = "normal",
|
||||
[HFS_PARTITION_NAME_INDEX(HashFileSystemPartitionType_Secure)] = "secure"
|
||||
};
|
||||
|
||||
bool hfsReadPartitionData(HashFileSystemContext *ctx, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size)
|
||||
if (!hfsIsValidContext(ctx) || !out || !read_size || (offset + read_size) > ctx->size)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -60,16 +70,16 @@ bool hfsReadEntryData(HashFileSystemContext *ctx, HashFileSystemEntry *fs_entry,
|
|||
|
||||
bool hfsGetTotalDataSize(HashFileSystemContext *ctx, u64 *out_size)
|
||||
{
|
||||
u64 total_size = 0;
|
||||
u32 entry_count = hfsGetEntryCount(ctx);
|
||||
HashFileSystemEntry *fs_entry = NULL;
|
||||
|
||||
if (!entry_count || !out_size)
|
||||
if (!hfsIsValidContext(ctx) || !out_size)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 total_size = 0;
|
||||
u32 entry_count = hfsGetEntryCount(ctx);
|
||||
HashFileSystemEntry *fs_entry = NULL;
|
||||
|
||||
for(u32 i = 0; i < entry_count; i++)
|
||||
{
|
||||
if (!(fs_entry = hfsGetEntryByIndex(ctx, i)))
|
||||
|
@ -89,15 +99,24 @@ bool hfsGetTotalDataSize(HashFileSystemContext *ctx, u64 *out_size)
|
|||
bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *out_idx)
|
||||
{
|
||||
HashFileSystemEntry *fs_entry = NULL;
|
||||
u32 entry_count = hfsGetEntryCount(ctx), name_table_size = 0;
|
||||
char *name_table = hfsGetNameTable(ctx);
|
||||
u32 entry_count = 0, name_table_size = 0;
|
||||
char *name_table = NULL;
|
||||
bool ret = false;
|
||||
|
||||
if (!entry_count || !name_table || !name || !*name || !out_idx)
|
||||
if (hfsIsValidContext(ctx) && name && *name && out_idx)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
entry_count = hfsGetEntryCount(ctx);
|
||||
name_table = hfsGetNameTable(ctx);
|
||||
ret = (entry_count && name_table);
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = false;
|
||||
name_table_size = ((HashFileSystemHeader*)ctx->header)->name_table_size;
|
||||
|
||||
for(u32 i = 0; i < entry_count; i++)
|
||||
|
@ -117,9 +136,17 @@ bool hfsGetEntryIndexByName(HashFileSystemContext *ctx, const char *name, u32 *o
|
|||
if (!strcmp(name_table + fs_entry->name_offset, name))
|
||||
{
|
||||
*out_idx = i;
|
||||
return true;
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char *hfsGetPartitionNameString(u8 hfs_partition_type)
|
||||
{
|
||||
return ((hfs_partition_type > HashFileSystemPartitionType_None && hfs_partition_type < HashFileSystemPartitionType_Count) ? \
|
||||
g_hfsPartitionNames[HFS_PARTITION_NAME_INDEX(hfs_partition_type)] : NULL);
|
||||
}
|
||||
|
|
|
@ -181,8 +181,8 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
u8 valid_fs_section_cnt = 0;
|
||||
|
||||
if (!out || (storage_id != NcmStorageId_GameCard && !(ncm_storage = titleGetNcmStorageByStorageId(storage_id))) || \
|
||||
(storage_id == NcmStorageId_GameCard && (!hfs_partition_type || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count)) || !content_info || \
|
||||
content_info->content_type >= NcmContentType_DeltaFragment)
|
||||
(storage_id == NcmStorageId_GameCard && (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count)) || \
|
||||
!content_info || content_info->content_type >= NcmContentType_DeltaFragment)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -279,7 +279,7 @@ bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset)
|
|||
/* This strips NAX0 crypto from SD card NCAs (not used on eMMC NCAs). */
|
||||
rc = ncmContentStorageReadContentIdFile(ctx->ncm_storage, out, read_size, &(ctx->content_id), offset);
|
||||
ret = R_SUCCEEDED(rc);
|
||||
if (!ret) LOG_MSG_ERROR("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (0x%X) (ncm).", read_size, offset, ctx->content_id_str, rc);
|
||||
if (!ret) LOG_MSG_ERROR("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (ncm) (0x%X).", read_size, offset, ctx->content_id_str, rc);
|
||||
} else {
|
||||
/* Retrieve NCA data using raw gamecard reads. */
|
||||
/* Fixes NCA read issues with gamecards under HOS < 4.0.0 when using ncmContentStorageReadContentIdFile(). */
|
||||
|
|
|
@ -67,13 +67,13 @@ bool bfsarInitialize(void)
|
|||
if (ptr1 && ptr2 && ptr1 != ptr2)
|
||||
{
|
||||
/* Create BFSAR file in the current working directory. */
|
||||
snprintf(g_bfsarPath, sizeof(g_bfsarPath), "%.*s" BFSAR_FILENAME, (int)((ptr2 - ptr1) + 1), ptr1);
|
||||
snprintf(g_bfsarPath, sizeof(g_bfsarPath), "%.*s" BFSAR_FILENAME, (int)((ptr2 - launch_path) + 1), launch_path);
|
||||
use_root = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create BFSAR file in the SD card root directory. */
|
||||
if (use_root) sprintf(g_bfsarPath, "/" BFSAR_FILENAME);
|
||||
if (use_root) sprintf(g_bfsarPath, DEVOPTAB_SDMC_DEVICE "/" BFSAR_FILENAME);
|
||||
|
||||
LOG_MSG_DEBUG("BFSAR path: \"%s\".", g_bfsarPath);
|
||||
|
||||
|
@ -106,7 +106,9 @@ bool bfsarInitialize(void)
|
|||
}
|
||||
|
||||
/* Initialize NCA context. */
|
||||
if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Program, 0), title_info->version.value, NULL))
|
||||
/* Don't allow invalid NCA signatures. */
|
||||
if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Program, 0), title_info->version.value, NULL) || \
|
||||
!nca_ctx->valid_main_signature)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to initialize qlaunch Program NCA context!");
|
||||
break;
|
||||
|
|
|
@ -430,13 +430,13 @@ static bool logOpenLogFile(void)
|
|||
|
||||
if (ptr1 && ptr2 && ptr1 != ptr2)
|
||||
{
|
||||
/* Create logfile in the current working directory. */
|
||||
/* Create logfile in the current working directory. Strip the devoptab device name prefix while we're at it, since we won't need it. */
|
||||
snprintf(path, sizeof(path), "%.*s" LOG_FILE_NAME, (int)((ptr2 - ptr1) + 1), ptr1);
|
||||
use_root = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create logfile in the SD card root directory. */
|
||||
/* Create logfile in the SD card root directory, if needed. */
|
||||
if (use_root) sprintf(path, "/" LOG_FILE_NAME);
|
||||
|
||||
/* Create file. This will fail if the logfile exists, so we don't check its return value. */
|
||||
|
@ -452,17 +452,17 @@ static bool logOpenLogFile(void)
|
|||
{
|
||||
size_t len = 0;
|
||||
|
||||
/* Write UTF-8 BOM right away (if needed). */
|
||||
if (!g_logFileOffset)
|
||||
{
|
||||
/* Write UTF-8 BOM if the logfile is empty. */
|
||||
len = strlen(UTF8_BOM);
|
||||
fsFileWrite(&g_logFile, g_logFileOffset, UTF8_BOM, len, FsWriteOption_Flush);
|
||||
g_logFileOffset += (s64)len;
|
||||
rc = fsFileWrite(&g_logFile, g_logFileOffset, UTF8_BOM, len, FsWriteOption_Flush);
|
||||
} else {
|
||||
/* Write session separator if the logfile isn't empty. */
|
||||
len = strlen(g_logSessionSeparator);
|
||||
rc = fsFileWrite(&g_logFile, g_logFileOffset, g_logSessionSeparator, len, FsWriteOption_Flush);
|
||||
}
|
||||
|
||||
/* Write session separator right away. */
|
||||
len = strlen(g_logSessionSeparator);
|
||||
rc = fsFileWrite(&g_logFile, g_logFileOffset, g_logSessionSeparator, len, FsWriteOption_Flush);
|
||||
if (R_SUCCEEDED(rc)) g_logFileOffset += (s64)len;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -244,7 +244,13 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
|
|||
{
|
||||
bool flag = false;
|
||||
rc = appletIsGamePlayRecordingSupported(&flag);
|
||||
if (R_SUCCEEDED(rc) && flag) appletInitializeGamePlayRecording();
|
||||
if (R_SUCCEEDED(rc) && flag)
|
||||
{
|
||||
rc = appletInitializeGamePlayRecording();
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("appletInitializeGamePlayRecording failed! (0x%X).", rc);
|
||||
} else {
|
||||
LOG_MSG_ERROR("appletIsGamePlayRecordingSupported returned [0x%X, %u].", rc, flag);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update flags. */
|
||||
|
@ -758,6 +764,86 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element)
|
|||
free(tmp);
|
||||
}
|
||||
|
||||
bool utilsDeleteDirectoryRecursively(const char *path)
|
||||
{
|
||||
char *name_end = NULL, *entry_path = NULL;
|
||||
DIR *dir = NULL;
|
||||
struct dirent *entry = NULL;
|
||||
bool success = true;
|
||||
|
||||
/* Sanity checks. */
|
||||
if (!path || !*path || !(name_end = strchr(path, ':')) || *(name_end + 1) != '/' || !*(name_end + 2))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(dir = opendir(path)))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to open directory \"%s\"! (%d).", path, errno);
|
||||
success = false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!(entry_path = calloc(1, FS_MAX_PATH)))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to allocate memory for path buffer!");
|
||||
success = false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Read directory entries. */
|
||||
while((entry = readdir(dir)))
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
/* Skip current directory and parent directory entries. */
|
||||
if (!strcmp(".", entry->d_name) || !strcmp("..", entry->d_name)) continue;
|
||||
|
||||
/* Generate path to the current entry. */
|
||||
snprintf(entry_path, FS_MAX_PATH, "%s/%s", path, entry->d_name);
|
||||
|
||||
if (entry->d_type == DT_DIR)
|
||||
{
|
||||
/* Delete directory contents. */
|
||||
if (!utilsDeleteDirectoryRecursively(entry_path))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Delete directory. */
|
||||
status = rmdir(entry_path);
|
||||
} else {
|
||||
/* Delete file. */
|
||||
status = unlink(entry_path);
|
||||
}
|
||||
|
||||
if (status != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to delete \"%s\"! (%s, %d).", entry_path, entry->d_type == DT_DIR ? "dir" : "file", errno);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
closedir(dir);
|
||||
dir = NULL;
|
||||
|
||||
success = (rmdir(path) == 0);
|
||||
if (!success) LOG_MSG_ERROR("Failed to delete topmost directory \"%s\"! (%d).", path, errno);
|
||||
}
|
||||
|
||||
end:
|
||||
if (entry_path) free(entry_path);
|
||||
|
||||
if (dir) closedir(dir);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension)
|
||||
{
|
||||
if (!filename || !*filename)
|
||||
|
|
|
@ -62,7 +62,8 @@ static ServiceInfo g_serviceInfo[] = {
|
|||
{ false, "set", NULL, &setInitialize, &setExit },
|
||||
{ false, "set:sys", NULL, &setsysInitialize, &setsysExit },
|
||||
{ false, "set:cal", NULL, &setcalInitialize, &setcalExit },
|
||||
{ false, "bsd:u", NULL, &socketInitializeDefault, &socketExit } /* socketInitialize*() functions take care of initializing bsd:* too. */
|
||||
{ false, "bsd:u", NULL, &socketInitializeDefault, &socketExit }, /* socketInitialize*() functions take care of initializing bsd:* too. */
|
||||
{ false, "usb:ds", NULL, &usbDsInitialize, &usbDsExit }
|
||||
};
|
||||
|
||||
static const u32 g_serviceInfoCount = MAX_ELEMENTS(g_serviceInfo);
|
||||
|
|
|
@ -168,7 +168,7 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c
|
|||
u8 *signature = NULL;
|
||||
u64 signature_size = 0;
|
||||
|
||||
bool dev_cert = false;
|
||||
bool generate_cert = false, dev_cert = false;
|
||||
char cert_chain_issuer[0x40] = {0};
|
||||
static const char *common_cert_names[] = { "XS00000020", "XS00000022", NULL };
|
||||
|
||||
|
@ -176,26 +176,31 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c
|
|||
u64 raw_cert_chain_size = 0;
|
||||
|
||||
if (!tik || tik->type == TikType_None || tik->type > TikType_SigHmac160 || tik->size < SIGNED_TIK_MIN_SIZE || tik->size > SIGNED_TIK_MAX_SIZE || \
|
||||
!(tik_common_block = tikGetCommonBlock(tik->data)) || tik_common_block->titlekey_type != TikTitleKeyType_Personalized || !out_raw_cert_chain || !out_raw_cert_chain_size)
|
||||
!(tik_common_block = tikGetCommonBlock(tik->data)) || tik_common_block->titlekey_type != TikTitleKeyType_Personalized || (!out_raw_cert_chain && out_raw_cert_chain_size) || \
|
||||
(out_raw_cert_chain && !out_raw_cert_chain_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Generate raw certificate chain for the new signature issuer (common). */
|
||||
dev_cert = (strstr(tik_common_block->issuer, "CA00000004") != NULL);
|
||||
|
||||
for(u8 i = 0; common_cert_names[i] != NULL; i++)
|
||||
/* Generate raw certificate chain for the new signature issuer (common), if needed. */
|
||||
generate_cert = (out_raw_cert_chain && out_raw_cert_chain_size);
|
||||
if (generate_cert)
|
||||
{
|
||||
sprintf(cert_chain_issuer, "Root-CA%08X-%s", dev_cert ? 4 : 3, common_cert_names[i]);
|
||||
raw_cert_chain = certGenerateRawCertificateChainBySignatureIssuer(cert_chain_issuer, &raw_cert_chain_size);
|
||||
if (raw_cert_chain) break;
|
||||
}
|
||||
dev_cert = (strstr(tik_common_block->issuer, "CA00000004") != NULL);
|
||||
|
||||
if (!raw_cert_chain)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to generate raw certificate chain for common ticket signature issuer!");
|
||||
return false;
|
||||
for(u8 i = 0; common_cert_names[i] != NULL; i++)
|
||||
{
|
||||
sprintf(cert_chain_issuer, "Root-CA%08X-%s", dev_cert ? 4 : 3, common_cert_names[i]);
|
||||
raw_cert_chain = certGenerateRawCertificateChainBySignatureIssuer(cert_chain_issuer, &raw_cert_chain_size);
|
||||
if (raw_cert_chain) break;
|
||||
}
|
||||
|
||||
if (!raw_cert_chain)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to generate raw certificate chain for common ticket signature issuer!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wipe signature. */
|
||||
|
@ -230,8 +235,11 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c
|
|||
memset(tik->data + tik->size, 0, SIGNED_TIK_MAX_SIZE - tik->size);
|
||||
|
||||
/* Update output pointers. */
|
||||
*out_raw_cert_chain = raw_cert_chain;
|
||||
*out_raw_cert_chain_size = raw_cert_chain_size;
|
||||
if (generate_cert)
|
||||
{
|
||||
*out_raw_cert_chain = raw_cert_chain;
|
||||
*out_raw_cert_chain_size = raw_cert_chain_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -250,7 +258,7 @@ static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsI
|
|||
utilsGenerateHexStringFromData(tik_filename, sizeof(tik_filename), id->c, sizeof(id->c), false);
|
||||
strcat(tik_filename, ".tik");
|
||||
|
||||
if (!gamecardGetHashFileSystemEntryInfoByName(GameCardHashFileSystemPartitionType_Secure, tik_filename, &tik_offset, &tik_size))
|
||||
if (!gamecardGetHashFileSystemEntryInfoByName(HashFileSystemPartitionType_Secure, tik_filename, &tik_offset, &tik_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!", tik_filename);
|
||||
return false;
|
||||
|
|
|
@ -540,6 +540,8 @@ static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApp
|
|||
|
||||
NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system, u32 extra_app_count);
|
||||
|
||||
NX_INLINE u64 titleGetApplicationIdByMetaKey(const NcmContentMetaKey *meta_key);
|
||||
|
||||
static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_storage);
|
||||
static bool titleGetMetaKeysFromContentDatabase(NcmContentMetaDatabase *ncm_db, NcmContentMetaKey **out_meta_keys, u32 *out_meta_key_count);
|
||||
static bool titleGetContentInfosForMetaKey(NcmContentMetaDatabase *ncm_db, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count);
|
||||
|
@ -1777,6 +1779,33 @@ NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 ti
|
|||
return NULL;
|
||||
}
|
||||
|
||||
NX_INLINE u64 titleGetApplicationIdByMetaKey(const NcmContentMetaKey *meta_key)
|
||||
{
|
||||
if (!meta_key) return 0;
|
||||
|
||||
u64 app_id = meta_key->id;
|
||||
|
||||
switch(meta_key->type)
|
||||
{
|
||||
case NcmContentMetaType_Patch:
|
||||
app_id = titleGetApplicationIdByPatchId(meta_key->id);
|
||||
break;
|
||||
case NcmContentMetaType_AddOnContent:
|
||||
app_id = titleGetApplicationIdByAddOnContentId(meta_key->id);
|
||||
break;
|
||||
case NcmContentMetaType_Delta:
|
||||
app_id = titleGetApplicationIdByDeltaId(meta_key->id);
|
||||
break;
|
||||
case NcmContentMetaType_DataPatch:
|
||||
app_id = titleGetApplicationIdByDataPatchId(meta_key->id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return app_id;
|
||||
}
|
||||
|
||||
static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_storage)
|
||||
{
|
||||
if (!title_storage || title_storage->storage_id < NcmStorageId_GameCard || title_storage->storage_id > NcmStorageId_SdCard || !serviceIsActive(&(title_storage->ncm_db.s)))
|
||||
|
@ -1850,12 +1879,7 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto
|
|||
utilsGenerateFormattedSizeString((double)cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str));
|
||||
|
||||
/* Retrieve application metadata. */
|
||||
u64 app_id = (cur_title_info->meta_key.type <= NcmContentMetaType_Application ? cur_title_info->meta_key.id : \
|
||||
(cur_title_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(cur_title_info->meta_key.id) : \
|
||||
(cur_title_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(cur_title_info->meta_key.id) : \
|
||||
(cur_title_info->meta_key.type == NcmContentMetaType_Delta ? titleGetApplicationIdByDeltaId(cur_title_info->meta_key.id) : \
|
||||
(cur_title_info->meta_key.type == NcmContentMetaType_DataPatch ? titleGetApplicationIdByDataPatchId(cur_title_info->meta_key.id) : 0)))));
|
||||
|
||||
u64 app_id = titleGetApplicationIdByMetaKey(&(cur_title_info->meta_key));
|
||||
cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, storage_id == NcmStorageId_BuiltInSystem, 0);
|
||||
if (!cur_title_info->app_metadata && storage_id == NcmStorageId_BuiltInSystem)
|
||||
{
|
||||
|
@ -2097,10 +2121,7 @@ static void titleUpdateTitleInfoLinkedLists(void)
|
|||
{
|
||||
/* We're dealing with a patch, an add-on content or an add-on content patch. */
|
||||
/* We'll just retrieve a pointer to the first matching user application entry and use it to set a pointer to an application metadata entry. */
|
||||
u64 app_id = (child_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(child_info->meta_key.id) : \
|
||||
(child_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(child_info->meta_key.id) : \
|
||||
titleGetApplicationIdByDataPatchId(child_info->meta_key.id)));
|
||||
|
||||
u64 app_id = titleGetApplicationIdByMetaKey(&(child_info->meta_key));
|
||||
TitleInfo *parent = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id);
|
||||
if (parent)
|
||||
{
|
||||
|
@ -2263,14 +2284,9 @@ static bool titleRefreshGameCardTitleInfo(void)
|
|||
TitleInfo *cur_title_info = titles[i];
|
||||
if (!cur_title_info) continue;
|
||||
|
||||
u64 app_id = (cur_title_info->meta_key.type <= NcmContentMetaType_Application ? cur_title_info->meta_key.id : \
|
||||
(cur_title_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(cur_title_info->meta_key.id) : \
|
||||
(cur_title_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(cur_title_info->meta_key.id) : \
|
||||
(cur_title_info->meta_key.type == NcmContentMetaType_Delta ? titleGetApplicationIdByDeltaId(cur_title_info->meta_key.id) : \
|
||||
(cur_title_info->meta_key.type == NcmContentMetaType_DataPatch ? titleGetApplicationIdByDataPatchId(cur_title_info->meta_key.id) : 0)))));
|
||||
|
||||
/* Do not proceed if we couldn't retrieve an application ID, if application metadata has already been retrieved, or if we can successfully retrieve it. */
|
||||
if (!app_id || cur_title_info->app_metadata != NULL || (cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, false, extra_app_count)) != NULL) continue;
|
||||
/* Do not proceed if application metadata has already been retrieved, or if we can successfully retrieve it. */
|
||||
u64 app_id = titleGetApplicationIdByMetaKey(&(cur_title_info->meta_key));
|
||||
if (cur_title_info->app_metadata != NULL || (cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, false, extra_app_count)) != NULL) continue;
|
||||
|
||||
/* Retrieve application metadata pointer. */
|
||||
TitleApplicationMetadata *cur_app_metadata = g_userMetadata[g_userMetadataCount + extra_app_count];
|
||||
|
@ -2568,7 +2584,7 @@ static int titleInfoEntrySortFunction(const void *a, const void *b)
|
|||
static char *titleGetPatchVersionString(TitleInfo *title_info)
|
||||
{
|
||||
NcmContentInfo *nacp_content = NULL;
|
||||
u8 storage_id = 0, hfs_partition_type = 0;
|
||||
u8 storage_id = NcmStorageId_None, hfs_partition_type = HashFileSystemPartitionType_None;
|
||||
NcaContext *nca_ctx = NULL;
|
||||
NacpContext nacp_ctx = {0};
|
||||
char display_version[0x11] = {0}, *str = NULL;
|
||||
|
@ -2581,7 +2597,7 @@ static char *titleGetPatchVersionString(TitleInfo *title_info)
|
|||
|
||||
/* Update parameters. */
|
||||
storage_id = title_info->storage_id;
|
||||
if (storage_id == NcmStorageId_GameCard) hfs_partition_type = GameCardHashFileSystemPartitionType_Secure;
|
||||
if (storage_id == NcmStorageId_GameCard) hfs_partition_type = HashFileSystemPartitionType_Secure;
|
||||
|
||||
/* Allocate memory for the NCA context. */
|
||||
nca_ctx = calloc(1, sizeof(NcaContext));
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
#include "nxdt_utils.h"
|
||||
#include "usb.h"
|
||||
|
||||
#define USB_ABI_VERSION 1
|
||||
#define USB_ABI_VERSION_MAJOR 1
|
||||
#define USB_ABI_VERSION_MINOR 1
|
||||
#define USB_ABI_VERSION ((USB_ABI_VERSION_MAJOR << 4) | USB_ABI_VERSION_MINOR)
|
||||
|
||||
#define USB_CMD_HEADER_MAGIC 0x4E584454 /* "NXDT". */
|
||||
|
||||
|
@ -41,7 +43,7 @@
|
|||
#define USB_FS_EP_MAX_PACKET_SIZE 0x40 /* 64 bytes. */
|
||||
|
||||
#define USB_HS_BCD_REVISION 0x0200 /* USB 2.0. */
|
||||
#define USB_HS_EP0_MAX_PACKET_SIZE USB_FS_EP0_MAX_PACKET_SIZE /* 64 bytes. */
|
||||
#define USB_HS_EP0_MAX_PACKET_SIZE 0x40 /* 64 bytes. */
|
||||
#define USB_HS_EP_MAX_PACKET_SIZE 0x200 /* 512 bytes. */
|
||||
|
||||
#define USB_SS_BCD_REVISION 0x0300 /* USB 3.0. */
|
||||
|
@ -145,27 +147,27 @@ enum usb_supported_speed {
|
|||
};
|
||||
|
||||
/// Imported from libusb, with some adjustments.
|
||||
struct usb_bos_descriptor {
|
||||
struct PACKED usb_bos_descriptor {
|
||||
u8 bLength;
|
||||
u8 bDescriptorType; ///< Must match USB_DT_BOS.
|
||||
u16 wTotalLength; ///< Length of this descriptor and all of its sub descriptors.
|
||||
u8 bNumDeviceCaps; ///< The number of separate device capability descriptors in the BOS.
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
NXDT_ASSERT(struct usb_bos_descriptor, 0x5);
|
||||
|
||||
/// Imported from libusb, with some adjustments.
|
||||
struct usb_2_0_extension_descriptor {
|
||||
struct PACKED usb_2_0_extension_descriptor {
|
||||
u8 bLength;
|
||||
u8 bDescriptorType; ///< Must match USB_DT_DEVICE_CAPABILITY.
|
||||
u8 bDevCapabilityType; ///< Must match USB_BT_USB_2_0_EXTENSION.
|
||||
u32 bmAttributes; ///< usb_2_0_extension_attributes.
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
NXDT_ASSERT(struct usb_2_0_extension_descriptor, 0x7);
|
||||
|
||||
/// Imported from libusb, with some adjustments.
|
||||
struct usb_ss_usb_device_capability_descriptor {
|
||||
struct PACKED usb_ss_usb_device_capability_descriptor {
|
||||
u8 bLength;
|
||||
u8 bDescriptorType; ///< Must match USB_DT_DEVICE_CAPABILITY.
|
||||
u8 bDevCapabilityType; ///< Must match USB_BT_SS_USB_DEVICE_CAPABILITY.
|
||||
|
@ -174,7 +176,7 @@ struct usb_ss_usb_device_capability_descriptor {
|
|||
u8 bFunctionalitySupport; ///< The lowest speed at which all the functionality that the device supports is available to the user.
|
||||
u8 bU1DevExitLat; ///< U1 Device Exit Latency.
|
||||
u16 bU2DevExitLat; ///< U2 Device Exit Latency.
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
NXDT_ASSERT(struct usb_ss_usb_device_capability_descriptor, 0xA);
|
||||
|
||||
|
@ -183,7 +185,7 @@ NXDT_ASSERT(struct usb_ss_usb_device_capability_descriptor, 0xA);
|
|||
static Mutex g_usbInterfaceMutex = 0;
|
||||
static UsbDsInterface *g_usbInterface = NULL;
|
||||
static UsbDsEndpoint *g_usbEndpointIn = NULL, *g_usbEndpointOut = NULL;
|
||||
static bool g_usbInterfaceInit = false;
|
||||
static bool g_usbInterfaceInit = false, g_usbHos5xEnabled = false;
|
||||
|
||||
static Event *g_usbStateChangeEvent = NULL;
|
||||
static Thread g_usbDetectionThread = {0};
|
||||
|
@ -214,10 +216,11 @@ NX_INLINE bool usbAllocateTransferBuffer(void);
|
|||
NX_INLINE void usbFreeTransferBuffer(void);
|
||||
|
||||
static bool usbInitializeComms(void);
|
||||
static bool usbInitializeComms5x(void);
|
||||
static bool usbInitializeComms1x(void);
|
||||
static void usbCloseComms(void);
|
||||
|
||||
static bool usbInitializeDeviceInterface5x(void);
|
||||
static bool usbInitializeDeviceInterface1x(void);
|
||||
static bool _usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size, bool enforce_nsp_mode);
|
||||
|
||||
NX_INLINE bool usbIsHostAvailable(void);
|
||||
|
||||
|
@ -243,10 +246,10 @@ bool usbInitialize(void)
|
|||
break;
|
||||
}
|
||||
|
||||
/* Initialize USB device interface. */
|
||||
/* Initialize USB comms. */
|
||||
if (!usbInitializeComms())
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to initialize USB device interface!");
|
||||
LOG_MSG_ERROR("Failed to initialize USB comms!");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -334,45 +337,17 @@ u8 usbIsReady(void)
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size)
|
||||
bool usbSendFileProperties(u64 file_size, const char *filename)
|
||||
{
|
||||
bool ret = false;
|
||||
SCOPED_LOCK(&g_usbInterfaceMutex) ret = _usbSendFileProperties(file_size, filename, 0, false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
SCOPED_LOCK(&g_usbInterfaceMutex)
|
||||
{
|
||||
size_t filename_length = 0;
|
||||
|
||||
if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || !filename || !(filename_length = strlen(filename)) || filename_length >= FS_MAX_PATH || \
|
||||
(!g_nspTransferMode && ((file_size && nsp_header_size >= file_size) || g_usbTransferRemainingSize)) || (g_nspTransferMode && nsp_header_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Prepare command data. */
|
||||
usbPrepareCommandHeader(UsbCommandType_SendFileProperties, (u32)sizeof(UsbCommandSendFileProperties));
|
||||
|
||||
UsbCommandSendFileProperties *cmd_block = (UsbCommandSendFileProperties*)(g_usbTransferBuffer + sizeof(UsbCommandHeader));
|
||||
memset(cmd_block, 0, sizeof(UsbCommandSendFileProperties));
|
||||
|
||||
cmd_block->file_size = file_size;
|
||||
cmd_block->filename_length = (u32)filename_length;
|
||||
cmd_block->nsp_header_size = nsp_header_size;
|
||||
snprintf(cmd_block->filename, sizeof(cmd_block->filename), "%s", filename);
|
||||
|
||||
/* Send command. */
|
||||
ret = usbSendCommand();
|
||||
if (!ret) goto end;
|
||||
|
||||
/* Update variables. */
|
||||
g_usbTransferRemainingSize = file_size;
|
||||
g_usbTransferWrittenSize = 0;
|
||||
if (!g_nspTransferMode) g_nspTransferMode = (file_size && nsp_header_size);
|
||||
|
||||
end:
|
||||
if (!ret && g_nspTransferMode) g_nspTransferMode = false;
|
||||
}
|
||||
|
||||
bool usbSendNspProperties(u64 nsp_size, const char *filename, u32 nsp_header_size)
|
||||
{
|
||||
bool ret = false;
|
||||
SCOPED_LOCK(&g_usbInterfaceMutex) ret = _usbSendFileProperties(nsp_size, filename, nsp_header_size, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -426,7 +401,7 @@ bool usbSendFileData(void *data, u64 data_size)
|
|||
if (!(ret = usbWrite(buf, data_size)))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to write 0x%lX bytes long file data chunk from offset 0x%lX! (total size: 0x%lX).", data_size, g_usbTransferWrittenSize, \
|
||||
g_usbTransferRemainingSize + g_usbTransferWrittenSize);
|
||||
g_usbTransferRemainingSize + g_usbTransferWrittenSize);
|
||||
goto end;
|
||||
}
|
||||
|
||||
|
@ -630,7 +605,7 @@ static bool usbStartSession(void)
|
|||
{
|
||||
/* Get the endpoint max packet size from the response sent by the USB host. */
|
||||
/* This is done to accurately know when and where to enable Zero Length Termination (ZLT) packets during bulk transfers. */
|
||||
/* As much as I'd like to avoid this, usb:ds doesn't disclose information such as the exact device descriptor and/or speed used by the USB host. */
|
||||
/* As much as I'd like to avoid this, the GetUsbDeviceSpeed cmd from usb:ds is only available in HOS 8.0.0+ -- and we definitely want to provide USB comms under older versions. */
|
||||
g_usbEndpointMaxPacketSize = ((UsbStatus*)g_usbTransferBuffer)->max_packet_size;
|
||||
if (g_usbEndpointMaxPacketSize != USB_FS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_HS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_SS_EP_MAX_PACKET_SIZE)
|
||||
{
|
||||
|
@ -801,9 +776,47 @@ NX_INLINE void usbFreeTransferBuffer(void)
|
|||
static bool usbInitializeComms(void)
|
||||
{
|
||||
Result rc = 0;
|
||||
bool ret = false, init_dev_if = false;
|
||||
bool ret = false, is_5x = hosversionAtLeast(5, 0, 0);
|
||||
|
||||
/* Carry out USB comms initialization steps for this HOS version. */
|
||||
ret = (is_5x ? usbInitializeComms5x() : usbInitializeComms1x());
|
||||
if (!ret) goto end;
|
||||
|
||||
/* Enable USB interface. */
|
||||
/* This is always needed regardless of the HOS version. */
|
||||
rc = usbDsInterface_EnableInterface(g_usbInterface);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_EnableInterface failed! (0x%X).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Additional step needed under HOS 5.0.0+. */
|
||||
if (is_5x)
|
||||
{
|
||||
rc = usbDsEnable();
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsEnable failed! (0x%X).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
g_usbHos5xEnabled = true;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
if (!ret) usbCloseComms();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool usbInitializeComms5x(void)
|
||||
{
|
||||
Result rc = 0;
|
||||
bool ret = false;
|
||||
|
||||
/* Used on HOS >= 5.0.0. */
|
||||
struct usb_device_descriptor device_descriptor = {
|
||||
.bLength = USB_DT_DEVICE_SIZE,
|
||||
.bDescriptorType = USB_DT_DEVICE,
|
||||
|
@ -849,130 +862,6 @@ static bool usbInitializeComms(void)
|
|||
usb3_devcap_desc->bU1DevExitLat = 0;
|
||||
usb3_devcap_desc->bU2DevExitLat = 0;
|
||||
|
||||
/* Used on HOS < 5.0.0. */
|
||||
static const UsbDsDeviceInfo device_info = {
|
||||
.idVendor = USB_DEV_VID,
|
||||
.idProduct = USB_DEV_PID,
|
||||
.bcdDevice = USB_DEV_BCD_REL,
|
||||
.Manufacturer = APP_AUTHOR,
|
||||
.Product = APP_TITLE,
|
||||
.SerialNumber = APP_VERSION
|
||||
};
|
||||
|
||||
rc = usbDsInitialize();
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInitialize failed! (0x%X).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (hosversionAtLeast(5, 0, 0))
|
||||
{
|
||||
/* Set language string descriptor. */
|
||||
rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, num_supported_langs);
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsAddUsbLanguageStringDescriptor failed! (0x%X).", rc);
|
||||
|
||||
/* Set manufacturer string descriptor. */
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iManufacturer), APP_AUTHOR);
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (manufacturer).", rc);
|
||||
}
|
||||
|
||||
/* Set product string descriptor. */
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iProduct), APP_TITLE);
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (product).", rc);
|
||||
}
|
||||
|
||||
/* Set serial number string descriptor. */
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iSerialNumber), APP_VERSION);
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (serial number).", rc);
|
||||
}
|
||||
|
||||
/* Set device descriptors. */
|
||||
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor); /* Full Speed is USB 1.1. */
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 1.1).", rc);
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
/* Update USB revision before proceeding. */
|
||||
device_descriptor.bcdUSB = USB_HS_BCD_REVISION;
|
||||
|
||||
rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor); /* High Speed is USB 2.0. */
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 2.0).", rc);
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
/* Update USB revision and upgrade control endpoint packet size before proceeding. */
|
||||
device_descriptor.bcdUSB = USB_SS_BCD_REVISION;
|
||||
device_descriptor.bMaxPacketSize0 = USB_SS_EP0_MAX_PACKET_SIZE;
|
||||
|
||||
rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor); /* Super Speed is USB 3.0. */
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 3.0).", rc);
|
||||
}
|
||||
|
||||
/* Set Binary Object Store. */
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
rc = usbDsSetBinaryObjectStore(bos, USB_BOS_SIZE);
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetBinaryObjectStore failed! (0x%X).", rc);
|
||||
}
|
||||
} else {
|
||||
/* Set VID, PID and BCD. */
|
||||
rc = usbDsSetVidPidBcd(&device_info);
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("usbDsSetVidPidBcd failed! (0x%X).", rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc)) goto end;
|
||||
|
||||
/* Initialize USB device interface. */
|
||||
init_dev_if = (hosversionAtLeast(5, 0, 0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x());
|
||||
if (!init_dev_if)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to initialize USB device interface!");
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (hosversionAtLeast(5, 0, 0))
|
||||
{
|
||||
rc = usbDsEnable();
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsEnable failed! (0x%X).", rc);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
if (!ret) usbCloseComms();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usbCloseComms(void)
|
||||
{
|
||||
usbDsExit();
|
||||
|
||||
g_usbInterface = NULL;
|
||||
g_usbEndpointIn = NULL;
|
||||
g_usbEndpointOut = NULL;
|
||||
}
|
||||
|
||||
static bool usbInitializeDeviceInterface5x(void)
|
||||
{
|
||||
Result rc = 0;
|
||||
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
@ -1011,12 +900,77 @@ static bool usbInitializeDeviceInterface5x(void)
|
|||
.wBytesPerInterval = 0
|
||||
};
|
||||
|
||||
/* Set language string descriptor. */
|
||||
rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, num_supported_langs);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsAddUsbLanguageStringDescriptor failed! (0x%X).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Set manufacturer string descriptor. */
|
||||
rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iManufacturer), APP_AUTHOR);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (manufacturer).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Set product string descriptor. */
|
||||
rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iProduct), APP_TITLE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (product).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Set serial number string descriptor. */
|
||||
rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iSerialNumber), APP_VERSION);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsAddUsbStringDescriptor failed! (0x%X) (serial number).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Set device descriptors. */
|
||||
rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor); /* Full Speed is USB 1.1. */
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 1.1).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
device_descriptor.bcdUSB = USB_HS_BCD_REVISION;
|
||||
rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor); /* High Speed is USB 2.0. */
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 2.0).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
device_descriptor.bcdUSB = USB_SS_BCD_REVISION;
|
||||
device_descriptor.bMaxPacketSize0 = USB_SS_EP0_MAX_PACKET_SIZE;
|
||||
rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor); /* Super Speed is USB 3.0. */
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsSetUsbDeviceDescriptor failed! (0x%X) (USB 3.0).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Set Binary Object Store. */
|
||||
rc = usbDsSetBinaryObjectStore(bos, USB_BOS_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsSetBinaryObjectStore failed! (0x%X).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Setup interface. */
|
||||
rc = usbDsRegisterInterface(&g_usbInterface);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsRegisterInterface failed! (0x%X).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
interface_descriptor.bInterfaceNumber = g_usbInterface->interface_index;
|
||||
|
@ -1028,85 +982,83 @@ static bool usbInitializeDeviceInterface5x(void)
|
|||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 1.1) (interface).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 1.1) (in endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 1.1) (out endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* High Speed config (USB 2.0). */
|
||||
endpoint_descriptor_in.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE;
|
||||
endpoint_descriptor_out.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE;
|
||||
endpoint_descriptor_in.wMaxPacketSize = endpoint_descriptor_out.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE;
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 2.0) (interface).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 2.0) (in endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 2.0) (out endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Super Speed config (USB 3.0). */
|
||||
endpoint_descriptor_in.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE;
|
||||
endpoint_descriptor_out.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE;
|
||||
endpoint_descriptor_in.wMaxPacketSize = endpoint_descriptor_out.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE;
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (interface).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (in endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (in endpoint companion).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (out endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_AppendConfigurationData failed! (0x%X) (USB 3.0) (out endpoint companion).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Setup endpoints. */
|
||||
|
@ -1114,29 +1066,35 @@ static bool usbInitializeDeviceInterface5x(void)
|
|||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_RegisterEndpoint failed! (0x%X) (in endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_RegisterEndpoint(g_usbInterface, &g_usbEndpointOut, endpoint_descriptor_out.bEndpointAddress);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_RegisterEndpoint failed! (0x%X) (out endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_EnableInterface(g_usbInterface);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_EnableInterface failed! (0x%X).", rc);
|
||||
return false;
|
||||
}
|
||||
ret = true;
|
||||
|
||||
return true;
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool usbInitializeDeviceInterface1x(void)
|
||||
static bool usbInitializeComms1x(void)
|
||||
{
|
||||
Result rc = 0;
|
||||
bool ret = false;
|
||||
|
||||
static const UsbDsDeviceInfo device_info = {
|
||||
.idVendor = USB_DEV_VID,
|
||||
.idProduct = USB_DEV_PID,
|
||||
.bcdDevice = USB_DEV_BCD_REL,
|
||||
.Manufacturer = APP_AUTHOR,
|
||||
.Product = APP_TITLE,
|
||||
.SerialNumber = APP_VERSION
|
||||
};
|
||||
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
|
@ -1167,12 +1125,20 @@ static bool usbInitializeDeviceInterface1x(void)
|
|||
.bInterval = 0
|
||||
};
|
||||
|
||||
/* Set VID, PID and BCD. */
|
||||
rc = usbDsSetVidPidBcd(&device_info);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsSetVidPidBcd failed! (0x%X).", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Setup interface. */
|
||||
rc = usbDsGetDsInterface(&g_usbInterface, &interface_descriptor, "usb");
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsGetDsInterface failed! (0x%X).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Setup endpoints. */
|
||||
|
@ -1180,24 +1146,94 @@ static bool usbInitializeDeviceInterface1x(void)
|
|||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_GetDsEndpoint failed! (0x%X) (in endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_GetDsEndpoint(g_usbInterface, &g_usbEndpointOut, &endpoint_descriptor_out);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_GetDsEndpoint failed! (0x%X) (out endpoint).", rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = usbDsInterface_EnableInterface(g_usbInterface);
|
||||
if (R_FAILED(rc))
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usbCloseComms(void)
|
||||
{
|
||||
bool is_5x = hosversionAtLeast(5, 0, 0);
|
||||
|
||||
if (is_5x && g_usbHos5xEnabled)
|
||||
{
|
||||
LOG_MSG_ERROR("usbDsInterface_EnableInterface failed! (0x%X).", rc);
|
||||
usbDsDisable();
|
||||
g_usbHos5xEnabled = false;
|
||||
}
|
||||
|
||||
if (g_usbEndpointOut)
|
||||
{
|
||||
usbDsEndpoint_Close(g_usbEndpointOut);
|
||||
g_usbEndpointOut = NULL;
|
||||
}
|
||||
|
||||
if (g_usbEndpointIn)
|
||||
{
|
||||
usbDsEndpoint_Close(g_usbEndpointIn);
|
||||
g_usbEndpointIn = NULL;
|
||||
}
|
||||
|
||||
if (g_usbInterface)
|
||||
{
|
||||
/* usbDsInterface_DisableInterface() is internally called here. */
|
||||
usbDsInterface_Close(g_usbInterface);
|
||||
g_usbInterface = NULL;
|
||||
}
|
||||
|
||||
if (is_5x) usbDsClearDeviceData();
|
||||
}
|
||||
|
||||
static bool _usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size, bool enforce_nsp_mode)
|
||||
{
|
||||
bool ret = false;
|
||||
size_t filename_length = 0;
|
||||
|
||||
/* Disallow sending new files if we're not in NSP transfer mode and the remaining transfer size isn't zero. */
|
||||
/* Allow empty files if we're not in NSP transfer mode. */
|
||||
/* Disallow sending new NSPs if we're already in NSP transfer mode. */
|
||||
if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || (!g_nspTransferMode && g_usbTransferRemainingSize) || \
|
||||
!filename || !(filename_length = strlen(filename)) || filename_length >= FS_MAX_PATH || (!enforce_nsp_mode && nsp_header_size) || \
|
||||
(enforce_nsp_mode && (g_nspTransferMode || !file_size || !nsp_header_size || nsp_header_size >= file_size)))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
/* Prepare command data. */
|
||||
usbPrepareCommandHeader(UsbCommandType_SendFileProperties, (u32)sizeof(UsbCommandSendFileProperties));
|
||||
|
||||
UsbCommandSendFileProperties *cmd_block = (UsbCommandSendFileProperties*)(g_usbTransferBuffer + sizeof(UsbCommandHeader));
|
||||
memset(cmd_block, 0, sizeof(UsbCommandSendFileProperties));
|
||||
|
||||
cmd_block->file_size = file_size;
|
||||
cmd_block->filename_length = (u32)filename_length;
|
||||
cmd_block->nsp_header_size = nsp_header_size;
|
||||
snprintf(cmd_block->filename, sizeof(cmd_block->filename), "%s", filename);
|
||||
|
||||
/* Send command. */
|
||||
ret = usbSendCommand();
|
||||
if (ret)
|
||||
{
|
||||
g_usbTransferRemainingSize = file_size;
|
||||
g_usbTransferWrittenSize = 0;
|
||||
if (!g_nspTransferMode && enforce_nsp_mode) g_nspTransferMode = true;
|
||||
} else {
|
||||
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
|
||||
g_nspTransferMode = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NX_INLINE bool usbIsHostAvailable(void)
|
||||
|
|
|
@ -945,7 +945,7 @@ static void unlock_volume (
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT chk_share ( /* Check if the file can be accessed */
|
||||
DIR* dp, /* Directory object pointing the file to be checked */
|
||||
FDIR* dp, /* Directory object pointing the file to be checked */
|
||||
int acc /* Desired access type (0:Read mode open, 1:Write mode open, 2:Delete or rename) */
|
||||
)
|
||||
{
|
||||
|
@ -981,7 +981,7 @@ static int enq_share (void) /* Check if an entry is available for a new object *
|
|||
|
||||
|
||||
static UINT inc_share ( /* Increment object open counter and returns its index (0:Internal error) */
|
||||
DIR* dp, /* Directory object pointing the file to register or increment */
|
||||
FDIR* dp, /* Directory object pointing the file to register or increment */
|
||||
int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */
|
||||
)
|
||||
{
|
||||
|
@ -1697,7 +1697,7 @@ static FRESULT dir_clear ( /* Returns FR_OK or FR_DISK_ERR */
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */
|
||||
DIR* dp, /* Pointer to directory object */
|
||||
FDIR* dp, /* Pointer to directory object */
|
||||
DWORD ofs /* Offset of directory table */
|
||||
)
|
||||
{
|
||||
|
@ -1745,7 +1745,7 @@ static FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */
|
||||
DIR* dp, /* Pointer to the directory object */
|
||||
FDIR* dp, /* Pointer to the directory object */
|
||||
int stretch /* 0: Do not stretch table, 1: Stretch table if needed */
|
||||
)
|
||||
{
|
||||
|
@ -1806,7 +1806,7 @@ static FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DEN
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT dir_alloc ( /* FR_OK(0):succeeded, !=0:error */
|
||||
DIR* dp, /* Pointer to the directory object */
|
||||
FDIR* dp, /* Pointer to the directory object */
|
||||
UINT n_ent /* Number of contiguous entries to allocate */
|
||||
)
|
||||
{
|
||||
|
@ -2130,7 +2130,7 @@ static DWORD xsum32 ( /* Returns 32-bit checksum */
|
|||
/*------------------------------------*/
|
||||
|
||||
static FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */
|
||||
DIR* dp /* Reading directory object pointing top of the entry block to load */
|
||||
FDIR* dp /* Reading directory object pointing top of the entry block to load */
|
||||
)
|
||||
{
|
||||
FRESULT res;
|
||||
|
@ -2199,7 +2199,7 @@ static void init_alloc_info (
|
|||
/*------------------------------------------------*/
|
||||
|
||||
static FRESULT load_obj_xdir (
|
||||
DIR* dp, /* Blank directory object to be used to access containing directory */
|
||||
FDIR* dp, /* Blank directory object to be used to access containing directory */
|
||||
const FFOBJID* obj /* Object with its containing directory information */
|
||||
)
|
||||
{
|
||||
|
@ -2228,7 +2228,7 @@ static FRESULT load_obj_xdir (
|
|||
/*----------------------------------------*/
|
||||
|
||||
static FRESULT store_xdir (
|
||||
DIR* dp /* Pointer to the directory object */
|
||||
FDIR* dp /* Pointer to the directory object */
|
||||
)
|
||||
{
|
||||
FRESULT res;
|
||||
|
@ -2306,7 +2306,7 @@ static void create_xdir (
|
|||
#define DIR_READ_LABEL(dp) dir_read(dp, 1)
|
||||
|
||||
static FRESULT dir_read (
|
||||
DIR* dp, /* Pointer to the directory object */
|
||||
FDIR* dp, /* Pointer to the directory object */
|
||||
int vol /* Filtered by 0:file/directory or 1:volume label */
|
||||
)
|
||||
{
|
||||
|
@ -2384,7 +2384,7 @@ static FRESULT dir_read (
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */
|
||||
DIR* dp /* Pointer to the directory object with the file name */
|
||||
FDIR* dp /* Pointer to the directory object with the file name */
|
||||
)
|
||||
{
|
||||
FRESULT res;
|
||||
|
@ -2465,7 +2465,7 @@ static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */
|
||||
DIR* dp /* Target directory with object name to be created */
|
||||
FDIR* dp /* Target directory with object name to be created */
|
||||
)
|
||||
{
|
||||
FRESULT res;
|
||||
|
@ -2492,7 +2492,7 @@ static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too
|
|||
res = fill_last_frag(&dp->obj, dp->clust, 0xFFFFFFFF); /* Fill the last fragment on the FAT if needed */
|
||||
if (res != FR_OK) return res;
|
||||
if (dp->obj.sclust != 0) { /* Is it a sub-directory? */
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
|
||||
res = load_obj_xdir(&dj, &dp->obj); /* Load the object status */
|
||||
if (res != FR_OK) return res;
|
||||
|
@ -2571,7 +2571,7 @@ static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */
|
||||
DIR* dp /* Directory object pointing the entry to be removed */
|
||||
FDIR* dp /* Directory object pointing the entry to be removed */
|
||||
)
|
||||
{
|
||||
FRESULT res;
|
||||
|
@ -2617,7 +2617,7 @@ static FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static void get_fileinfo (
|
||||
DIR* dp, /* Pointer to the directory object */
|
||||
FDIR* dp, /* Pointer to the directory object */
|
||||
FILINFO* fno /* Pointer to the file information to be filled */
|
||||
)
|
||||
{
|
||||
|
@ -2847,7 +2847,7 @@ static int pattern_match ( /* 0:mismatched, 1:matched */
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */
|
||||
DIR* dp, /* Pointer to the directory object */
|
||||
FDIR* dp, /* Pointer to the directory object */
|
||||
const TCHAR** path /* Pointer to pointer to the segment in the path string */
|
||||
)
|
||||
{
|
||||
|
@ -3051,7 +3051,7 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */
|
||||
DIR* dp, /* Directory object to return last directory and found object */
|
||||
FDIR* dp, /* Directory object to return last directory and found object */
|
||||
const TCHAR* path /* Full-path string to find a file or directory */
|
||||
)
|
||||
{
|
||||
|
@ -3073,7 +3073,7 @@ static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */
|
|||
dp->obj.n_frag = 0; /* Invalidate last fragment counter of the object */
|
||||
#if FF_FS_RPATH != 0
|
||||
if (fs->fs_type == FS_EXFAT && dp->obj.sclust) { /* exFAT: Retrieve the sub-directory's status */
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
|
||||
dp->obj.c_scl = fs->cdc_scl;
|
||||
dp->obj.c_size = fs->cdc_size;
|
||||
|
@ -3534,7 +3534,7 @@ static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */
|
|||
if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */
|
||||
|
||||
/* Determine the FAT sub type */
|
||||
sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + DIR */
|
||||
sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + FDIR */
|
||||
if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
|
||||
nclst = (tsect - sysect) / fs->csize; /* Number of clusters */
|
||||
if (nclst == 0) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
|
||||
|
@ -3613,7 +3613,7 @@ static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */
|
||||
FFOBJID* obj, /* Pointer to the FFOBJID, the 1st member in the FIL/DIR structure, to check validity */
|
||||
FFOBJID* obj, /* Pointer to the FFOBJID, the 1st member in the FIL/FDIR structure, to check validity */
|
||||
FATFS** rfs /* Pointer to pointer to the owner filesystem object to return */
|
||||
)
|
||||
{
|
||||
|
@ -3723,7 +3723,7 @@ FRESULT f_open (
|
|||
)
|
||||
{
|
||||
FRESULT res;
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
FATFS *fs;
|
||||
#if !FF_FS_READONLY
|
||||
DWORD cl, bcs, clst, tm;
|
||||
|
@ -3766,7 +3766,7 @@ FRESULT f_open (
|
|||
mode |= FA_CREATE_ALWAYS; /* File is created */
|
||||
}
|
||||
else { /* Any object with the same name is already existing */
|
||||
if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */
|
||||
if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or FDIR) */
|
||||
res = FR_DENIED;
|
||||
} else {
|
||||
if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */
|
||||
|
@ -4162,7 +4162,7 @@ FRESULT f_sync (
|
|||
res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */
|
||||
}
|
||||
if (res == FR_OK) {
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
DEF_NAMBUF
|
||||
|
||||
INIT_NAMBUF(fs);
|
||||
|
@ -4276,7 +4276,7 @@ FRESULT f_chdir (
|
|||
UINT i;
|
||||
#endif
|
||||
FRESULT res;
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
FATFS *fs;
|
||||
DEF_NAMBUF
|
||||
|
||||
|
@ -4336,7 +4336,7 @@ FRESULT f_getcwd (
|
|||
)
|
||||
{
|
||||
FRESULT res;
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
FATFS *fs;
|
||||
UINT i, n;
|
||||
DWORD ccl;
|
||||
|
@ -4597,7 +4597,7 @@ FRESULT f_lseek (
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
FRESULT f_opendir (
|
||||
DIR* dp, /* Pointer to directory object to create */
|
||||
FDIR* dp, /* Pointer to directory object to create */
|
||||
const TCHAR* path /* Pointer to the directory path */
|
||||
)
|
||||
{
|
||||
|
@ -4663,7 +4663,7 @@ FRESULT f_opendir (
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
FRESULT f_closedir (
|
||||
DIR *dp /* Pointer to the directory object to be closed */
|
||||
FDIR *dp /* Pointer to the directory object to be closed */
|
||||
)
|
||||
{
|
||||
FRESULT res;
|
||||
|
@ -4693,7 +4693,7 @@ FRESULT f_closedir (
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
FRESULT f_readdir (
|
||||
DIR* dp, /* Pointer to the open directory object */
|
||||
FDIR* dp, /* Pointer to the open directory object */
|
||||
FILINFO* fno /* Pointer to file information to return */
|
||||
)
|
||||
{
|
||||
|
@ -4729,7 +4729,7 @@ FRESULT f_readdir (
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
FRESULT f_findnext (
|
||||
DIR* dp, /* Pointer to the open directory object */
|
||||
FDIR* dp, /* Pointer to the open directory object */
|
||||
FILINFO* fno /* Pointer to the file information structure */
|
||||
)
|
||||
{
|
||||
|
@ -4754,7 +4754,7 @@ FRESULT f_findnext (
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
FRESULT f_findfirst (
|
||||
DIR* dp, /* Pointer to the blank directory object */
|
||||
FDIR* dp, /* Pointer to the blank directory object */
|
||||
FILINFO* fno, /* Pointer to the file information structure */
|
||||
const TCHAR* path, /* Pointer to the directory to open */
|
||||
const TCHAR* pattern /* Pointer to the matching pattern */
|
||||
|
@ -4786,7 +4786,7 @@ FRESULT f_stat (
|
|||
)
|
||||
{
|
||||
FRESULT res;
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
DEF_NAMBUF
|
||||
|
||||
|
||||
|
@ -4967,7 +4967,7 @@ FRESULT f_unlink (
|
|||
{
|
||||
FRESULT res;
|
||||
FATFS *fs;
|
||||
DIR dj, sdj;
|
||||
FDIR dj, sdj;
|
||||
DWORD dclst = 0;
|
||||
#if FF_FS_EXFAT
|
||||
FFOBJID obj;
|
||||
|
@ -5061,7 +5061,7 @@ FRESULT f_mkdir (
|
|||
{
|
||||
FRESULT res;
|
||||
FATFS *fs;
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
FFOBJID sobj;
|
||||
DWORD dcl, pcl, tm;
|
||||
DEF_NAMBUF
|
||||
|
@ -5146,7 +5146,7 @@ FRESULT f_rename (
|
|||
{
|
||||
FRESULT res;
|
||||
FATFS *fs;
|
||||
DIR djo, djn;
|
||||
FDIR djo, djn;
|
||||
BYTE buf[FF_FS_EXFAT ? SZDIRE * 2 : SZDIRE], *dir;
|
||||
LBA_t sect;
|
||||
DEF_NAMBUF
|
||||
|
@ -5193,7 +5193,7 @@ FRESULT f_rename (
|
|||
#endif
|
||||
{ /* At FAT/FAT32 volume */
|
||||
memcpy(buf, djo.dir, SZDIRE); /* Save directory entry of the object */
|
||||
memcpy(&djn, &djo, sizeof (DIR)); /* Duplicate the directory object */
|
||||
memcpy(&djn, &djo, sizeof (FDIR)); /* Duplicate the directory object */
|
||||
res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */
|
||||
if (res == FR_OK) { /* Is new name already in use by any other object? */
|
||||
res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST;
|
||||
|
@ -5257,7 +5257,7 @@ FRESULT f_chmod (
|
|||
{
|
||||
FRESULT res;
|
||||
FATFS *fs;
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
DEF_NAMBUF
|
||||
|
||||
|
||||
|
@ -5303,7 +5303,7 @@ FRESULT f_utime (
|
|||
{
|
||||
FRESULT res;
|
||||
FATFS *fs;
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
DEF_NAMBUF
|
||||
|
||||
|
||||
|
@ -5351,7 +5351,7 @@ FRESULT f_getlabel (
|
|||
{
|
||||
FRESULT res;
|
||||
FATFS *fs;
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
UINT si, di;
|
||||
WCHAR wc;
|
||||
|
||||
|
@ -5450,7 +5450,7 @@ FRESULT f_setlabel (
|
|||
{
|
||||
FRESULT res;
|
||||
FATFS *fs;
|
||||
DIR dj;
|
||||
FDIR dj;
|
||||
BYTE dirvn[22];
|
||||
UINT di;
|
||||
WCHAR wc;
|
||||
|
|
|
@ -100,6 +100,7 @@ namespace nxdt::views
|
|||
{
|
||||
TitleApplicationMetadata **app_metadata = NULL;
|
||||
u32 app_metadata_count = 0;
|
||||
GameCardHeader card_header = {0};
|
||||
GameCardInfo card_info = {0};
|
||||
|
||||
bool update_focused_view = this->IsListItemFocused();
|
||||
|
@ -110,7 +111,7 @@ namespace nxdt::views
|
|||
this->list->invalidate(true);
|
||||
|
||||
/* Information about how to handle HOS launch errors. */
|
||||
/* TODO: remove this after we find a way to fix this issue. */
|
||||
/* TODO: remove this if we ever find a way to fix this issue. */
|
||||
FocusableLabel *launch_error_info = new FocusableLabel(brls::LabelStyle::DESCRIPTION, "gamecard_tab/list/launch_error_info"_i18n, true);
|
||||
launch_error_info->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
this->list->addView(launch_error_info);
|
||||
|
@ -151,28 +152,70 @@ namespace nxdt::views
|
|||
brls::TableRow *lafw_version = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/lafw_version"_i18n);
|
||||
brls::TableRow *sdk_version = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/sdk_version"_i18n);
|
||||
brls::TableRow *compatibility_type = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/compatibility_type"_i18n);
|
||||
brls::TableRow *package_id = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/package_id"_i18n);
|
||||
|
||||
capacity->setValue(this->GetFormattedSizeString(&gamecardGetRomCapacity));
|
||||
total_size->setValue(this->GetFormattedSizeString(&gamecardGetTotalSize));
|
||||
trimmed_size->setValue(this->GetFormattedSizeString(&gamecardGetTrimmedSize));
|
||||
|
||||
gamecardGetHeader(&card_header);
|
||||
gamecardGetDecryptedCardInfoArea(&card_info);
|
||||
|
||||
const Version *upp_version = &(card_info.upp_version);
|
||||
update_version->setValue(fmt::format("{}.{}.{}-{}.{} (v{})", upp_version->system_version.major, upp_version->system_version.minor, upp_version->system_version.micro, \
|
||||
upp_version->system_version.major_relstep, upp_version->system_version.minor_relstep, upp_version->value));
|
||||
const SystemVersion upp_version = card_info.upp_version.system_version;
|
||||
const SdkAddOnVersion upp_version_old = card_info.upp_version.sdk_addon_version;
|
||||
|
||||
/* TODO: move somewhere else? */
|
||||
if (upp_version_old.major == 0 && upp_version_old.minor == 0)
|
||||
{
|
||||
std::string upp_version_display = "";
|
||||
|
||||
switch(upp_version_old.micro)
|
||||
{
|
||||
case 0: /* v450 / 0.0.0-450 */
|
||||
upp_version_display = "1.0.0";
|
||||
break;
|
||||
case 1: /* v65796 / 0.0.1-260 */
|
||||
upp_version_display = "2.0.0";
|
||||
break;
|
||||
case 2: /* v131162 / 0.0.2-90 */
|
||||
upp_version_display = "2.1.0";
|
||||
break;
|
||||
case 3: /* v196628 / 0.0.3-20 */
|
||||
upp_version_display = "2.2.0";
|
||||
break;
|
||||
case 4: /* v262164 / 0.0.4-20 */
|
||||
upp_version_display = "2.3.0";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (upp_version_display != "")
|
||||
{
|
||||
update_version->setValue(fmt::format("{} ({}.{}.{}-{}) (v{})", upp_version_display, upp_version_old.major, upp_version_old.minor, upp_version_old.micro, \
|
||||
upp_version_old.relstep, upp_version_old.value));
|
||||
} else {
|
||||
update_version->setValue(fmt::format("{}.{}.{}-{} (v{})", upp_version_old.major, upp_version_old.minor, upp_version_old.micro, \
|
||||
upp_version_old.relstep, upp_version_old.value));
|
||||
}
|
||||
} else {
|
||||
update_version->setValue(fmt::format("{}.{}.{}-{}.{} (v{})", upp_version.major, upp_version.minor, upp_version.micro, \
|
||||
upp_version.major_relstep, upp_version.minor_relstep, upp_version.value));
|
||||
}
|
||||
|
||||
u64 fw_version = card_info.fw_version;
|
||||
lafw_version->setValue(fmt::format("{} ({})", fw_version, fw_version >= GameCardFwVersion_Count ? "generic/unknown"_i18n : gamecardGetRequiredHosVersionString(fw_version)));
|
||||
|
||||
const SdkAddOnVersion *fw_mode = &(card_info.fw_mode);
|
||||
sdk_version->setValue(fmt::format("{}.{}.{}-{} (v{})", fw_mode->major, fw_mode->minor, fw_mode->micro, fw_mode->relstep, fw_mode->value));
|
||||
const SdkAddOnVersion fw_mode = card_info.fw_mode.sdk_addon_version;
|
||||
sdk_version->setValue(fmt::format("{}.{}.{}-{} (v{})", fw_mode.major, fw_mode.minor, fw_mode.micro, fw_mode.relstep, fw_mode.value));
|
||||
|
||||
u8 compat_type = card_info.compatibility_type;
|
||||
compatibility_type->setValue(fmt::format("{} ({})", \
|
||||
compat_type >= GameCardCompatibilityType_Count ? "generic/unknown"_i18n : gamecardGetCompatibilityTypeString(compat_type), \
|
||||
compat_type));
|
||||
|
||||
package_id->setValue(fmt::format("{:016X}", card_header.package_id));
|
||||
|
||||
this->list->addView(properties_table);
|
||||
|
||||
/* ListItem elements. */
|
||||
|
|
6
todo.txt
6
todo.txt
|
@ -6,11 +6,11 @@ todo:
|
|||
title: more functions for content lookup? (based on id)
|
||||
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
|
||||
|
||||
gamecard: functions to display filelist
|
||||
gamecard: functions to display filelist (devoptab device?)
|
||||
|
||||
pfs0: functions to display filelist
|
||||
pfs0: functions to display filelist (devoptab device?)
|
||||
|
||||
romfs: functions to display filelist
|
||||
romfs: functions to display filelist (devoptab device?)
|
||||
|
||||
usb: change buffer size?
|
||||
usb: change chunk size?
|
||||
|
|
Loading…
Reference in a new issue