From 6b8b3184ac514caa99d8b5db18d209dc8ff97042 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sat, 15 Aug 2020 17:22:49 -0400 Subject: [PATCH] Fixes for development units + diff patch for ns-usbloader. Big thanks to ZachyCatGames. --- nsul_ndxt_patch.diff | 213 +++++++++++++++++++++++++++++++++++++++++++ source/services.c | 90 +++++++++++++----- source/services.h | 5 + source/utils.c | 96 +++++++++++++++---- source/utils.h | 2 + 5 files changed, 362 insertions(+), 44 deletions(-) create mode 100644 nsul_ndxt_patch.diff diff --git a/nsul_ndxt_patch.diff b/nsul_ndxt_patch.diff new file mode 100644 index 0000000..28df861 --- /dev/null +++ b/nsul_ndxt_patch.diff @@ -0,0 +1,213 @@ +diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java +index 054f03a..f46fed9 100644 +--- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java ++++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java +@@ -40,6 +40,8 @@ class NxdtUsbAbi1 { + private boolean isWindows; + private boolean isWindows10; + ++ private BufferedOutputStream bos; ++ + private static final int NXDT_MAX_DIRECTIVE_SIZE = 0x1000; + private static final int NXDT_FILE_CHUNK_SIZE = 0x800000; + private static final int NXDT_FILE_PROPERTIES_MAX_NAME_LENGTH = 0x300; +@@ -50,6 +52,8 @@ class NxdtUsbAbi1 { + private static final int CMD_HANDSHAKE = 0; + private static final int CMD_SEND_FILE_PROPERTIES = 1; + private static final int CMD_ENDSESSION = 3; ++ ++ private static final int NXDT_USB_TIMEOUT = 5000; + + // Standard set of possible replies + private static final byte[] USBSTATUS_SUCCESS = { 0x4e, 0x58, 0x44, 0x54, +@@ -186,6 +190,8 @@ class NxdtUsbAbi1 { + return; + } + ++ logPrinter.print("Receiving: '"+filename+"' ("+fileSize+" b)", EMsgType.INFO); ++ + // If RomFs related + if (isRomFs(filename)) { + if (isWindows) +@@ -196,7 +202,6 @@ class NxdtUsbAbi1 { + createPath(filename); + } + else { +- logPrinter.print("Receiving: '"+filename+"' ("+fileSize+" b)", EMsgType.INFO); + filename = saveToPath + filename; + } + +@@ -220,13 +225,11 @@ class NxdtUsbAbi1 { + if (fileSize == 0) + return; + +- if (isWindows10) +- dumpFileOnWindowsTen(fileToDump, fileSize); +- else +- dumpFile(fileToDump, fileSize); ++ dumpFile(fileToDump, fileSize); + + writeUsb(USBSTATUS_SUCCESS); + ++ logPrinter.print("Transfer finished!", EMsgType.INFO); + } + + private int getLEint(byte[] bytes, int fromOffset){ +@@ -255,45 +258,43 @@ class NxdtUsbAbi1 { + throw new Exception("Unable to create dir(s) for file in "+folderForTheFile); + } + +- private void dumpFile(File file, long size) throws Exception{ +- BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file, false)); +- +- byte[] readBuffer; +- long received = 0; +- int bufferSize; +- +- while (received < size){ +- readBuffer = readUsbFile(); +- bos.write(readBuffer); +- bufferSize = readBuffer.length; +- received += bufferSize; +- logPrinter.updateProgress((received + bufferSize) / (size / 100.0) / 100.0); +- } +- logPrinter.updateProgress(1.0); +- bos.close(); ++ private boolean isAligned(long size, long alignment){ ++ return ((size & (alignment - 1)) == 0); + } + + // @see https://bugs.openjdk.java.net/browse/JDK-8146538 +- private void dumpFileOnWindowsTen(File file, long size) throws Exception{ +- FileOutputStream fos = new FileOutputStream(file, true); +- BufferedOutputStream bos = new BufferedOutputStream(fos); ++ private void dumpFile(File file, long size) throws Exception{ ++ FileOutputStream fos = new FileOutputStream(file, false); + FileDescriptor fd = fos.getFD(); ++ bos = new BufferedOutputStream(fos); + + byte[] readBuffer; + long received = 0; +- int bufferSize; ++ int chunk_size = NXDT_FILE_CHUNK_SIZE; ++ boolean zlt_expected = (isAligned(size, 0x40) || isAligned(size, 0x200) || isAligned(size, 0x400)); // wMaxPacketSize ++ //logPrinter.print("ZLT packet expected: "+zlt_expected, EMsgType.INFO); ++ ++ while(true){ ++ if (!zlt_expected && received >= size) break; ++ ++ if (received < size && (long)chunk_size > (size - received)) chunk_size = (int)(size - received); ++ ++ readBuffer = readUsbFile(chunk_size); ++ if (readBuffer == null || readBuffer.length == 0) ++ { ++ //logPrinter.print("ZLT packet received.", EMsgType.INFO); ++ if (zlt_expected && received >= size) break; ++ continue; ++ } + +- while (received < size){ +- readBuffer = readUsbFile(); + bos.write(readBuffer); +- fd.sync(); // Fixes flushing under Windows (unharmful for other OS) +- bufferSize = readBuffer.length; +- received += bufferSize; ++ if (isWindows10) fd.sync(); // Fixes flushing under Windows 10 ++ received += readBuffer.length; + +- logPrinter.updateProgress((received + bufferSize) / (size / 100.0) / 100.0); ++ //logPrinter.print("Received "+readBuffer.length+" b. Got thus far: "+received+" b.", EMsgType.INFO); ++ logPrinter.updateProgress((double)received / (double)size); + } + +- logPrinter.updateProgress(1.0); + bos.close(); + } + +@@ -310,7 +311,7 @@ class NxdtUsbAbi1 { + if ( parent.isCancelled() ) + throw new InterruptedException("Execution interrupted"); + +- int result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, 5050); ++ int result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, NXDT_USB_TIMEOUT); + + switch (result){ + case LibUsb.SUCCESS: +@@ -324,7 +325,6 @@ class NxdtUsbAbi1 { + "\n Returned: "+ UsbErrorCodes.getErrCode(result) + + "\n (execution stopped)"); + } +- + } + /** + * Reading what USB device responded (command). +@@ -360,29 +360,29 @@ class NxdtUsbAbi1 { + * @return byte array if data read successful + * 'null' if read failed + * */ +- private byte[] readUsbFile() throws Exception{ +- ByteBuffer readBuffer = ByteBuffer.allocateDirect(NXDT_FILE_CHUNK_SIZE); ++ private byte[] readUsbFile(int chunk_size) throws Exception{ ++ ByteBuffer readBuffer = ByteBuffer.allocateDirect(chunk_size); + IntBuffer readBufTransferred = IntBuffer.allocate(1); +- int result; +- int countDown = 0; +- while (! parent.isCancelled() && countDown < 5) { +- result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); + +- switch (result) { +- case LibUsb.SUCCESS: +- int trans = readBufTransferred.get(); +- byte[] receivedBytes = new byte[trans]; +- readBuffer.get(receivedBytes); +- return receivedBytes; +- case LibUsb.ERROR_TIMEOUT: +- countDown++; +- break; +- default: +- throw new Exception("Data transfer issue [read file]" + +- "\n Returned: " + UsbErrorCodes.getErrCode(result)+ +- "\n (execution stopped)"); +- } ++ if ( parent.isCancelled() ) ++ { ++ bos.close(); ++ throw new InterruptedException(); ++ } ++ ++ int result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, NXDT_USB_TIMEOUT); ++ ++ switch (result) { ++ case LibUsb.SUCCESS: ++ int trans = readBufTransferred.get(); ++ byte[] receivedBytes = new byte[trans]; ++ readBuffer.get(receivedBytes); ++ return receivedBytes; ++ default: ++ bos.close(); ++ throw new Exception("Data transfer issue [read file]" + ++ "\n Returned: " + UsbErrorCodes.getErrCode(result)+ ++ "\n (execution stopped)"); + } +- throw new InterruptedException(); + } + } +diff --git a/src/main/resources/NSLMain.fxml b/src/main/resources/NSLMain.fxml +index a2d42d6..9114c3d 100644 +--- a/src/main/resources/NSLMain.fxml ++++ b/src/main/resources/NSLMain.fxml +@@ -71,12 +71,12 @@ Steps to roll NXDT functionality back: + + + +- ++ + + + + +- ++ + + + diff --git a/source/services.c b/source/services.c index e02af97..86881ab 100644 --- a/source/services.c +++ b/source/services.c @@ -25,20 +25,22 @@ /* 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. */ +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. */ -typedef struct ServicesInfoEntry { +typedef struct { bool initialized; char name[8]; ServiceCondFunction cond_func; ServiceInitFunction init_func; ServiceCloseFunction close_func; -} ServicesInfoEntry; +} ServiceInfo; /* Function prototypes. */ +static Result smHasService(bool *out_has_service, SmServiceName name); + static Result servicesPlUserInitialize(void); static Result servicesNifmUserInitialize(void); static bool servicesClkGetServiceType(void *arg); @@ -47,18 +49,18 @@ static bool servicesFspUsbCheckAvailability(void *arg); /* Global variables. */ -static ServicesInfoEntry g_serviceInfo[] = { +static ServiceInfo g_serviceInfo[] = { { false, "ncm", NULL, &ncmInitialize, &ncmExit }, { false, "ns", NULL, &nsInitialize, &nsExit }, { false, "csrng", NULL, &csrngInitialize, &csrngExit }, - { false, "spl", NULL, &splInitialize, &splExit }, + { false, "spl:", NULL, &splInitialize, &splExit }, { false, "spl:mig", &servicesSplCryptoCheckAvailability, &splCryptoInitialize, &splCryptoExit }, /* Checks if spl:mig is really available (e.g. avoid calling splInitialize twice). */ { false, "pm:dmnt", NULL, &pmdmntInitialize, &pmdmntExit }, { false, "pl:u", NULL, &servicesPlUserInitialize, &plExit }, { false, "psm", NULL, &psmInitialize, &psmExit }, { false, "nifm:u", NULL, &servicesNifmUserInitialize, &nifmExit }, { false, "clk", &servicesClkGetServiceType, NULL, NULL }, /* Placeholder for pcv / clkrst. */ - { false, "fsp-usb", &servicesFspUsbCheckAvailability, &fspusbInitialize, &fspusbExit }, /* Checks if fsp-usb is really available. */ + { false, "fsp-usb", &servicesFspUsbCheckAvailability, &fspusbInitialize, &fspusbExit }, /* Checks if fsp-usb really is available. */ { false, "es", NULL, &esInitialize, &esExit }, { false, "set:cal", NULL, &setcalInitialize, &setcalExit } }; @@ -79,28 +81,30 @@ bool servicesInitialize(void) for(u32 i = 0; i < g_serviceInfoCount; i++) { + ServiceInfo *service_info = &(g_serviceInfo[i]); + /* Check if this service has been already initialized or if it actually has a valid initialize function. */ - if (g_serviceInfo[i].initialized || g_serviceInfo[i].init_func == NULL) continue; + if (service_info->initialized || service_info->init_func == NULL) continue; /* Check if this service depends on a condition function. */ - if (g_serviceInfo[i].cond_func != NULL) + 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 (!g_serviceInfo[i].cond_func(&(g_serviceInfo[i]))) continue; + if (!service_info->cond_func(service_info)) continue; } /* Initialize service. */ - rc = g_serviceInfo[i].init_func(); + rc = service_info->init_func(); if (R_FAILED(rc)) { - LOGFILE("Failed to initialize %s service! (0x%08X).", g_serviceInfo[i].name, rc); + LOGFILE("Failed to initialize %s service! (0x%08X).", service_info->name, rc); ret = false; break; } /* Update initialized flag. */ - g_serviceInfo[i].initialized = true; + service_info->initialized = true; } mutexUnlock(&g_servicesMutex); @@ -114,11 +118,16 @@ void servicesClose(void) 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 (!g_serviceInfo[i].initialized || g_serviceInfo[i].close_func == NULL) continue; + if (!service_info->initialized || service_info->close_func == NULL) continue; /* Close service. */ - g_serviceInfo[i].close_func(); + service_info->close_func(); + + /* Update initialized flag. */ + service_info->initialized = false; } mutexUnlock(&g_servicesMutex); @@ -128,13 +137,16 @@ bool servicesCheckRunningServiceByName(const char *name) { if (!name || !strlen(name)) return false; + Result rc = 0; Handle handle = INVALID_HANDLE; SmServiceName service_name = smEncodeName(name); - Result rc = smRegisterService(&handle, service_name, false, 1); - bool running = R_FAILED(rc); + bool running = false; + + rc = smRegisterService(&handle, service_name, false, 1); + if (R_FAILED(rc)) LOGFILE("smRegisterService failed for \"%s\"! (0x%08X).", name, rc); + running = R_FAILED(rc); if (handle != INVALID_HANDLE) svcCloseHandle(handle); - if (!running) smUnregisterService(service_name); return running; @@ -152,9 +164,11 @@ bool servicesCheckInitializedServiceByName(const char *name) for(u32 i = 0; i < g_serviceInfoCount; i++) { - if (strlen(g_serviceInfo[i].name) == name_len && !strcmp(g_serviceInfo[i].name, name)) + ServiceInfo *service_info = &(g_serviceInfo[i]); + + if (strlen(service_info->name) == name_len && !strcmp(service_info->name, name)) { - ret = g_serviceInfo[i].initialized; + ret = service_info->initialized; break; } } @@ -181,6 +195,30 @@ void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate) mutexUnlock(&g_servicesMutex); } +Result servicesAtmosphereHasService(bool *out_has_service, const char *name) +{ + if (!out_has_service || !name || !strlen(name)) + { + LOGFILE("Invalid parameters!"); + return MAKERESULT(Module_Libnx, LibnxError_IoError); + } + + SmServiceName service_name = smEncodeName(name); + + Result rc = smHasService(out_has_service, service_name); + if (R_FAILED(rc)) LOGFILE("smHasService failed for \"%s\"! (0x%08X).", name, rc); + + return rc; +} + +static Result smHasService(bool *out_has_service, SmServiceName name) +{ + u8 tmp = 0; + Result rc = serviceDispatchInOut(smGetServiceSession(), 65100, name, tmp); + if (R_SUCCEEDED(rc) && out_has_service) *out_has_service = (tmp & 1); + return rc; +} + static Result servicesPlUserInitialize(void) { return plInitialize(PlServiceType_User); @@ -234,7 +272,7 @@ static bool servicesClkGetServiceType(void *arg) { if (!arg) return false; - ServicesInfoEntry *info = (ServicesInfoEntry*)arg; + ServiceInfo *info = (ServiceInfo*)arg; if (strlen(info->name) != 3 || 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. */ @@ -253,7 +291,7 @@ static bool servicesSplCryptoCheckAvailability(void *arg) { if (!arg) return false; - ServicesInfoEntry *info = (ServicesInfoEntry*)arg; + ServiceInfo *info = (ServiceInfo*)arg; if (strlen(info->name) != 7 || strcmp(info->name, "spl:mig") != 0 || info->init_func == NULL || info->close_func == NULL) return false; /* Check if spl:mig is available (sysver equal to or greater than 4.0.0). */ @@ -264,9 +302,11 @@ static bool servicesFspUsbCheckAvailability(void *arg) { if (!arg) return false; - ServicesInfoEntry *info = (ServicesInfoEntry*)arg; + ServiceInfo *info = (ServiceInfo*)arg; if (strlen(info->name) != 7 || strcmp(info->name, "fsp-usb") != 0 || info->init_func == NULL || info->close_func == NULL) return false; /* Check if fsp-usb is actually running in the background. */ - return servicesCheckRunningServiceByName("fsp-usb"); + bool has_service = false; + return (utilsGetCustomFirmwareType() == UtilsCustomFirmwareType_Atmosphere ? (R_SUCCEEDED(servicesAtmosphereHasService(&has_service, info->name)) && has_service) : \ + servicesCheckRunningServiceByName(info->name)); } diff --git a/source/services.h b/source/services.h index e834d16..9f93e5b 100644 --- a/source/services.h +++ b/source/services.h @@ -36,6 +36,7 @@ bool servicesInitialize(); void servicesClose(); /// Checks if a service is running by its name. +/// Uses the smRegisterService() call, which may crash under development units. bool servicesCheckRunningServiceByName(const char *name); /// Check if a service has been initialized by its name. @@ -44,4 +45,8 @@ bool servicesCheckInitializedServiceByName(const char *name); /// Changes CPU/MEM clock rates at runtime. void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate); +/// Wrapper for the Atmosphere-only SM API "HasService" extension. +/// Perfectly safe under development units. Not available in older Atmosphere releases. +Result servicesAtmosphereHasService(bool *out_has_service, const char *name); + #endif /* __SERVICES_H__ */ diff --git a/source/utils.c b/source/utils.c index 3b925fa..63b46a5 100644 --- a/source/utils.c +++ b/source/utils.c @@ -34,7 +34,7 @@ /* Global variables. */ -static bool g_resourcesInitialized = false; +static bool g_resourcesInitialized = false, g_isDevUnit = false; static Mutex g_resourcesMutex = 0; static FsFileSystem *g_sdCardFileSystem = NULL; @@ -57,13 +57,15 @@ static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes); /* Function prototypes. */ +static bool _utilsGetCustomFirmwareType(void); + +static bool _utilsIsDevelopmentUnit(void); + static bool utilsMountEmmcBisSystemPartitionStorage(void); static void utilsUnmountEmmcBisSystemPartitionStorage(void); static bool utilsGetDeviceFileSystemAndFilePathFromAbsolutePath(const char *path, FsFileSystem **out_fs, char **out_filepath); -static void _utilsGetCustomFirmwareType(void); - static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param); bool utilsInitializeResources(void) @@ -73,12 +75,8 @@ bool utilsInitializeResources(void) bool ret = g_resourcesInitialized; if (ret) goto end; - /* Retrieve pointer to the SD card FsFileSystem element. */ - if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem("sdmc:"))) - { - LOGFILE("Failed to retrieve FsFileSystem from SD card!"); - goto end; - } + /* Retrieve custom firmware type. */ + if (!_utilsGetCustomFirmwareType()) goto end; /* Initialize needed services. */ if (!servicesInitialize()) @@ -87,6 +85,10 @@ bool utilsInitializeResources(void) goto end; } + /* Check if we're not running under a development unit. */ + /* USB comms make development units crash. */ + if (!_utilsIsDevelopmentUnit()) goto end; + /* Initialize USB interface. */ if (!usbInitialize()) { @@ -122,6 +124,13 @@ bool utilsInitializeResources(void) goto end; } + /* Retrieve pointer to the SD card FsFileSystem element. */ + if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem("sdmc:"))) + { + LOGFILE("Failed to retrieve FsFileSystem from SD card!"); + goto end; + } + /* Mount eMMC BIS System partition. */ if (!utilsMountEmmcBisSystemPartitionStorage()) goto end; @@ -131,9 +140,6 @@ bool utilsInitializeResources(void) /* Disable screen dimming and auto sleep. */ appletSetMediaPlaybackState(true); - /* Retrieve custom firmware type. */ - _utilsGetCustomFirmwareType(); - /* Overclock system. */ utilsOverclockSystem(true); @@ -199,6 +205,14 @@ void utilsCloseResources(void) mutexUnlock(&g_resourcesMutex); } +bool utilsIsDevelopmentUnit(void) +{ + mutexLock(&g_resourcesMutex); + bool ret = (g_resourcesInitialized && g_isDevUnit); + mutexUnlock(&g_resourcesMutex); + return ret; +} + u64 utilsHidKeysAllDown(void) { u64 keys_down = 0; @@ -526,6 +540,57 @@ void utilsOverclockSystem(bool overclock) servicesChangeHardwareClockRates(cpuClkRate, memClkRate); } +static bool _utilsGetCustomFirmwareType(void) +{ + bool has_service = false, tx_srv = false, rnx_srv = false; + + /* First, check if we're running under Atmosphere or an Atmosphere-based CFW by using a SM API extension that's only provided by it. */ + if (R_SUCCEEDED(servicesAtmosphereHasService(&has_service, "ncm"))) + { + /* We're running under Atmosphere or an Atmosphere-based CFW. Time to check which one is it. */ + tx_srv = (R_SUCCEEDED(servicesAtmosphereHasService(&has_service, "tx")) && has_service); + rnx_srv = (R_SUCCEEDED(servicesAtmosphereHasService(&has_service, "rnx")) && has_service); + } else { + /* Odds are we're not running under Atmosphere, or maybe we're running under an old Atmosphere version without SM API extensions. */ + /* We'll use the smRegisterService() trick to check for running services. */ + /* But first, we need to re-initialize SM in order to avoid 0xF601 (port remote dead) errors. */ + smExit(); + + Result rc = smInitialize(); + if (R_FAILED(rc)) + { + LOGFILE("smInitialize failed! (0x%08X).", rc); + return false; + } + + tx_srv = servicesCheckRunningServiceByName("tx"); + rnx_srv = servicesCheckRunningServiceByName("rnx"); + } + + /* Finally, determine the CFW type. */ + g_customFirmwareType = (rnx_srv ? UtilsCustomFirmwareType_ReiNX : (tx_srv ? UtilsCustomFirmwareType_SXOS : UtilsCustomFirmwareType_Atmosphere)); + LOGFILE("Detected %s CFW.", (g_customFirmwareType == UtilsCustomFirmwareType_Atmosphere ? "Atmosphere" : (g_customFirmwareType == UtilsCustomFirmwareType_SXOS ? "SX OS" : "ReiNX"))); + + return true; +} + +static bool _utilsIsDevelopmentUnit(void) +{ + Result rc = 0; + bool tmp = false; + + rc = splIsDevelopment(&tmp); + if (R_SUCCEEDED(rc)) + { + g_isDevUnit = tmp; + LOGFILE("Running under %s unit.", g_isDevUnit ? "development" : "retail"); + } else { + LOGFILE("splIsDevelopment failed! (0x%08X).", rc); + } + + return R_SUCCEEDED(rc); +} + static bool utilsMountEmmcBisSystemPartitionStorage(void) { Result rc = 0; @@ -590,13 +655,6 @@ static bool utilsGetDeviceFileSystemAndFilePathFromAbsolutePath(const char *path return true; } -static void _utilsGetCustomFirmwareType(void) -{ - bool tx_srv = servicesCheckRunningServiceByName("tx"); - bool rnx_srv = servicesCheckRunningServiceByName("rnx"); - g_customFirmwareType = (rnx_srv ? UtilsCustomFirmwareType_ReiNX : (tx_srv ? UtilsCustomFirmwareType_SXOS : UtilsCustomFirmwareType_Atmosphere)); -} - static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param) { (void)param; diff --git a/source/utils.h b/source/utils.h index 5a5e5c9..779d19a 100644 --- a/source/utils.h +++ b/source/utils.h @@ -76,6 +76,8 @@ typedef enum { bool utilsInitializeResources(void); void utilsCloseResources(void); +bool utilsIsDevelopmentUnit(void); + /// hidScanInput() must be called before any of these functions. u64 utilsHidKeysAllDown(void); u64 utilsHidKeysAllHeld(void);