1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-26 20:22:17 +00:00
nxdumptool/source/core/nxdt_utils.c

1579 lines
44 KiB
C
Raw Normal View History

2020-04-16 01:06:41 +01:00
/*
2021-03-26 04:35:14 +00:00
* nxdt_utils.c
2020-04-16 01:06:41 +01:00
*
2024-04-12 10:47:36 +01:00
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
2020-04-16 01:06:41 +01:00
*
* nxdumptool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
2020-04-16 01:06:41 +01:00
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2020-04-16 01:06:41 +01:00
*/
#include <sys/statvfs.h>
#include <core/nxdt_utils.h>
#include <core/keys.h>
#include <core/gamecard.h>
#include <core/services.h>
#include <core/nca.h>
#include <core/usb.h>
#include <core/title.h>
#include <core/bfttf.h>
#include <core/nxdt_bfsar.h>
#include <core/devoptab/nxdt_devoptab.h>
#include <core/fatfs/ff.h>
2020-04-15 21:50:07 +01:00
/* Type definitions. */
/* Reference: https://github.com/Atmosphere-NX/Atmosphere/blob/master/exosphere/program/source/smc/secmon_smc_info.hpp. */
typedef struct {
SdkAddOnVersion target_firmware;
u8 key_generation;
u8 ams_ver_micro;
u8 ams_ver_minor;
u8 ams_ver_major;
} UtilsExosphereApiVersion;
NXDT_ASSERT(UtilsExosphereApiVersion, 0x8);
typedef struct {
u32 major;
u32 minor;
u32 micro;
} UtilsApplicationVersion;
2020-04-15 21:50:07 +01:00
/* Global variables. */
extern int __system_argc;
extern char **__system_argv;
static bool g_resourcesInit = false;
2020-05-03 00:40:50 +01:00
static Mutex g_resourcesMutex = 0;
static const char *g_appLaunchPath = NULL;
2020-05-03 00:40:50 +01:00
static FsFileSystem *g_sdCardFileSystem = NULL;
static int g_nxLinkSocketFd = -1;
2020-05-03 00:40:50 +01:00
2020-04-16 11:13:11 +01:00
static u8 g_customFirmwareType = UtilsCustomFirmwareType_Unknown;
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
static u8 g_productModel = SetSysProductModel_Invalid;
2024-05-26 12:31:43 +01:00
static bool g_isTerraUnit = false, g_isDevUnit = false;
static AppletType g_programAppletType = AppletType_None;
static FsStorage g_emmcBisSystemPartitionStorage = {0};
static FATFS *g_emmcBisSystemPartitionFatFsObj = NULL;
2020-04-15 21:50:07 +01:00
static AppletHookCookie g_systemOverclockCookie = {0};
static bool g_longRunningProcess = false;
static const char *g_sizeSuffixes[] = { "B", "KiB", "MiB", "GiB", "TiB" };
static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes);
static const char g_illegalFileSystemChars[] = "\\/:*?\"<>|";
static const size_t g_illegalFileSystemCharsLength = (MAX_ELEMENTS(g_illegalFileSystemChars) - 1);
static bool g_appUpdated = false;
static const SplConfigItem SplConfigItem_ExosphereApiVersion = (SplConfigItem)65000;
2024-10-08 00:09:46 +01:00
static const SplConfigItem SplConfigItem_ExosphereEmummcType = (SplConfigItem)65007;
static UtilsExosphereApiVersion g_exosphereApiVersion = {0};
2024-10-08 00:09:46 +01:00
static bool g_exosphereIsEmummc = false;
2020-04-15 21:50:07 +01:00
/* Function prototypes. */
static void _utilsGetLaunchPath(void);
static bool utilsGetExosphereApiVersion(void);
2024-10-08 00:09:46 +01:00
static bool utilsGetExosphereEmummcType(void);
static void _utilsGetCustomFirmwareType(void);
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
static bool _utilsGetProductModel(void);
2024-05-26 12:31:43 +01:00
static bool utilsGetDevelopmentUnitFlag(void);
static bool utilsGetTerraUnitFlag(void);
2020-05-03 00:40:50 +01:00
static bool utilsMountEmmcBisSystemPartitionStorage(void);
static void utilsUnmountEmmcBisSystemPartitionStorage(void);
static void utilsOverclockSystem(bool overclock);
2020-04-15 21:50:07 +01:00
static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param);
static void utilsChangeHomeButtonBlockStatus(bool block);
static size_t utilsGetUtf8StringLimit(const char *str, size_t str_size, size_t byte_limit);
static char utilsConvertHexDigitToBinary(char c);
bool utilsInitializeResources(void)
2020-05-03 00:40:50 +01:00
{
Result rc = 0;
bool ret = false;
SCOPED_LOCK(&g_resourcesMutex)
{
ret = g_resourcesInit;
if (ret) break;
/* Lock applet exit. */
appletLockExit();
/* Retrieve pointer to the application launch path. */
_utilsGetLaunchPath();
/* Retrieve pointer to the SD card FsFileSystem element. */
if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem(DEVOPTAB_SDMC_DEVICE)))
{
LOG_MSG_ERROR("Failed to retrieve FsFileSystem object for the SD card!");
break;
}
2024-03-31 14:04:14 +01:00
/*FsFileSystemAttribute fs_attr = {0};
if (R_SUCCEEDED(fsFsGetFileSystemAttribute(g_sdCardFileSystem, &fs_attr)))
{
LOG_DATA_INFO(&fs_attr, sizeof(FsFileSystemAttribute), "SD card FS attributes:");
}*/
/* Initialize needed services. */
if (!servicesInitialize()) break;
/* Check if a valid nxlink host IP address was set by libnx. */
/* If so, initialize nxlink connection without redirecting stdout and/or stderr. */
if (__nxlink_host.s_addr != 0 && __nxlink_host.s_addr != INADDR_NONE) g_nxLinkSocketFd = nxlinkConnectToHost(false, false);
#if LOG_LEVEL <= LOG_LEVEL_INFO
/* Log info messages. */
LOG_MSG_INFO(APP_TITLE " v" APP_VERSION " starting (" GIT_REV "). Built on " BUILD_TIMESTAMP ".");
if (g_nxLinkSocketFd >= 0) LOG_MSG_INFO("nxlink enabled! Host IP address: %s.", inet_ntoa(__nxlink_host));
#endif
/* Retrieve Exosphère API version. */
if (!utilsGetExosphereApiVersion())
{
LOG_MSG_ERROR("Failed to retrieve Exosphère API version!");
break;
}
2024-10-08 00:09:46 +01:00
/* Retrieve Exosphère emuMMC status. */
if (!utilsGetExosphereEmummcType())
{
LOG_MSG_ERROR("Failed to retrieve Exosphère emuMMC status!");
break;
}
/* Retrieve custom firmware type. */
_utilsGetCustomFirmwareType();
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
/* Get product model. */
if (!_utilsGetProductModel()) break;
/* Get development unit flag. */
2024-05-26 12:31:43 +01:00
if (!utilsGetDevelopmentUnitFlag()) break;
/* Get Terra unit flag. */
if (!utilsGetTerraUnitFlag()) break;
/* Get applet type. */
g_programAppletType = appletGetAppletType();
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
2024-10-08 00:09:46 +01:00
#if LOG_LEVEL <= LOG_LEVEL_INFO
/* Log info messages. */
u32 hos_version = hosversionGet();
LOG_MSG_INFO("Console info:\r\n" \
"- Horizon OS version: %u.%u.%u.\r\n" \
"- CFW: %s.\r\n" \
"- eMMC type: %s.\r\n" \
"- SoC type: %s\r\n" \
"- Development unit: %s.\r\n" \
"- Terra flag: %s.\r\n" \
"- Execution mode: %s.", \
HOSVER_MAJOR(hos_version), HOSVER_MINOR(hos_version), HOSVER_MICRO(hos_version), \
(g_customFirmwareType == UtilsCustomFirmwareType_Atmosphere ? "Atmosphère" : (g_customFirmwareType == UtilsCustomFirmwareType_SXOS ? "SX OS" : g_customFirmwareType == UtilsCustomFirmwareType_ReiNX ? "ReiNX" : "Unknown")), \
g_exosphereIsEmummc ? "emuMMC" : "sysMMC", \
utilsIsMarikoUnit() ? "Mariko" : "Erista", \
g_isDevUnit ? "yes" : "no", \
g_isTerraUnit ? "yes" : "no", \
utilsIsAppletMode() ? "applet" : "title override");
LOG_MSG_INFO("Exosphère API version info:\r\n" \
"- Release version: %u.%u.%u.\r\n" \
"- PKG1 key generation: %u (0x%02X).\r\n" \
"- Target firmware: %u.%u.%u.", \
g_exosphereApiVersion.ams_ver_major, g_exosphereApiVersion.ams_ver_minor, g_exosphereApiVersion.ams_ver_micro, \
g_exosphereApiVersion.key_generation, !g_exosphereApiVersion.key_generation ? g_exosphereApiVersion.key_generation : (g_exosphereApiVersion.key_generation + 1), \
g_exosphereApiVersion.target_firmware.major, g_exosphereApiVersion.target_firmware.minor, g_exosphereApiVersion.target_firmware.micro);
#endif
if (g_appLaunchPath)
{
LOG_MSG_INFO("Launch path: \"%s\".", g_appLaunchPath);
/* Move NRO if the launch path isn't the right one, then return. */
/* TODO: uncomment this block whenever we are ready for a release. */
/*if (strcmp(g_appLaunchPath, NRO_PATH) != 0)
{
utilsCreateDirectoryTree(NRO_PATH, false);
remove(NRO_PATH);
rename(g_appLaunchPath, NRO_PATH);
LOG_MSG_INFO("Moved NRO to \"%s\". Please reload the application.", NRO_PATH);
break;
}*/
}
/* Initialize HTTP interface. */
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
/* cURL must be initialized before starting any other threads. */
if (!httpInitialize()) break;
/* Initialize USB interface. */
if (!usbInitialize()) break;
/* Initialize USB Mass Storage interface. */
if (!umsInitialize()) break;
/* Load keyset. */
if (!keysLoadKeyset()) break;
/* Allocate NCA crypto buffer. */
if (!ncaAllocateCryptoBuffer())
{
LOG_MSG_ERROR("Unable to allocate memory for NCA crypto buffer!");
break;
}
/* Initialize gamecard interface. */
if (!gamecardInitialize()) break;
/* Initialize title interface. */
if (!titleInitialize()) break;
/* Initialize BFTTF interface. */
if (!bfttfInitialize()) break;
/* Initialize BFSAR interface. */
//if (!bfsarInitialize()) break;
/* Mount eMMC BIS System partition. */
if (!utilsMountEmmcBisSystemPartitionStorage()) break;
/* Mount application RomFS. */
rc = romfsInit();
if (R_FAILED(rc))
{
LOG_MSG_ERROR("Failed to mount " APP_TITLE "'s RomFS container!");
break;
}
/* Initialize configuration interface. */
if (!configInitialize()) break;
/* Setup an applet hook to change the hardware clocks after a system mode change (docked <-> undocked). */
appletHook(&g_systemOverclockCookie, utilsOverclockSystemAppletHook, NULL);
/* Enable video recording if we're running under title override mode. */
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
if (!utilsIsAppletMode())
{
bool flag = false;
rc = appletIsGamePlayRecordingSupported(&flag);
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.
2023-05-24 20:05:34 +01:00
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. */
ret = g_resourcesInit = true;
}
if (!ret)
{
char *msg = NULL;
size_t msg_size = 0;
/* Generate error message. */
utilsAppendFormattedStringToBuffer(&msg, &msg_size, "An error occurred while initializing resources.");
#if LOG_LEVEL <= LOG_LEVEL_ERROR
/* Get last log message. */
char *log_msg = logGetLastMessage();
if (log_msg)
{
utilsAppendFormattedStringToBuffer(&msg, &msg_size, "\n\n%s", log_msg);
free(log_msg);
}
#endif
/* Print error message. */
utilsPrintConsoleError(msg);
/* Free error message. */
if (msg) free(msg);
}
2020-05-03 00:40:50 +01:00
return ret;
}
void utilsCloseResources(void)
{
SCOPED_LOCK(&g_resourcesMutex)
{
2024-03-31 14:04:14 +01:00
LOG_MSG_INFO("Shutting down...");
/* Unmount all custom devoptab devices. */
devoptabUnmountAllDevices();
/* Unset long running process state. */
utilsSetLongRunningProcessState(false);
/* Unset our overclock applet hook. */
appletUnhook(&g_systemOverclockCookie);
/* Close configuration interface. */
configExit();
/* Unmount application RomFS. */
romfsExit();
/* Unmount eMMC BIS System partition. */
utilsUnmountEmmcBisSystemPartitionStorage();
/* Deinitialize BFSAR interface. */
//bfsarExit();
/* Deinitialize BFTTF interface. */
bfttfExit();
/* Deinitialize title interface. */
titleExit();
/* Deinitialize gamecard interface. */
gamecardExit();
/* Free NCA crypto buffer. */
ncaFreeCryptoBuffer();
/* Close USB Mass Storage interface. */
umsExit();
/* Close USB interface. */
usbExit();
/* Close HTTP interface. */
httpExit();
/* Close nxlink socket. */
if (g_nxLinkSocketFd >= 0)
{
close(g_nxLinkSocketFd);
g_nxLinkSocketFd = -1;
}
/* Close initialized services. */
servicesClose();
/* Replace application NRO (if needed). */
/* TODO: uncomment this block whenever we're ready for a release. */
/*if (g_appUpdated)
{
remove(NRO_PATH);
rename(NRO_TMP_PATH, NRO_PATH);
}*/
#if LOG_LEVEL <= LOG_LEVEL_ERROR
/* Close logfile. */
logCloseLogFile();
#endif
/* Unlock applet exit. */
appletUnlockExit();
g_resourcesInit = false;
}
}
const char *utilsGetLaunchPath(void)
{
return g_appLaunchPath;
}
int utilsGetNxLinkFileDescriptor(void)
{
return g_nxLinkSocketFd;
}
FsFileSystem *utilsGetSdCardFileSystemObject(void)
{
return g_sdCardFileSystem;
}
bool utilsCommitSdCardFileSystemChanges(void)
{
return (g_sdCardFileSystem ? R_SUCCEEDED(fsFsCommit(g_sdCardFileSystem)) : false);
}
u32 utilsGetAtmosphereVersion(void)
{
return MAKEHOSVERSION(g_exosphereApiVersion.ams_ver_major, g_exosphereApiVersion.ams_ver_minor, g_exosphereApiVersion.ams_ver_micro);
}
u8 utilsGetAtmosphereKeyGeneration(void)
{
return g_exosphereApiVersion.key_generation;
}
void utilsGetAtmosphereTargetFirmware(SdkAddOnVersion *out)
{
memcpy(out, &(g_exosphereApiVersion.target_firmware), sizeof(SdkAddOnVersion));
}
2024-10-08 00:09:46 +01:00
bool utilsGetAtmosphereEmummcStatus(void)
{
return g_exosphereIsEmummc;
}
u8 utilsGetCustomFirmwareType(void)
{
return g_customFirmwareType;
}
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
bool utilsIsMarikoUnit(void)
{
return (g_productModel > SetSysProductModel_Copper);
}
bool utilsIsDevelopmentUnit(void)
{
return g_isDevUnit;
}
2024-05-26 12:31:43 +01:00
bool utilsIsTerraUnit(void)
{
return g_isTerraUnit;
}
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
bool utilsIsAppletMode(void)
{
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
return (g_programAppletType > AppletType_Application && g_programAppletType < AppletType_SystemApplication);
}
FsStorage *utilsGetEmmcBisSystemPartitionStorage(void)
{
return &g_emmcBisSystemPartitionStorage;
}
void utilsSetLongRunningProcessState(bool state)
{
SCOPED_LOCK(&g_resourcesMutex)
{
/* Don't proceed if resources haven't been initialized, or if the requested state matches the current one. */
if (!g_resourcesInit || state == g_longRunningProcess) break;
/* Change HOME button block status. */
utilsChangeHomeButtonBlockStatus(state);
/* Enable/disable screen dimming and auto sleep. */
appletSetMediaPlaybackState(state);
/* Enable/disable system overclock. */
utilsOverclockSystem(configGetBoolean("overclock") & state);
/* Update flag. */
g_longRunningProcess = state;
}
2020-05-03 00:40:50 +01:00
}
bool utilsCreateThread(Thread *out_thread, ThreadFunc func, void *arg, int cpu_id)
{
/* Core 3 is reserved for HOS, so we can only use cores 0, 1 and 2. */
/* -2 can be provided to use the default process core. */
if (!out_thread || !func || (cpu_id < 0 && cpu_id != -2) || cpu_id > 2)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
Result rc = 0;
u64 core_mask = 0;
size_t stack_size = 0x20000; /* Same value as libnx's newlib. */
bool success = false;
memset(out_thread, 0, sizeof(Thread));
/* Get process core mask. */
rc = svcGetInfo(&core_mask, InfoType_CoreMask, CUR_PROCESS_HANDLE, 0);
if (R_FAILED(rc))
{
LOG_MSG_ERROR("svcGetInfo failed! (0x%X).", rc);
goto end;
}
/* Create thread. */
/* Enable preemptive multithreading by using priority 0x3B. */
rc = threadCreate(out_thread, func, arg, NULL, stack_size, 0x3B, cpu_id);
if (R_FAILED(rc))
{
LOG_MSG_ERROR("threadCreate failed! (0x%X).", rc);
goto end;
}
/* Set thread core mask. */
rc = svcSetThreadCoreMask(out_thread->handle, cpu_id == -2 ? -1 : cpu_id, core_mask);
if (R_FAILED(rc))
{
LOG_MSG_ERROR("svcSetThreadCoreMask failed! (0x%X).", rc);
goto end;
}
/* Start thread. */
rc = threadStart(out_thread);
if (R_FAILED(rc))
{
LOG_MSG_ERROR("threadStart failed! (0x%X).", rc);
goto end;
}
success = true;
end:
if (!success && out_thread->handle != INVALID_HANDLE) threadClose(out_thread);
return success;
}
void utilsJoinThread(Thread *thread)
{
if (!thread || thread->handle == INVALID_HANDLE)
{
LOG_MSG_ERROR("Invalid parameters!");
return;
}
Result rc = threadWaitForExit(thread);
if (R_FAILED(rc))
{
LOG_MSG_ERROR("threadWaitForExit failed! (0x%X).", rc);
return;
}
threadClose(thread);
memset(thread, 0, sizeof(Thread));
}
__attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...)
{
bool use_log = false;
SCOPED_LOCK(&g_resourcesMutex) use_log = g_resourcesInit;
*FormattedStringToBuffer: relax input validation Affects both utilsAppendFormattedStringToBuffer() and logWriteFormattedStringToBuffer(). Fixes logging issues within both the exception handler and memory debugging code. Other changes include: * bfttf: rename BfttfFontType_Total -> BfttfFontType_Count. * config: rename "append_authoringtool_data" -> "generate_authoringtool_data". * fs_ext: update FsGameCardCertificate struct * gamecard: fix gamecardReadLotusAsicFirmwareBlob() not returning false if FS .data segment memory couldn't be retrieved; update GameCardInfo struct to reflect a recently discovered area that's not zeroed out. * mem: expand MemoryProgramSegmentType enum; define extra macros for PID buffer size and memory page type checks; force empty memory page attribute and R/X permission checks while looking for the last FS .text segment; make memory page filtering code more readable. * npdm: rename KernelCapability enums; rename entry_value -> bitmask in all KernelCapability descriptor structs. * tik: force byte-for-byte memory lookup while dumping volatile tickets. * libs: update libusbhsfs to latest commit. * codebase: add missing comments to some enums; add missing "None" and "Count" elements to some enums. "Count" entries from enums with bitmasks will only reflect the number of available bitmask values. Enums with hex values remain unchanged. * PoC builds: use EXIT_SUCCESS and EXIT_FAILURE as return values. * nxdt_rw_poc: move "end" jump label within main() to avoid a crash if utilsInitializeResources() fails; reflect changes in config.c.
2023-07-17 00:03:05 +01:00
if (!dst || !dst_size || !fmt || !*fmt)
{
if (use_log) LOG_MSG_ERROR("Invalid parameters!");
return false;
}
va_list args;
int formatted_str_len = 0;
size_t formatted_str_len_cast = 0;
char *dst_ptr = *dst, *tmp_str = NULL;
size_t dst_cur_size = *dst_size, dst_str_len = (dst_ptr ? strlen(dst_ptr) : 0);
bool success = false;
*FormattedStringToBuffer: relax input validation Affects both utilsAppendFormattedStringToBuffer() and logWriteFormattedStringToBuffer(). Fixes logging issues within both the exception handler and memory debugging code. Other changes include: * bfttf: rename BfttfFontType_Total -> BfttfFontType_Count. * config: rename "append_authoringtool_data" -> "generate_authoringtool_data". * fs_ext: update FsGameCardCertificate struct * gamecard: fix gamecardReadLotusAsicFirmwareBlob() not returning false if FS .data segment memory couldn't be retrieved; update GameCardInfo struct to reflect a recently discovered area that's not zeroed out. * mem: expand MemoryProgramSegmentType enum; define extra macros for PID buffer size and memory page type checks; force empty memory page attribute and R/X permission checks while looking for the last FS .text segment; make memory page filtering code more readable. * npdm: rename KernelCapability enums; rename entry_value -> bitmask in all KernelCapability descriptor structs. * tik: force byte-for-byte memory lookup while dumping volatile tickets. * libs: update libusbhsfs to latest commit. * codebase: add missing comments to some enums; add missing "None" and "Count" elements to some enums. "Count" entries from enums with bitmasks will only reflect the number of available bitmask values. Enums with hex values remain unchanged. * PoC builds: use EXIT_SUCCESS and EXIT_FAILURE as return values. * nxdt_rw_poc: move "end" jump label within main() to avoid a crash if utilsInitializeResources() fails; reflect changes in config.c.
2023-07-17 00:03:05 +01:00
/* Sanity check. */
if (dst_cur_size && dst_str_len >= dst_cur_size)
{
if (use_log) LOG_MSG_ERROR("String length is equal to or greater than the provided buffer size! (0x%lX >= 0x%lX).", dst_str_len, dst_cur_size);
return false;
}
va_start(args, fmt);
/* Get formatted string length. */
formatted_str_len = vsnprintf(NULL, 0, fmt, args);
if (formatted_str_len <= 0)
{
if (use_log) LOG_MSG_ERROR("Failed to retrieve formatted string length!");
goto end;
}
formatted_str_len_cast = (size_t)(formatted_str_len + 1);
*FormattedStringToBuffer: relax input validation Affects both utilsAppendFormattedStringToBuffer() and logWriteFormattedStringToBuffer(). Fixes logging issues within both the exception handler and memory debugging code. Other changes include: * bfttf: rename BfttfFontType_Total -> BfttfFontType_Count. * config: rename "append_authoringtool_data" -> "generate_authoringtool_data". * fs_ext: update FsGameCardCertificate struct * gamecard: fix gamecardReadLotusAsicFirmwareBlob() not returning false if FS .data segment memory couldn't be retrieved; update GameCardInfo struct to reflect a recently discovered area that's not zeroed out. * mem: expand MemoryProgramSegmentType enum; define extra macros for PID buffer size and memory page type checks; force empty memory page attribute and R/X permission checks while looking for the last FS .text segment; make memory page filtering code more readable. * npdm: rename KernelCapability enums; rename entry_value -> bitmask in all KernelCapability descriptor structs. * tik: force byte-for-byte memory lookup while dumping volatile tickets. * libs: update libusbhsfs to latest commit. * codebase: add missing comments to some enums; add missing "None" and "Count" elements to some enums. "Count" entries from enums with bitmasks will only reflect the number of available bitmask values. Enums with hex values remain unchanged. * PoC builds: use EXIT_SUCCESS and EXIT_FAILURE as return values. * nxdt_rw_poc: move "end" jump label within main() to avoid a crash if utilsInitializeResources() fails; reflect changes in config.c.
2023-07-17 00:03:05 +01:00
if (!dst_ptr || !dst_cur_size || formatted_str_len_cast > (dst_cur_size - dst_str_len))
{
/* Update buffer size. */
dst_cur_size = (dst_str_len + formatted_str_len_cast);
/* Reallocate buffer. */
tmp_str = realloc(dst_ptr, dst_cur_size);
if (!tmp_str)
{
if (use_log) LOG_MSG_ERROR("Failed to resize buffer to 0x%lX byte(s).", dst_cur_size);
goto end;
}
dst_ptr = tmp_str;
tmp_str = NULL;
/* Clear allocated area. */
memset(dst_ptr + dst_str_len, 0, formatted_str_len_cast);
/* Update pointers. */
*dst = dst_ptr;
*dst_size = dst_cur_size;
}
/* Generate formatted string. */
vsprintf(dst_ptr + dst_str_len, fmt, args);
*FormattedStringToBuffer: relax input validation Affects both utilsAppendFormattedStringToBuffer() and logWriteFormattedStringToBuffer(). Fixes logging issues within both the exception handler and memory debugging code. Other changes include: * bfttf: rename BfttfFontType_Total -> BfttfFontType_Count. * config: rename "append_authoringtool_data" -> "generate_authoringtool_data". * fs_ext: update FsGameCardCertificate struct * gamecard: fix gamecardReadLotusAsicFirmwareBlob() not returning false if FS .data segment memory couldn't be retrieved; update GameCardInfo struct to reflect a recently discovered area that's not zeroed out. * mem: expand MemoryProgramSegmentType enum; define extra macros for PID buffer size and memory page type checks; force empty memory page attribute and R/X permission checks while looking for the last FS .text segment; make memory page filtering code more readable. * npdm: rename KernelCapability enums; rename entry_value -> bitmask in all KernelCapability descriptor structs. * tik: force byte-for-byte memory lookup while dumping volatile tickets. * libs: update libusbhsfs to latest commit. * codebase: add missing comments to some enums; add missing "None" and "Count" elements to some enums. "Count" entries from enums with bitmasks will only reflect the number of available bitmask values. Enums with hex values remain unchanged. * PoC builds: use EXIT_SUCCESS and EXIT_FAILURE as return values. * nxdt_rw_poc: move "end" jump label within main() to avoid a crash if utilsInitializeResources() fails; reflect changes in config.c.
2023-07-17 00:03:05 +01:00
/* Update output flag. */
success = true;
end:
va_end(args);
return success;
}
void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
2020-05-03 00:40:50 +01:00
{
size_t str_size = 0, cur_pos = 0;
if (!str || !(str_size = strlen(str))) return;
u8 *ptr1 = (u8*)str, *ptr2 = ptr1;
ssize_t units = 0;
u32 code = 0;
bool repl = false;
while(cur_pos < str_size)
{
units = decode_utf8(&code, ptr1);
if (units < 0) break;
if (memchr(g_illegalFileSystemChars, (int)code, g_illegalFileSystemCharsLength) || code < 0x20 || (!ascii_only && code == 0x7F) || (ascii_only && code >= 0x7F))
{
if (!repl)
{
*ptr2++ = '_';
repl = true;
}
} else {
if (ptr2 != ptr1) memmove(ptr2, ptr1, (size_t)units);
ptr2 += units;
repl = false;
}
ptr1 += units;
cur_pos += (size_t)units;
}
*ptr2 = '\0';
2020-04-15 21:50:07 +01:00
}
char *utilsEscapeCharacters(const char *str, const char *chars_to_escape, const char escape_char)
{
size_t str_size = 0, chars_to_escape_size = 0;
if (!str || !(str_size = strlen(str)) || !chars_to_escape || !(chars_to_escape_size = strlen(chars_to_escape)) || \
escape_char < 0x20 || escape_char >= 0x7F || memchr(chars_to_escape, (int)escape_char, chars_to_escape_size))
{
LOG_MSG_ERROR("Invalid parameters!");
return NULL;
}
ssize_t units = 0;
u32 code = 0, escape_cnt = 0;
const u8 *ptr = (const u8*)str;
size_t cur_pos = 0, escaped_str_size = 0;
char *ret = NULL;
/* Determine the number of character we need to escape. */
while(cur_pos < str_size)
{
units = decode_utf8(&code, ptr);
if (units < 0) break;
if (memchr(chars_to_escape, (int)code, chars_to_escape_size)) escape_cnt++;
ptr += units;
cur_pos += (size_t)units;
}
/* Short-circuit: check if we don't have to escape anything. */
/* If so, we'll just duplicate the provided string and call it a day. */
if (!escape_cnt)
{
ret = strdup(str);
goto end;
}
/* Calculate escaped string size. */
escaped_str_size = (str_size + escape_cnt);
/* Allocate memory for the output string. */
ret = calloc(sizeof(char), escaped_str_size + 1);
if (!ret)
{
LOG_MSG_ERROR("Failed to allocate memory for the output string! (0x%lX).", escaped_str_size + 1);
goto end;
}
/* Reset current position. */
ptr = (const u8*)str;
cur_pos = 0;
/* Copy characters and deal with the ones that need to be escaped. */
while(cur_pos < escaped_str_size)
{
units = decode_utf8(&code, ptr);
if (units < 0) break;
if (memchr(chars_to_escape, (int)code, chars_to_escape_size)) ret[cur_pos++] = escape_char;
for(ssize_t i = 0; i < units; i++) ret[cur_pos + (size_t)i] = ptr[i];
ptr += units;
cur_pos += (size_t)units;
}
end:
return ret;
}
2020-05-03 00:40:50 +01:00
void utilsTrimString(char *str)
2020-04-15 21:50:07 +01:00
{
2020-05-03 00:40:50 +01:00
size_t strsize = 0;
char *start = NULL, *end = NULL;
2020-05-03 00:40:50 +01:00
if (!str || !(strsize = strlen(str))) return;
2020-05-03 00:40:50 +01:00
start = str;
end = (start + strsize);
2020-05-03 00:40:50 +01:00
while(--end >= start)
{
if (!isspace((unsigned char)*end)) break;
}
2020-05-03 00:40:50 +01:00
*(++end) = '\0';
2020-05-03 00:40:50 +01:00
while(isspace((unsigned char)*start)) start++;
2020-05-03 00:40:50 +01:00
if (start != str) memmove(str, start, end - start + 1);
2020-04-16 11:13:11 +01:00
}
void utilsGenerateHexString(char *dst, size_t dst_size, const void *src, size_t src_size, bool uppercase)
{
if (!src || !src_size || !dst || dst_size < ((src_size * 2) + 1)) return;
size_t i, j;
const u8 *src_u8 = (const u8*)src;
for(i = 0, j = 0; i < src_size; i++)
{
2020-04-19 23:44:22 +01:00
char h_nib = ((src_u8[i] >> 4) & 0xF);
char l_nib = (src_u8[i] & 0xF);
dst[j++] = (h_nib + (h_nib < 0xA ? 0x30 : (uppercase ? 0x37 : 0x57)));
dst[j++] = (l_nib + (l_nib < 0xA ? 0x30 : (uppercase ? 0x37 : 0x57)));
}
dst[j] = '\0';
}
bool utilsParseHexString(void *dst, size_t dst_size, const char *src, size_t src_size)
{
u8 *dst_u8 = (u8*)dst;
bool success = true;
if (!dst || !dst_size || !src || !*src || (!src_size && !(src_size = strlen(src))) || (src_size % 2) != 0 || dst_size < (src_size / 2))
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
memset(dst, 0, dst_size);
for(size_t i = 0; i < src_size; i++)
{
char val = utilsConvertHexDigitToBinary(src[i]);
if (val == 'z')
{
LOG_MSG_ERROR("Invalid hex character in string \"%s\" at position %lu!", src, i);
success = false;
break;
}
if ((i & 1) == 0) val <<= 4;
dst_u8[i >> 1] |= val;
}
return success;
}
void utilsGenerateFormattedSizeString(double size, char *dst, size_t dst_size)
{
if (!dst || dst_size < 2) return;
size = fabs(size);
for(u32 i = 0; i < g_sizeSuffixesCount; i++)
{
if (size >= pow(1024.0, i + 1) && (i + 1) < g_sizeSuffixesCount) continue;
size /= pow(1024.0, i);
/* Don't display decimal places if we're dealing with plain bytes. */
snprintf(dst, dst_size, "%.*f %s", i == 0 ? 0 : 2, size, g_sizeSuffixes[i]);
break;
}
}
bool utilsGetFileSystemStatsByPath(const char *path, u64 *out_total, u64 *out_free)
2020-08-01 05:43:55 +01:00
{
char *name_end = NULL, stat_path[32] = {0};
struct statvfs info = {0};
int ret = -1;
if (!path || !*path || !(name_end = strchr(path, ':')) || *(name_end + 1) != '/' || (!out_total && !out_free))
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
name_end += 2;
[ci skip] Add GameCardImageDumpTask and FileWriter classes GameCardImageDumpTask is a derived class of DataTransferTask, and it's designed to dump a gamecard image using the options selected by the user (which must be passed from a GameCardImageDumpOptionsFrame object). It uses std::optional<std::string> as its return type -- the idea behind this is to return error strings that may later be displayed by an ErrorFrame during the dump process (views not yet implemented). FileWriter is a class that encapsulates write operations to different storage mediums (SD card, USB host and UMS devices), based on the provided input path. It is used by GameCardImageDumpTask to painlessly write data to the right output storage without explicitly having to implement multiple code paths for all storage types as part of the actual dump code. Furthermore, FileWriter also supports writing split files to FAT-formatted UMS devices if an output file is >= 4 GiB -- part file handling is completely abstracted away from any callers. Other changes include: * AsyncTask: rename all class methods to use PascalCase naming. * AsyncTask: rename get() -> GetResult(). * DataTransferTask: reflect the changes made to AsyncTask. * DataTransferTask: pause the RepeatingTask right after LoopCallback() returns true instead of pausing it in the cancel/post-execute callbacks. * DataTransferTask: add private FormatTimeString() method. * DataTransferTask: remove superfluous override for DoInBackground() -- classes derived from DataTransferTask must provide it on their own, anyway. * DataTransferTask: add public GetDurationString() method. * defines: update FAT32_FILESIZE_LIMIT macro to use UINT32_MAX. * defines: add CONCATENATION_FILE_PART_SIZE macro (used by the new FileWriter class). * DownloadTask: reflect the changes made to AsyncTask. * DumpOptionsFrame: file extension is no longer stored as a class member, nor required by the class constructor. * DumpOptionsFrame: change the return type for GetOutputFilePath() to bool. The method now saves its output to a variable passed by reference. * GameCardImageDumpOptionsFrame: reflect the changes made to DumpOptionsFrame. * i18n: update localization strings where applicable. * nxdt_utils: fix a potential buffer overflow in utilsGetFileSystemStatsByPath(). * OptionsTab: reflect the changes made to AsyncTask. * usb: add const qualifier to the input buffer required by usbSendFileData(). * usb: add const qualifier to the input buffer required by usbSendNspHeader().
2024-04-25 00:49:04 +01:00
snprintf(stat_path, MAX_ELEMENTS(stat_path), "%.*s", (int)(name_end - path), path);
if ((ret = statvfs(stat_path, &info)) != 0)
{
LOG_MSG_ERROR("statvfs failed for \"%s\"! (%d) (errno: %d).", stat_path, ret, errno);
return false;
}
if (out_total) *out_total = ((u64)info.f_blocks * (u64)info.f_frsize);
if (out_free) *out_free = ((u64)info.f_bfree * (u64)info.f_frsize);
return true;
2020-08-01 05:43:55 +01:00
}
2020-05-03 00:40:50 +01:00
bool utilsCheckIfFileExists(const char *path)
{
if (!path || !*path) return false;
2020-05-03 00:40:50 +01:00
FILE *chkfile = fopen(path, "rb");
if (chkfile)
2020-04-16 11:13:11 +01:00
{
2020-05-03 00:40:50 +01:00
fclose(chkfile);
return true;
2020-04-16 11:13:11 +01:00
}
2020-05-03 00:40:50 +01:00
return false;
2020-04-16 11:13:11 +01:00
}
void utilsRemoveConcatenationFile(const char *path)
{
if (!path || !*path) return;
remove(path);
fsdevDeleteDirectoryRecursively(path);
}
2020-05-03 00:40:50 +01:00
bool utilsCreateConcatenationFile(const char *path)
2020-04-15 21:50:07 +01:00
{
if (!path || !*path)
2020-05-03 00:40:50 +01:00
{
LOG_MSG_ERROR("Invalid parameters!");
2020-05-03 00:40:50 +01:00
return false;
}
/* Safety measure: remove any existant file/directory at the destination path. */
utilsRemoveConcatenationFile(path);
/* Create ConcatenationFile. */
/* If the call succeeds, the caller function will be able to operate on this file using stdio calls. */
2020-08-01 05:43:55 +01:00
Result rc = fsdevCreateFile(path, 0, FsCreateOption_BigFile);
if (R_FAILED(rc)) LOG_MSG_ERROR("fsdevCreateFile failed for \"%s\"! (0x%X).", path, rc);
2020-08-01 05:43:55 +01:00
return R_SUCCEEDED(rc);
2020-05-03 00:40:50 +01:00
}
void utilsCreateDirectoryTree(const char *path, bool create_last_element)
{
char *ptr = NULL, *tmp = NULL;
size_t path_len = 0;
if (!path || !(path_len = strlen(path))) return;
tmp = calloc(path_len + 1, sizeof(char));
if (!tmp) return;
ptr = strchr(path, '/');
while(ptr)
{
sprintf(tmp, "%.*s", (int)(ptr - path), path);
mkdir(tmp, 0777);
ptr = strchr(++ptr, '/');
}
if (create_last_element) mkdir(path, 0777);
free(tmp);
}
bool utilsGetDirectorySize(const char *path, u64 *out_size)
{
u64 total_size = 0;
char *name_end = NULL, *entry_path = NULL;
DIR *dir = NULL;
struct dirent *entry = NULL;
struct stat st = {0};
bool success = false;
/* Sanity checks. */
if (!path || !*path || !(name_end = strchr(path, ':')) || *(name_end + 1) != '/' || !*(name_end + 2) || !out_size)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
if (!(dir = opendir(path)))
{
LOG_MSG_ERROR("Failed to open directory \"%s\"! (%d).", path, errno);
goto end;
}
if (!(entry_path = calloc(1, FS_MAX_PATH)))
{
LOG_MSG_ERROR("Failed to allocate memory for path buffer!");
goto end;
}
/* Read directory entries. */
while((entry = readdir(dir)))
{
/* 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)
{
/* Get directory size. */
u64 dir_size = 0;
if (!utilsGetDirectorySize(entry_path, &dir_size)) goto end;
/* Update size. */
total_size += dir_size;
} else {
/* Get file properties. */
if (stat(entry_path, &st) != 0)
{
LOG_MSG_ERROR("Failed to stat file \"%s\"! (%d).", entry_path, errno);
goto end;
}
/* Update size. */
total_size += st.st_size;
}
}
/* Update output pointer. */
*out_size = total_size;
/* Update return value. */
success = true;
end:
if (entry_path) free(entry_path);
if (dir) closedir(dir);
return success;
}
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.
2023-05-24 20:05:34 +01:00
bool utilsDeleteDirectoryRecursively(const char *path)
{
char *name_end = NULL, *entry_path = NULL;
DIR *dir = NULL;
struct dirent *entry = NULL;
bool success = false;
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.
2023-05-24 20:05:34 +01:00
/* 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);
goto end;
}
if (!(entry_path = calloc(1, FS_MAX_PATH)))
{
LOG_MSG_ERROR("Failed to allocate memory for path buffer!");
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)) goto end;
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.
2023-05-24 20:05:34 +01:00
/* 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->d_type == DT_DIR ? "directory" : "file", entry_path, errno);
goto end;
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.
2023-05-24 20:05:34 +01:00
}
}
/* Close topmost directory so we can delete it. */
closedir(dir);
dir = NULL;
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.
2023-05-24 20:05:34 +01:00
success = (rmdir(path) == 0);
if (!success) LOG_MSG_ERROR("Failed to delete topmost directory \"%s\"! (%d).", path, errno);
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.
2023-05-24 20:05:34 +01:00
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)
{
LOG_MSG_ERROR("Invalid parameters!");
return NULL;
}
bool use_prefix = (prefix && *prefix);
size_t prefix_len = (use_prefix ? strlen(prefix) : 0);
bool append_path_sep = (use_prefix && prefix[prefix_len - 1] != '/' && *filename != '/');
bool use_extension = (extension && *extension);
size_t extension_len = (use_extension ? strlen(extension) : 0);
size_t path_len = (prefix_len + strlen(filename) + extension_len);
if (append_path_sep) path_len++;
const size_t max_filename_len = ((use_prefix && !strncmp(prefix, DEVOPTAB_SDMC_DEVICE, strlen(DEVOPTAB_SDMC_DEVICE))) ? SDMC_MAX_FILENAME_LENGTH : FS_MAX_FILENAME_LENGTH);
char *path = NULL, *ptr1 = NULL, *ptr2 = NULL;
bool filename_only = false, success = false;
/* Allocate memory for the output path. */
if (!(path = calloc(path_len + 1, sizeof(char))))
{
LOG_MSG_ERROR("Failed to allocate 0x%lX bytes for output path!", path_len);
goto end;
}
/* Generate output path. */
if (use_prefix) strcat(path, prefix);
if (append_path_sep) strcat(path, "/");
strcat(path, filename);
if (use_extension) strcat(path, extension);
/* Retrieve pointer to the first path separator. */
ptr1 = strchr(path, '/');
if (!ptr1)
{
filename_only = true;
ptr1 = path;
}
/* Make sure each path element doesn't exceed our max filename length. */
while(ptr1)
{
if (!filename_only)
{
/* End loop if we find a NULL terminator. */
if (!*ptr1++) break;
/* Get pointer to next path separator. */
ptr2 = strchr(ptr1, '/');
}
/* Get current path element size. */
size_t element_size = (ptr2 ? (size_t)(ptr2 - ptr1) : (path_len - (size_t)(ptr1 - path)));
/* Get UTF-8 string limit. */
/* Use our max filename length as the byte count limit. */
size_t last_cp_pos = utilsGetUtf8StringLimit(ptr1, element_size, max_filename_len);
if (last_cp_pos < element_size)
{
if (ptr2)
{
/* Truncate current element by moving the rest of the path to the current position. */
memmove(ptr1 + last_cp_pos, ptr2, path_len - (size_t)(ptr2 - path));
/* Update pointer. */
ptr2 -= (element_size - last_cp_pos);
} else
if (use_extension)
{
/* Truncate last element. Make sure to preserve the provided file extension. */
if (extension_len >= last_cp_pos)
{
LOG_MSG_ERROR("File extension length is >= truncated filename length! (0x%lX >= 0x%lX).", extension_len, last_cp_pos);
goto end;
}
memmove(ptr1 + last_cp_pos - extension_len, ptr1 + element_size - extension_len, extension_len);
}
path_len -= (element_size - last_cp_pos);
path[path_len] = '\0';
}
ptr1 = ptr2;
}
/* Check if the full length for the generated path is >= FS_MAX_PATH. */
if (path_len >= FS_MAX_PATH)
{
LOG_MSG_ERROR("Generated path length is >= FS_MAX_PATH! (0x%lX).", path_len);
goto end;
}
/* Update flag. */
success = true;
end:
if (!success && path)
{
free(path);
path = NULL;
}
return path;
}
void utilsPrintConsoleError(const char *msg)
{
PadState pad = {0};
/* Don't consider stick movement as button inputs. */
u64 flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \
HidNpadButton_StickRUp | HidNpadButton_StickRDown);
/* Configure input. */
/* Up to 8 different, full controller inputs. */
/* Individual Joy-Cons not supported. */
padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl);
padInitializeWithMask(&pad, 0x1000000FFUL);
/* Initialize console output. */
consoleInit(NULL);
/* Print message. */
if (msg && *msg)
{
printf("%s", msg);
} else {
printf("An error occurred.");
}
#if LOG_LEVEL < LOG_LEVEL_NONE
printf("\n\nFor more information, please check the logfile. Press any button to exit.");
#else
printf("\n\nPress any button to exit.");
#endif
consoleUpdate(NULL);
/* Wait until the user presses a button. */
while(appletMainLoop())
{
padUpdate(&pad);
if (padGetButtonsDown(&pad) & flag) break;
utilsAppletLoopDelay();
}
/* Deinitialize console output. */
consoleExit(NULL);
}
bool utilsGetApplicationUpdatedState(void)
{
bool ret = false;
SCOPED_LOCK(&g_resourcesMutex) ret = g_appUpdated;
return ret;
}
void utilsSetApplicationUpdatedState(void)
{
SCOPED_LOCK(&g_resourcesMutex) g_appUpdated = true;
}
bool utilsParseGitHubReleaseJsonData(const char *json_buf, size_t json_buf_size, UtilsGitHubReleaseJsonData *out)
{
if (!json_buf || !json_buf_size || !out)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
bool ret = false;
const char *published_at = NULL;
struct json_object *assets = NULL;
/* Free output buffer beforehand. */
utilsFreeGitHubReleaseJsonData(out);
/* Parse JSON object. */
out->obj = jsonParseFromString(json_buf, json_buf_size);
if (!out->obj)
{
LOG_MSG_ERROR("Failed to parse JSON object!");
return false;
}
/* Get required JSON elements. */
out->version = jsonGetString(out->obj, "tag_name");
out->commit_hash = jsonGetString(out->obj, "target_commitish");
published_at = jsonGetString(out->obj, "published_at");
out->changelog = jsonGetString(out->obj, "body");
assets = jsonGetArray(out->obj, "assets");
if (!out->version || !out->commit_hash || !published_at || !out->changelog || !assets)
{
LOG_MSG_ERROR("Failed to retrieve required elements from the provided JSON!");
goto end;
}
/* Parse release date. */
if (!strptime(published_at, "%Y-%m-%dT%H:%M:%SZ", &(out->date)))
{
LOG_MSG_ERROR("Failed to parse release date \"%s\"!", published_at);
goto end;
}
/* Loop through the assets array until we find the NRO. */
size_t assets_len = json_object_array_length(assets);
for(size_t i = 0; i < assets_len; i++)
{
struct json_object *cur_asset = NULL;
const char *asset_name = NULL;
/* Get current asset object. */
cur_asset = json_object_array_get_idx(assets, i);
if (!cur_asset) continue;
/* Get current asset name. */
asset_name = jsonGetString(cur_asset, "name");
if (!asset_name || strcmp(asset_name, NRO_NAME) != 0) continue;
/* Jackpot. Get the download URL. */
out->download_url = jsonGetString(cur_asset, "browser_download_url");
break;
}
if (!out->download_url)
{
LOG_MSG_ERROR("Failed to retrieve required elements from the provided JSON!");
goto end;
}
/* Update return value. */
ret = true;
end:
if (!ret) utilsFreeGitHubReleaseJsonData(out);
return ret;
}
bool utilsIsApplicationUpdatable(const char *version, const char *commit_hash)
{
if (!version || !*version || *version != 'v' || !commit_hash || !*commit_hash)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
bool ret = false;
UtilsApplicationVersion cur_version = { VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO }, new_version = {0};
/* Parse version string. */
sscanf(version, "v%u.%u.%u", &(new_version.major), &(new_version.minor), &(new_version.micro));
/* Compare versions. */
if (cur_version.major == new_version.major)
{
if (cur_version.minor == new_version.minor)
{
if (cur_version.micro == new_version.micro)
{
/* Versions are equal. Let's compare the commit hashes and return true if they're different. */
ret = (strncasecmp(commit_hash, GIT_COMMIT, 7) != 0);
} else
if (cur_version.micro < new_version.micro)
{
ret = true;
}
} else
if (cur_version.minor < new_version.minor)
{
ret = true;
}
} else
if (cur_version.major < new_version.major)
{
ret = true;
}
return ret;
}
static void _utilsGetLaunchPath(void)
2020-05-03 00:40:50 +01:00
{
if (__system_argc <= 0 || !__system_argv) return;
for(int i = 0; i < __system_argc; i++)
2020-05-03 00:40:50 +01:00
{
if (__system_argv[i] && !strncmp(__system_argv[i], DEVOPTAB_SDMC_DEVICE "/", strlen(DEVOPTAB_SDMC_DEVICE)))
2020-05-03 00:40:50 +01:00
{
g_appLaunchPath = __system_argv[i];
break;
2020-05-03 00:40:50 +01:00
}
}
2020-04-15 21:50:07 +01:00
}
/* SMC config item available in Atmosphère and Atmosphère-based CFWs. */
static bool utilsGetExosphereApiVersion(void)
{
Result rc = splGetConfig(SplConfigItem_ExosphereApiVersion, (u64*)&g_exosphereApiVersion);
bool ret = R_SUCCEEDED(rc);
if (!ret) LOG_MSG_ERROR("splGetConfig failed! (0x%X).", rc);
return ret;
}
2024-10-08 00:09:46 +01:00
/* SMC config item available in Atmosphère and Atmosphère-based CFWs. */
static bool utilsGetExosphereEmummcType(void)
{
u64 is_emummc = 0;
Result rc = splGetConfig(SplConfigItem_ExosphereEmummcType, &is_emummc);
bool ret = R_SUCCEEDED(rc);
g_exosphereIsEmummc = (ret && is_emummc);
if (!ret) LOG_MSG_ERROR("splGetConfig failed! (0x%X).", rc);
return ret;
}
static void _utilsGetCustomFirmwareType(void)
{
2021-04-20 23:43:37 +01:00
bool tx_srv = servicesCheckRunningServiceByName("tx");
bool rnx_srv = servicesCheckRunningServiceByName("rnx");
g_customFirmwareType = (rnx_srv ? UtilsCustomFirmwareType_ReiNX : (tx_srv ? UtilsCustomFirmwareType_SXOS : UtilsCustomFirmwareType_Atmosphere));
}
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
static bool _utilsGetProductModel(void)
{
Result rc = 0;
bool ret = false;
SetSysProductModel model = SetSysProductModel_Invalid;
rc = setsysGetProductModel(&model);
if (R_SUCCEEDED(rc) && model != SetSysProductModel_Invalid)
{
g_productModel = model;
ret = true;
} else {
LOG_MSG_ERROR("setsysGetProductModel failed! (0x%X) (%d).", rc, model);
}
return ret;
}
2024-05-26 12:31:43 +01:00
static bool utilsGetDevelopmentUnitFlag(void)
{
Result rc = 0;
bool tmp = false;
rc = splIsDevelopment(&tmp);
if (R_SUCCEEDED(rc))
{
g_isDevUnit = tmp;
} else {
LOG_MSG_ERROR("splIsDevelopment failed! (0x%X).", rc);
}
return R_SUCCEEDED(rc);
}
2024-05-26 12:31:43 +01:00
static bool utilsGetTerraUnitFlag(void)
{
/* Return right away if we're running under a HOS version that's too low. */
if (hosversionBefore(8, 0, 0)) return true;
Result rc = 0;
bool tmp = false;
rc = setsysGetT(&tmp);
if (R_SUCCEEDED(rc))
{
g_isTerraUnit = tmp;
} else {
LOG_MSG_ERROR("setsysGetT failed! (0x%X).", rc);
}
return R_SUCCEEDED(rc);
}
static bool utilsMountEmmcBisSystemPartitionStorage(void)
{
Result rc = 0;
FRESULT fr = FR_OK;
rc = fsOpenBisStorage(&g_emmcBisSystemPartitionStorage, FsBisPartitionId_System);
if (R_FAILED(rc))
{
LOG_MSG_ERROR("Failed to open eMMC BIS System partition storage! (0x%X).", rc);
return false;
}
2020-04-19 23:44:22 +01:00
g_emmcBisSystemPartitionFatFsObj = calloc(1, sizeof(FATFS));
if (!g_emmcBisSystemPartitionFatFsObj)
{
LOG_MSG_ERROR("Unable to allocate memory for FatFs element!");
return false;
}
fr = f_mount(g_emmcBisSystemPartitionFatFsObj, BIS_SYSTEM_PARTITION_MOUNT_NAME, 1);
if (fr != FR_OK)
{
LOG_MSG_ERROR("Failed to mount eMMC BIS System partition! (%u).", fr);
return false;
}
return true;
}
static void utilsUnmountEmmcBisSystemPartitionStorage(void)
{
2020-04-19 23:44:22 +01:00
if (g_emmcBisSystemPartitionFatFsObj)
{
f_unmount(BIS_SYSTEM_PARTITION_MOUNT_NAME);
2020-04-19 23:44:22 +01:00
free(g_emmcBisSystemPartitionFatFsObj);
g_emmcBisSystemPartitionFatFsObj = NULL;
}
if (serviceIsActive(&(g_emmcBisSystemPartitionStorage.s)))
{
fsStorageClose(&g_emmcBisSystemPartitionStorage);
memset(&g_emmcBisSystemPartitionStorage, 0, sizeof(FsStorage));
}
}
2020-05-03 00:40:50 +01:00
static void utilsOverclockSystem(bool overclock)
{
u32 cpu_rate = ((overclock ? CPU_CLKRT_OVERCLOCKED : CPU_CLKRT_NORMAL) * 1000000);
u32 mem_rate = ((overclock ? MEM_CLKRT_OVERCLOCKED : MEM_CLKRT_NORMAL) * 1000000);
servicesChangeHardwareClockRates(cpu_rate, mem_rate);
}
2020-05-03 00:40:50 +01:00
static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param)
{
NX_IGNORE_ARG(param);
/* Don't proceed if we're not dealing with a desired hook type. */
2020-05-03 00:40:50 +01:00
if (hook != AppletHookType_OnOperationMode && hook != AppletHookType_OnPerformanceMode) return;
/* Overclock the system based on the overclock setting and the current long running state value. */
SCOPED_LOCK(&g_resourcesMutex) utilsOverclockSystem(configGetBoolean("overclock") & g_longRunningProcess);
2020-05-03 00:40:50 +01:00
}
static void utilsChangeHomeButtonBlockStatus(bool block)
{
/* Only change HOME button blocking status if we're running as a regular application or a system application. */
Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption().
2023-04-08 12:34:53 +01:00
if (utilsIsAppletMode()) return;
if (block)
{
appletBeginBlockingHomeButtonShortAndLongPressed(0);
} else {
appletEndBlockingHomeButtonShortAndLongPressed();
}
}
static size_t utilsGetUtf8StringLimit(const char *str, size_t str_size, size_t byte_limit)
{
if (!str || !*str || !str_size || !byte_limit) return 0;
if (byte_limit > str_size) return str_size;
u32 code = 0;
ssize_t units = 0;
size_t cur_pos = 0, last_cp_pos = 0;
const u8 *str_u8 = (const u8*)str;
while(cur_pos < str_size && cur_pos < byte_limit)
{
units = decode_utf8(&code, str_u8 + cur_pos);
size_t new_pos = (cur_pos + (size_t)units);
if (units < 0 || !code || new_pos > str_size) break;
cur_pos = new_pos;
if (cur_pos < byte_limit) last_cp_pos = cur_pos;
}
return last_cp_pos;
}
static char utilsConvertHexDigitToBinary(char c)
{
if ('a' <= c && c <= 'f') return (c - 'a' + 0xA);
if ('A' <= c && c <= 'F') return (c - 'A' + 0xA);
if ('0' <= c && c <= '9') return (c - '0');
return 'z';
}