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

333 lines
11 KiB
C
Raw Normal View History

2020-04-16 01:06:41 +01:00
/*
* services.c
2020-04-16 01:06:41 +01:00
*
2023-04-08 12:42:22 +01:00
* Copyright (c) 2020-2023, 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
*/
2021-03-26 04:35:14 +00:00
#include "nxdt_utils.h"
2020-04-15 21:50:07 +01:00
#include "services.h"
#include "es.h"
/* Type definitions. */
typedef bool (*ServiceCondFunction)(void *arg); /* Used to perform a runtime condition check (e.g. system version) before initializing the service. */
typedef Result (*ServiceInitFunction)(void); /* Used to initialize the service. */
typedef void (*ServiceCloseFunction)(void); /* Used to close the service. */
2020-04-15 21:50:07 +01:00
typedef struct {
2020-04-15 21:50:07 +01:00
bool initialized;
char name[8];
ServiceCondFunction cond_func;
ServiceInitFunction init_func;
ServiceCloseFunction close_func;
} ServiceInfo;
2020-04-15 21:50:07 +01:00
/* Function prototypes. */
static bool _servicesCheckInitializedServiceByName(const char *name);
2021-04-20 23:43:37 +01:00
static Result servicesAtmosphereHasService(bool *out, SmServiceName name);
static Result servicesGetExosphereApiVersion(u32 *out);
2020-04-15 21:50:07 +01:00
static Result servicesNifmUserInitialize(void);
static bool servicesClkGetServiceType(void *arg);
/* Global variables. */
static ServiceInfo g_serviceInfo[] = {
2020-04-15 21:50:07 +01:00
{ false, "ncm", NULL, &ncmInitialize, &ncmExit },
{ false, "ns", NULL, &nsInitialize, &nsExit },
{ false, "csrng", NULL, &csrngInitialize, &csrngExit },
{ false, "spl:", NULL, &splInitialize, &splExit },
2020-04-15 21:50:07 +01:00
{ false, "pm:dmnt", NULL, &pmdmntInitialize, &pmdmntExit },
{ false, "psm", NULL, &psmInitialize, &psmExit },
{ false, "nifm:u", NULL, &servicesNifmUserInitialize, &nifmExit },
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
{ false, "clk", &servicesClkGetServiceType, NULL, NULL }, /* Placeholder for pcv / clkrst. */
2020-04-15 21:50:07 +01:00
{ false, "es", NULL, &esInitialize, &esExit },
{ false, "set", NULL, &setInitialize, &setExit },
{ false, "set:sys", NULL, &setsysInitialize, &setsysExit },
{ false, "set:cal", NULL, &setcalInitialize, &setcalExit },
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
{ false, "bsd:u", NULL, &socketInitializeDefault, &socketExit }, /* socketInitialize*() functions take care of initializing bsd:* too. */
{ false, "usb:ds", NULL, &usbDsInitialize, &usbDsExit }
2020-04-15 21:50:07 +01:00
};
static const u32 g_serviceInfoCount = MAX_ELEMENTS(g_serviceInfo);
static bool g_clkSvcUsePcv = false;
static ClkrstSession g_clkrstCpuSession = {0}, g_clkrstMemSession = {0};
2020-05-03 00:40:50 +01:00
static Mutex g_servicesMutex = 0;
static u32 g_atmosphereVersion = 0;
2021-04-20 23:43:37 +01:00
/* Atmosphère-related constants. */
static const u32 g_smAtmosphereHasService = 65100;
static const SplConfigItem SplConfigItem_ExosphereApiVersion = (SplConfigItem)65000;
static const u32 g_atmosphereTipcVersion = MAKEHOSVERSION(0, 19, 0);
2020-04-15 21:50:07 +01:00
bool servicesInitialize(void)
{
bool ret = true;
SCOPED_LOCK(&g_servicesMutex)
2020-04-15 21:50:07 +01:00
{
for(u32 i = 0; i < g_serviceInfoCount; i++)
2020-04-15 21:50:07 +01:00
{
ServiceInfo *service_info = &(g_serviceInfo[i]);
/* Check if this service has been already initialized. */
if (service_info->initialized) continue;
/* Check if this service depends on a condition function. */
if (service_info->cond_func != NULL)
{
/* Run the condition function - it will update the current service member. */
/* Skip this service if the required conditions aren't met. */
if (!service_info->cond_func(service_info)) continue;
}
/* Check if this service actually has a valid initialization function. */
if (service_info->init_func == NULL) continue;
/* Initialize service. */
Result rc = service_info->init_func();
if (R_FAILED(rc))
{
LOG_MSG_ERROR("Failed to initialize \"%s\" service! (0x%X).", service_info->name, rc);
ret = false;
break;
}
/* Update flag. */
service_info->initialized = true;
2020-04-15 21:50:07 +01:00
}
}
2020-04-15 21:50:07 +01:00
return ret;
}
void servicesClose(void)
{
SCOPED_LOCK(&g_servicesMutex)
2020-04-15 21:50:07 +01:00
{
for(u32 i = 0; i < g_serviceInfoCount; i++)
{
ServiceInfo *service_info = &(g_serviceInfo[i]);
/* Check if this service has not been initialized, or if it doesn't have a valid close function. */
if (!service_info->initialized || service_info->close_func == NULL) continue;
/* Close service. */
service_info->close_func();
/* Update flag. */
service_info->initialized = false;
}
2020-04-15 21:50:07 +01:00
}
}
bool servicesCheckInitializedServiceByName(const char *name)
{
bool ret = false;
SCOPED_LOCK(&g_servicesMutex) ret = _servicesCheckInitializedServiceByName(name);
return ret;
}
2020-04-15 21:50:07 +01:00
bool servicesCheckRunningServiceByName(const char *name)
{
bool ret = false;
SCOPED_LOCK(&g_servicesMutex)
2021-04-20 23:43:37 +01:00
{
if (!name || !*name || !_servicesCheckInitializedServiceByName("spl:"))
{
LOG_MSG_ERROR("Invalid parameters!");
break;
}
Result rc = servicesAtmosphereHasService(&ret, smEncodeName(name));
if (R_FAILED(rc)) LOG_MSG_ERROR("servicesAtmosphereHasService failed for \"%s\"! (0x%X).", name, rc);
2021-04-20 23:43:37 +01:00
}
return ret;
2020-04-15 21:50:07 +01:00
}
void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate)
2020-04-15 21:50:07 +01:00
{
SCOPED_LOCK(&g_servicesMutex)
{
if ((g_clkSvcUsePcv && !_servicesCheckInitializedServiceByName("pcv")) || (!g_clkSvcUsePcv && !_servicesCheckInitializedServiceByName("clkrst")))
{
LOG_MSG_ERROR("Error: clock service uninitialized.");
break;
}
Result rc1 = 0, rc2 = 0;
if (g_clkSvcUsePcv)
{
rc1 = pcvSetClockRate(PcvModule_CpuBus, cpu_rate);
rc2 = pcvSetClockRate(PcvModule_EMC, mem_rate);
} else {
rc1 = clkrstSetClockRate(&g_clkrstCpuSession, cpu_rate);
rc2 = clkrstSetClockRate(&g_clkrstMemSession, mem_rate);
}
if (R_FAILED(rc1)) LOG_MSG_ERROR("%sSetClockRate failed! (0x%X) (CPU).", (g_clkSvcUsePcv ? "pcv" : "clkrst"), rc1);
if (R_FAILED(rc2)) LOG_MSG_ERROR("%sSetClockRate failed! (0x%X) (MEM).", (g_clkSvcUsePcv ? "pcv" : "clkrst"), rc2);
}
}
static bool _servicesCheckInitializedServiceByName(const char *name)
{
if (!name || !*name) return false;
bool ret = false;
2020-04-15 21:50:07 +01:00
for(u32 i = 0; i < g_serviceInfoCount; i++)
{
ServiceInfo *service_info = &(g_serviceInfo[i]);
if (!strcmp(service_info->name, name))
2020-04-15 21:50:07 +01:00
{
ret = service_info->initialized;
2020-04-15 21:50:07 +01:00
break;
}
}
2020-04-15 21:50:07 +01:00
return ret;
}
2021-04-20 23:43:37 +01:00
/* SM API extension available in Atmosphère and Atmosphère-based CFWs. */
static Result servicesAtmosphereHasService(bool *out, SmServiceName name)
{
if (!out || !name.name[0]) return MAKERESULT(Module_Libnx, LibnxError_BadInput);
2021-04-20 23:43:37 +01:00
u8 tmp = 0;
Result rc = 0;
/* Get Exosphère API version. */
if (!g_atmosphereVersion)
{
rc = servicesGetExosphereApiVersion(&g_atmosphereVersion);
if (R_FAILED(rc)) LOG_MSG_ERROR("servicesGetExosphereApiVersion failed! (0x%X).", rc);
}
2021-04-20 23:43:37 +01:00
/* Check if service is running. */
/* Dispatch IPC request using CMIF or TIPC serialization depending on our current environment. */
if (hosversionAtLeast(12, 0, 0) || g_atmosphereVersion >= g_atmosphereTipcVersion)
{
2021-04-20 23:43:37 +01:00
rc = tipcDispatchInOut(smGetServiceSessionTipc(), g_smAtmosphereHasService, name, tmp);
} else {
rc = serviceDispatchInOut(smGetServiceSession(), g_smAtmosphereHasService, name, tmp);
}
if (R_SUCCEEDED(rc)) *out = (tmp != 0);
return rc;
}
/* SMC config item available in Atmosphère and Atmosphère-based CFWs. */
static Result servicesGetExosphereApiVersion(u32 *out)
{
if (!out) return MAKERESULT(Module_Libnx, LibnxError_BadInput);
2021-04-20 23:43:37 +01:00
Result rc = 0;
u64 cfg = 0;
u32 version = 0;
rc = splGetConfig(SplConfigItem_ExosphereApiVersion, &cfg);
if (R_SUCCEEDED(rc))
2021-04-20 23:43:37 +01:00
{
*out = version = (u32)((cfg >> 40) & 0xFFFFFF);
LOG_MSG_INFO("Exosphère API version: %u.%u.%u.", HOSVER_MAJOR(version), HOSVER_MINOR(version), HOSVER_MICRO(version));
2021-04-20 23:43:37 +01:00
}
return rc;
}
2020-04-15 21:50:07 +01:00
static Result servicesNifmUserInitialize(void)
{
return nifmInitialize(NifmServiceType_User);
}
static Result servicesClkrstInitialize(void)
{
Result rc = 0;
/* Open clkrst service handle. */
2020-04-15 21:50:07 +01:00
rc = clkrstInitialize();
if (R_FAILED(rc))
{
LOG_MSG_ERROR("clkrstInitialize failed! (0x%X).", rc);
return rc;
}
/* Initialize CPU and MEM clkrst sessions. */
2020-04-15 21:50:07 +01:00
memset(&g_clkrstCpuSession, 0, sizeof(ClkrstSession));
memset(&g_clkrstMemSession, 0, sizeof(ClkrstSession));
2020-04-15 21:50:07 +01:00
rc = clkrstOpenSession(&g_clkrstCpuSession, PcvModuleId_CpuBus, 3);
if (R_FAILED(rc))
{
LOG_MSG_ERROR("clkrstOpenSession failed! (0x%X) (CPU).", rc);
2020-04-15 21:50:07 +01:00
clkrstExit();
return rc;
}
2020-04-15 21:50:07 +01:00
rc = clkrstOpenSession(&g_clkrstMemSession, PcvModuleId_EMC, 3);
if (R_FAILED(rc))
{
LOG_MSG_ERROR("clkrstOpenSession failed! (0x%X) (MEM).", rc);
2020-04-15 21:50:07 +01:00
clkrstCloseSession(&g_clkrstCpuSession);
clkrstExit();
}
2020-04-15 21:50:07 +01:00
return rc;
}
static void servicesClkrstExit(void)
{
/* Close CPU and MEM clkrst sessions. */
2020-04-15 21:50:07 +01:00
clkrstCloseSession(&g_clkrstMemSession);
clkrstCloseSession(&g_clkrstCpuSession);
/* Close clkrst service handle. */
2020-04-15 21:50:07 +01:00
clkrstExit();
}
static bool servicesClkGetServiceType(void *arg)
{
if (!arg) return false;
ServiceInfo *info = (ServiceInfo*)arg;
if (strcmp(info->name, "clk") != 0 || info->init_func != NULL || info->close_func != NULL) return false;
/* Determine which service needs to be used to control hardware clock rates, depending on the system version. */
/* This may either be pcv (sysver lower than 8.0.0) or clkrst (sysver equal to or greater than 8.0.0). */
2020-04-15 21:50:07 +01:00
g_clkSvcUsePcv = hosversionBefore(8, 0, 0);
/* Fill service info. */
2020-04-15 21:50:07 +01:00
sprintf(info->name, "%s", (g_clkSvcUsePcv ? "pcv" : "clkrst"));
info->cond_func = NULL;
2020-04-15 21:50:07 +01:00
info->init_func = (g_clkSvcUsePcv ? &pcvInitialize : &servicesClkrstInitialize);
info->close_func = (g_clkSvcUsePcv ? &pcvExit : &servicesClkrstExit);
2020-04-15 21:50:07 +01:00
return true;
}