diff --git a/arm9/data/config_template.ini b/arm9/data/config_template.ini index daab454..5c09c02 100644 Binary files a/arm9/data/config_template.ini and b/arm9/data/config_template.ini differ diff --git a/arm9/source/config.c b/arm9/source/config.c index c3368e4..c6a56e5 100644 --- a/arm9/source/config.c +++ b/arm9/source/config.c @@ -24,6 +24,8 @@ * reasonable ways as different from the original version. */ +#define _GNU_SOURCE // for strchrnul + #include #include #include "config.h" @@ -42,6 +44,9 @@ #define MAKE_LUMA_VERSION_MCU(major, minor, build) (u16)(((major) & 0xFF) << 8 | ((minor) & 0x1F) << 5 | ((build) & 7)) +#define FLOAT_CONV_MULT 100000000ll +#define FLOAT_CONV_PRECISION 8u + CfgData configData; ConfigurationStatus needConfig; static CfgData oldConfig; @@ -57,8 +62,10 @@ static const char *singleOptionIniNamesBoot[] = { "use_emunand_firm_if_r_pressed", "enable_external_firm_and_modules", "enable_game_patching", + "app_syscore_threads_on_core_2", "show_system_settings_string", "show_gba_boot_screen", + "force_headphone_output", }; static const char *singleOptionIniNamesMisc[] = { @@ -95,10 +102,9 @@ static int parseBoolOption(bool *out, const char *val) } } -static int parseDecIntOption(s64 *out, const char *val, s64 minval, s64 maxval) +static int parseDecIntOptionImpl(s64 *out, const char *val, size_t numDigits, s64 minval, s64 maxval) { *out = 0; - size_t numDigits = strlen(val); s64 res = 0; size_t i = 0; @@ -130,6 +136,61 @@ static int parseDecIntOption(s64 *out, const char *val, s64 minval, s64 maxval) } } +static int parseDecIntOption(s64 *out, const char *val, s64 minval, s64 maxval) +{ + return parseDecIntOptionImpl(out, val, strlen(val), minval, maxval); +} + +static int parseDecFloatOption(s64 *out, const char *val, s64 minval, s64 maxval) +{ + char *point = strchrnul(val, '.'); + if (point == val) { + return -1; + } + + // Parse integer part, then fractional part + s64 intPart = 0; + s64 fracPart = 0; + int rc = parseDecIntOptionImpl(&intPart, val, point - val, INT64_MIN, INT64_MAX); + + if (rc != 0) { + return -1; + } + + s64 sign = intPart < 0 ? -1 : 1; + s64 intPartAbs = sign == -1 ? -intPart : intPart; + s64 res = 0; + bool of = __builtin_mul_overflow(intPartAbs, FLOAT_CONV_MULT, &res); + + if (of) { + return -1; + } + + s64 mul = FLOAT_CONV_MULT / 10; + + // Check if there's a fractional part + if (point[0] != '\0' && point[1] != '\0') { + for (char *pos = point + 1; *pos != '\0' && mul > 0; pos++) { + if (*pos < '0' || *pos > '9') { + return -1; + } + + res += (*pos - '0') * mul; + mul /= 10; + } + } + + + res = sign * (res + fracPart); + + if (res <= maxval && res >= minval && !of) { + *out = res; + return 0; + } else { + return -1; + } +} + static int parseHexIntOption(u64 *out, const char *val, u64 minval, u64 maxval) { *out = 0; @@ -222,6 +283,34 @@ static void menuComboToString(char *out, u32 combo) out[-1] = 0; } +static int encodedFloatToString(char *out, s64 val) +{ + s64 sign = val >= 0 ? 1 : -1; + + s64 intPart = (sign * val) / FLOAT_CONV_MULT; + s64 fracPart = (sign * val) % FLOAT_CONV_MULT; + + while (fracPart % 10 != 0) { + // Remove trailing zeroes + fracPart /= 10; + } + + int n = sprintf(out, "%lld", sign * intPart); + if (fracPart != 0) { + n += sprintf(out + n, ".%0*lld", (int)FLOAT_CONV_PRECISION, fracPart); + + // Remove trailing zeroes + int n2 = n - 1; + while (out[n2] == '0') { + out[n2--] = '\0'; + } + + n = n2; + } + + return n; +} + static bool hasIniParseError = false; static int iniParseErrorLine = 0; @@ -344,18 +433,66 @@ static int configIniHandler(void* user, const char* section, const char* name, c CHECK_PARSE_OPTION(parseBoolOption(&opt, value)); cfg->pluginLoaderFlags = opt ? cfg->pluginLoaderFlags | 1 : cfg->pluginLoaderFlags & ~1; return 1; - } else if (strcmp(name, "screen_filters_cct") == 0) { - s64 opt; - CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 1000, 25100)); - cfg->screenFiltersCct = (u32)opt; - return 1; } else if (strcmp(name, "ntp_tz_offset_min") == 0) { s64 opt; CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, -779, 899)); cfg->ntpTzOffetMinutes = (s16)opt; return 1; + } else { + CHECK_PARSE_OPTION(-1); } - else { + } else if (strcmp(section, "screen_filters") == 0) { + if (strcmp(name, "screen_filters_top_cct") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 1000, 25100)); + cfg->topScreenFilter.cct = (u32)opt; + return 1; + } else if (strcmp(name, "screen_filters_top_gamma") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecFloatOption(&opt, value, 0, 1411 * FLOAT_CONV_MULT)); + cfg->topScreenFilter.gammaEnc = opt; + return 1; + } else if (strcmp(name, "screen_filters_top_contrast") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecFloatOption(&opt, value, 0, 255 * FLOAT_CONV_MULT)); + cfg->topScreenFilter.contrastEnc = opt; + return 1; + } else if (strcmp(name, "screen_filters_top_brightness") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecFloatOption(&opt, value, -1 * FLOAT_CONV_MULT, 1 * FLOAT_CONV_MULT)); + cfg->topScreenFilter.brightnessEnc = opt; + return 1; + } else if (strcmp(name, "screen_filters_top_invert") == 0) { + bool opt; + CHECK_PARSE_OPTION(parseBoolOption(&opt, value)); + cfg->topScreenFilter.invert = opt; + return 1; + } else if (strcmp(name, "screen_filters_bot_cct") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 1000, 25100)); + cfg->bottomScreenFilter.cct = (u32)opt; + return 1; + } else if (strcmp(name, "screen_filters_bot_gamma") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecFloatOption(&opt, value, 0, 1411 * FLOAT_CONV_MULT)); + cfg->bottomScreenFilter.gammaEnc = opt; + return 1; + } else if (strcmp(name, "screen_filters_bot_contrast") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecFloatOption(&opt, value, 0, 255 * FLOAT_CONV_MULT)); + cfg->bottomScreenFilter.contrastEnc = opt; + return 1; + } else if (strcmp(name, "screen_filters_bot_brightness") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecFloatOption(&opt, value, -1 * FLOAT_CONV_MULT, 1 * FLOAT_CONV_MULT)); + cfg->bottomScreenFilter.brightnessEnc = opt; + return 1; + } else if (strcmp(name, "screen_filters_bot_invert") == 0) { + bool opt; + CHECK_PARSE_OPTION(parseBoolOption(&opt, value)); + cfg->bottomScreenFilter.invert = opt; + return 1; + } else { CHECK_PARSE_OPTION(-1); } } else if (strcmp(section, "autoboot") == 0) { @@ -428,6 +565,20 @@ static size_t saveLumaIniConfigToStr(char *out) static const int pinOptionToDigits[] = { 0, 4, 6, 8 }; int pinNumDigits = pinOptionToDigits[MULTICONFIG(PIN)]; + char topScreenFilterGammaStr[32]; + char topScreenFilterContrastStr[32]; + char topScreenFilterBrightnessStr[32]; + encodedFloatToString(topScreenFilterGammaStr, cfg->topScreenFilter.gammaEnc); + encodedFloatToString(topScreenFilterContrastStr, cfg->topScreenFilter.contrastEnc); + encodedFloatToString(topScreenFilterBrightnessStr, cfg->topScreenFilter.brightnessEnc); + + char bottomScreenFilterGammaStr[32]; + char bottomScreenFilterContrastStr[32]; + char bottomScreenFilterBrightnessStr[32]; + encodedFloatToString(bottomScreenFilterGammaStr, cfg->bottomScreenFilter.gammaEnc); + encodedFloatToString(bottomScreenFilterContrastStr, cfg->bottomScreenFilter.contrastEnc); + encodedFloatToString(bottomScreenFilterBrightnessStr, cfg->bottomScreenFilter.brightnessEnc); + int n = sprintf( out, (const char *)config_template_ini, lumaVerStr, lumaRevSuffixStr, @@ -435,14 +586,21 @@ static size_t saveLumaIniConfigToStr(char *out) (int)CONFIG_VERSIONMAJOR, (int)CONFIG_VERSIONMINOR, (int)CONFIG(AUTOBOOTEMU), (int)CONFIG(USEEMUFIRM), (int)CONFIG(LOADEXTFIRMSANDMODULES), (int)CONFIG(PATCHGAMES), - (int)CONFIG(PATCHVERSTRING), (int)CONFIG(SHOWGBABOOT), + (int)CONFIG(REDIRECTAPPTHREADS), (int)CONFIG(PATCHVERSTRING), + (int)CONFIG(SHOWGBABOOT), (int)CONFIG(FORCEHEADPHONEOUTPUT), 1 + (int)MULTICONFIG(DEFAULTEMU), 4 - (int)MULTICONFIG(BRIGHTNESS), splashPosStr, (unsigned int)cfg->splashDurationMsec, pinNumDigits, n3dsCpuStr, (int)MULTICONFIG(AUTOBOOTMODE), cfg->hbldr3dsxTitleId, rosalinaMenuComboStr, (int)(cfg->pluginLoaderFlags & 1), - (int)cfg->screenFiltersCct, (int)cfg->ntpTzOffetMinutes, + (int)cfg->ntpTzOffetMinutes, + + (int)cfg->topScreenFilter.cct, (int)cfg->bottomScreenFilter.cct, + topScreenFilterGammaStr, bottomScreenFilterGammaStr, + topScreenFilterContrastStr, bottomScreenFilterContrastStr, + topScreenFilterBrightnessStr, bottomScreenFilterBrightnessStr, + (int)cfg->topScreenFilter.invert, (int)cfg->bottomScreenFilter.invert, cfg->autobootTwlTitleId, (int)cfg->autobootCtrAppmemtype, @@ -551,7 +709,10 @@ bool readConfig(void) configData.splashDurationMsec = 3000; configData.hbldr3dsxTitleId = HBLDR_DEFAULT_3DSX_TID; configData.rosalinaMenuCombo = 1u << 9 | 1u << 7 | 1u << 2; // L+Start+Select - configData.screenFiltersCct = 6500; // default temp, no-op + configData.topScreenFilter.cct = 6500; // default temp, no-op + configData.topScreenFilter.gammaEnc = 1 * FLOAT_CONV_MULT; // 1.0f + configData.topScreenFilter.contrastEnc = 1 * FLOAT_CONV_MULT; // 1.0f + configData.bottomScreenFilter = configData.topScreenFilter; configData.autobootTwlTitleId = AUTOBOOT_DEFAULT_TWL_TID; ret = false; } @@ -601,8 +762,10 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) "( ) Use EmuNAND FIRM if booting with R", "( ) Enable loading external FIRMs and modules", "( ) Enable game patching", + "( ) Redirect app. syscore threads to core2", "( ) Show NAND or user string in System Settings", "( ) Show GBA boot screen in patched AGB_FIRM", + "( ) Force routing audio output to headphones" }; static const char *optionsDescription[] = { "Select the default EmuNAND.\n\n" @@ -674,10 +837,19 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) "of patched code binaries, exHeaders,\n" "IPS code patches and LayeredFS\n" "for specific games.\n\n" - "Also makes certain DLCs\n" - "for out-of-region games work.\n\n" + "Also makes certain DLCs for out-of-\n" + "region games work.\n\n" "Refer to the wiki for instructions.", + "Redirect app. threads that would spawn\n" + "on core1, to core2 (which is an extra\n" + "CPU core for applications that usually\n" + "remains unused).\n\n" + "This improves the performance of very\n" + "demanding games (like Pok\x82mon US/UM)\n" // CP437 + "by about 10%. Can break some games\n" + "and other applications.\n", + "Enable showing the current NAND/FIRM:\n\n" "\t* Sys = SysNAND\n" "\t* Emu = EmuNAND 1\n" @@ -692,6 +864,15 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) "Enable showing the GBA boot screen\n" "when booting GBA games.", + + "Force audio output to headphones.\n\n" + "Currently only for NATIVE_FIRM.\n\n" + "Due to software limitations, this gets\n" + "undone if you actually insert then\n" + "remove HPs (just enter then exit sleep\n" + "mode if this happens).\n\n" + "Also gets bypassed for camera shutter\n" + "sound.", }; FirmwareSource nandType = FIRMWARE_SYSNAND; @@ -724,6 +905,8 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) { .visible = nandType == FIRMWARE_EMUNAND }, { .visible = true }, { .visible = true }, + { .visible = ISN3DS }, + { .visible = true }, { .visible = true }, { .visible = true }, }; diff --git a/arm9/source/config.h b/arm9/source/config.h index 23b4a04..00de4c1 100644 --- a/arm9/source/config.h +++ b/arm9/source/config.h @@ -36,7 +36,7 @@ #define CONFIG_FILE "config.bin" #define CONFIG_VERSIONMAJOR 3 -#define CONFIG_VERSIONMINOR 1 +#define CONFIG_VERSIONMINOR 5 #define BOOTCFG_NAND BOOTCONFIG(0, 7) #define BOOTCFG_FIRM BOOTCONFIG(3, 7) @@ -59,8 +59,10 @@ enum singleOptions USEEMUFIRM, LOADEXTFIRMSANDMODULES, PATCHGAMES, + REDIRECTAPPTHREADS, PATCHVERSTRING, SHOWGBABOOT, + FORCEHEADPHONEOUTPUT, PATCHUNITINFO, DISABLEARM11EXCHANDLERS, ENABLESAFEFIRMROSALINA, diff --git a/arm9/source/deliver_arg.c b/arm9/source/deliver_arg.c index df8dccf..badb0ef 100644 --- a/arm9/source/deliver_arg.c +++ b/arm9/source/deliver_arg.c @@ -28,10 +28,11 @@ #include "utils.h" #include "memory.h" #include "config.h" +#include "fs.h" u8 *loadDeliverArg(void) { - static u8 deliverArg[0x1000] = {0}; + static __attribute__((aligned(8))) u8 deliverArg[0x1000] = {0}; static bool deliverArgLoaded = false; if (!deliverArgLoaded) @@ -50,8 +51,11 @@ u8 *loadDeliverArg(void) // Validate deliver arg u32 testPattern = *(u32 *)(deliverArg + 0x438); - u32 crc = *(u32 *)(deliverArg + 0x43C); + u32 *crcPtr = (u32 *)(deliverArg + 0x43C); + u32 crc = *crcPtr; + *crcPtr = 0; // clear crc field before calculation u32 expectedCrc = crc32(deliverArg + 0x400, 0x140, 0xFFFFFFFF); + *crcPtr = crc; if (testPattern != 0xFFFF || crc != expectedCrc) memset(deliverArg, 0, 0x1000); } @@ -93,6 +97,7 @@ void commitDeliverArg(void) if (mode == 0) // CTR mode { *(u32 *)(deliverArg + 0x438) = 0xFFFF; + *(u32 *)(deliverArg + 0x43C) = 0; // clear CRC field before calculating it *(u32 *)(deliverArg + 0x43C) = crc32(deliverArg + 0x400, 0x140, 0xFFFFFFFF); memcpy((void *)0x20000000, deliverArg, 0x1000); } @@ -185,9 +190,27 @@ bool configureHomebrewAutoboot(void) u32 bootenv = CFG_BOOTENV; u32 mode = bootenv >> 1; - u32 testPattern = *(u32 *)(deliverArg + 0x438); - if (mode != 0 || testPattern == 0xFFFF) - return false; // bail out if this isn't a coldboot/plain reboot + // NS always writes a valid deliver arg on reboot, no matter what. + // Check if it is empty, and, of course, bail out if we aren't rebooting from + // NATIVE_FIRM. + // Checking if it is empty is necessary to let us reboot from autobooted hbmenu + // to hbmenu. + + if (mode != 0) + return false; + else if (bootenv == 1) + { + for (u32 i = 0; i < 0x410; i++) + { + if (deliverArg[i] != 0) + return false; + } + for (u32 i = 0x440; i < 0x1000; i++) + { + if (deliverArg[i] != 0) + return false; + } + } switch (MULTICONFIG(AUTOBOOTMODE)) { diff --git a/arm9/source/main.c b/arm9/source/main.c index 3ba0fb6..dd3ccbe 100644 --- a/arm9/source/main.c +++ b/arm9/source/main.c @@ -229,12 +229,19 @@ void main(int argc, char **argv, u32 magicWord) nandType = FIRMWARE_SYSNAND; firmSource = (BOOTCFG_NAND != 0) == (BOOTCFG_FIRM != 0) ? FIRMWARE_SYSNAND : (FirmwareSource)BOOTCFG_FIRM; - //Prevent multiple boot options-forcing + // Prevent multiple boot options-forcing + // This bit is a bit weird. Basically, as you return to Home Menu by pressing either + // the HOME or POWER button, nandType will be overridden to "SysNAND" (needed). But, + // if you reboot again (e.g. via Rosalina menu), it'll use your default settings. if(nandType != BOOTCFG_NAND || firmSource != BOOTCFG_FIRM) isNoForceFlagSet = true; goto boot; } + // Configure homebrew autoboot (if deliver arg ends up not containing anything) + if (bootenv == 1 && MULTICONFIG(AUTOBOOTMODE) != 0) + configureHomebrewAutoboot(); + /* Force the last used boot options if doing autolaunch from TWL, or unless a button is pressed or the no-forcing flag is set */ if(validTlnc || !(pressed || BOOTCFG_NOFORCEFLAG)) diff --git a/arm9/source/patches.c b/arm9/source/patches.c index a39d925..3ac208b 100644 --- a/arm9/source/patches.c +++ b/arm9/source/patches.c @@ -133,8 +133,11 @@ u32 installK11Extension(u8 *pos, u32 size, bool needToInitSd, u32 baseK11VA, u32 u64 hbldr3dsxTitleId; u32 rosalinaMenuCombo; u32 pluginLoaderFlags; - u16 screenFiltersCct; s16 ntpTzOffetMinutes; + + ScreenFiltersCfgData topScreenFilter; + ScreenFiltersCfgData bottomScreenFilter; + u64 autobootTwlTitleId; u8 autobootCtrAppmemtype; } info; @@ -211,8 +214,9 @@ u32 installK11Extension(u8 *pos, u32 size, bool needToInitSd, u32 baseK11VA, u32 info->hbldr3dsxTitleId = configData.hbldr3dsxTitleId; info->rosalinaMenuCombo = configData.rosalinaMenuCombo; info->pluginLoaderFlags = configData.pluginLoaderFlags; - info->screenFiltersCct = configData.screenFiltersCct; info->ntpTzOffetMinutes = configData.ntpTzOffetMinutes; + info->topScreenFilter = configData.topScreenFilter; + info->bottomScreenFilter = configData.bottomScreenFilter; info->autobootTwlTitleId = configData.autobootTwlTitleId; info->autobootCtrAppmemtype = configData.autobootCtrAppmemtype; info->versionMajor = VERSION_MAJOR; diff --git a/arm9/source/types.h b/arm9/source/types.h index f6c6e08..027a080 100644 --- a/arm9/source/types.h +++ b/arm9/source/types.h @@ -61,7 +61,15 @@ typedef volatile s64 vs64; #define ISN3DS (CFG11_SOCINFO & 2) #define ISDEVUNIT (CFG_UNITINFO != 0) -typedef struct { +typedef struct ScreenFiltersCfgData { + u16 cct; + bool invert; + s64 gammaEnc; + s64 contrastEnc; + s64 brightnessEnc; +} ScreenFiltersCfgData; + +typedef struct CfgData { u16 formatVersionMajor, formatVersionMinor; u32 config, multiConfig, bootConfig; @@ -70,9 +78,11 @@ typedef struct { u64 hbldr3dsxTitleId; u32 rosalinaMenuCombo; u32 pluginLoaderFlags; - u16 screenFiltersCct; s16 ntpTzOffetMinutes; + ScreenFiltersCfgData topScreenFilter; + ScreenFiltersCfgData bottomScreenFilter; + u64 autobootTwlTitleId; u8 autobootCtrAppmemtype; } CfgData; diff --git a/k11_extension/include/config.h b/k11_extension/include/config.h index 26ee057..b367b38 100644 --- a/k11_extension/include/config.h +++ b/k11_extension/include/config.h @@ -30,8 +30,10 @@ enum singleOptions USEEMUFIRM, LOADEXTFIRMSANDMODULES, PATCHGAMES, + REDIRECTAPPTHREADS, PATCHVERSTRING, SHOWGBABOOT, + FORCEHEADPHONEOUTPUT, PATCHUNITINFO, DISABLEARM11EXCHANDLERS, ENABLESAFEFIRMROSALINA, diff --git a/k11_extension/include/globals.h b/k11_extension/include/globals.h index 093fed1..9c0d03e 100644 --- a/k11_extension/include/globals.h +++ b/k11_extension/include/globals.h @@ -63,6 +63,7 @@ extern void (*KLinkedList_KBlockInfo__Clear)(KLinkedList *list); extern Result (*ControlMemory)(u32 *addrOut, u32 addr0, u32 addr1, u32 size, MemOp op, MemPerm perm, bool isLoader); extern Result (*doControlMemory)(KProcessHwInfo *this, u32 addr, u32 requestedNbPages, u32 pa, u32 state, u32 perm, u32 a7, u32 region); +extern Result (*CreateThread)(Handle *outThreadHandle, u32 ep, u32 arg, u32 stackTop, s32 priority, s32 processorId); extern void (*SleepThread)(s64 ns); extern Result (*CreateEvent)(Handle *out, ResetType resetType); extern Result (*CloseHandle)(Handle handle); @@ -127,6 +128,14 @@ extern void (*mcuReboot)(void); extern void (*coreBarrier)(void); extern void* (*kAlloc)(FcramDescriptor *fcramDesc, u32 nbPages, u32 alignment, u32 region); +typedef struct ScreenFiltersCfgData { + u16 cct; + bool invert; + s64 gammaEnc; + s64 contrastEnc; + s64 brightnessEnc; +} ScreenFiltersCfgData; + typedef struct CfwInfo { char magic[4]; @@ -144,8 +153,11 @@ typedef struct CfwInfo u64 hbldr3dsxTitleId; u32 rosalinaMenuCombo; u32 pluginLoaderFlags; - u16 screenFiltersCct; s16 ntpTzOffetMinutes; + + ScreenFiltersCfgData topScreenFilter; + ScreenFiltersCfgData bottomScreenFilter; + u64 autobootTwlTitleId; u8 autobootCtrAppmemtype; } CfwInfo; diff --git a/k11_extension/include/kernel.h b/k11_extension/include/kernel.h index 8d88039..8b3c002 100644 --- a/k11_extension/include/kernel.h +++ b/k11_extension/include/kernel.h @@ -1247,7 +1247,7 @@ offsetof(classname##O3DSPre8x, field))) #define KPROCESSHWINFO_GET_RVALUE(obj, field) *(KPROCESSHWINFO_GET_PTR(obj, field)) #define KPROCESSHWINFO_GET_RVALUE_TYPE(type, obj, field) *(KPROCESSHWINFO_GET_PTR_TYPE(type, obj, field)) -extern u32 pidOffsetKProcess, hwInfoOffsetKProcess, codeSetOffsetKProcess, handleTableOffsetKProcess, debugOffsetKProcess; +extern u32 pidOffsetKProcess, hwInfoOffsetKProcess, codeSetOffsetKProcess, handleTableOffsetKProcess, debugOffsetKProcess, flagsKProcess; static inline u32 idOfProcess(KProcess *process) { @@ -1280,6 +1280,13 @@ static inline KDebug *debugOfProcess(KProcess *process) return debug; } +static inline u32 flagsOfProcess(KProcess *process) +{ + u32 flags; + memcpy(&flags, (const u8 *)process + flagsKProcess, 4); + return flags; +} + static inline const char *classNameOfAutoObject(KAutoObject *object) { const char *name; diff --git a/k11_extension/include/svc/CreateThread.h b/k11_extension/include/svc/CreateThread.h new file mode 100644 index 0000000..e4f7de5 --- /dev/null +++ b/k11_extension/include/svc/CreateThread.h @@ -0,0 +1,32 @@ +/* +* This file is part of Luma3DS +* Copyright (C) 2016-2023 Aurora Wright, TuxSH +* +* This program 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. +* +* This program 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. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#include "utils.h" +#include "kernel.h" +#include "svc.h" + +Result CreateThreadHookWrapper(Handle *outThreadHandle, u32 ep, u32 arg, u32 stackTop, s32 priority, s32 processorId); +Result CreateThreadHook(Handle *outThreadHandle, u32 ep, u32 arg, u32 stackTop, s32 priority, s32 processorId); diff --git a/k11_extension/source/globals.c b/k11_extension/source/globals.c index 4112518..0164962 100644 --- a/k11_extension/source/globals.c +++ b/k11_extension/source/globals.c @@ -62,6 +62,7 @@ void (*KLinkedList_KBlockInfo__Clear)(KLinkedList *list); Result (*ControlMemory)(u32 *addrOut, u32 addr0, u32 addr1, u32 size, MemOp op, MemPerm perm, bool isLoader); Result (*doControlMemory)(KProcessHwInfo *this, u32 addr, u32 requestedNbPages, u32 pa, u32 state, u32 perm, u32 a7, u32 region); +Result (*CreateThread)(Handle *outThreadHandle, u32 ep, u32 arg, u32 stackTop, s32 priority, s32 processorId); void (*SleepThread)(s64 ns); Result (*CreateEvent)(Handle *out, ResetType resetType); Result (*CloseHandle)(Handle handle); @@ -130,7 +131,7 @@ u32 stolenSystemMemRegionSize; vu32 rosalinaState; bool hasStartedRosalinaNetworkFuncsOnce; KEvent* signalPluginEvent = NULL; -u32 pidOffsetKProcess, hwInfoOffsetKProcess, codeSetOffsetKProcess, handleTableOffsetKProcess, debugOffsetKProcess; +u32 pidOffsetKProcess, hwInfoOffsetKProcess, codeSetOffsetKProcess, handleTableOffsetKProcess, debugOffsetKProcess, flagsKProcess; KLinkedList* KLinkedList__Initialize(KLinkedList *list) { @@ -180,3 +181,4 @@ u32 PLG_GetStatus(void) { return (*(vu32 *)PA_FROM_VA_PTR((u32 *)0x1FF800F0)) & 0xFFFF; } + diff --git a/k11_extension/source/main.c b/k11_extension/source/main.c index e85218b..0ff4e68 100644 --- a/k11_extension/source/main.c +++ b/k11_extension/source/main.c @@ -127,6 +127,7 @@ void configHook(vu8 *cfgPage) codeSetOffsetKProcess = KPROCESS_OFFSETOF(codeSet); handleTableOffsetKProcess = KPROCESS_OFFSETOF(handleTable); debugOffsetKProcess = KPROCESS_OFFSETOF(debug); + flagsKProcess = KPROCESS_OFFSETOF(kernelFlags); } void KProcessHwInfo__MapL1Section_Hook(void); @@ -327,6 +328,7 @@ static void findUsefulSymbols(void) // The official prototype of ControlMemory doesn't have that extra param' ControlMemory = (Result (*)(u32 *, u32, u32, u32, MemOp, MemPerm, bool)) decodeArmBranch((u32 *)officialSVCs[0x01] + 5); + CreateThread = (Result (*)(Handle *, u32, u32, u32, s32, s32))decodeArmBranch((u32 *)officialSVCs[0x08] + 5); SleepThread = (void (*)(s64))officialSVCs[0x0A]; CreateEvent = (Result (*)(Handle *, ResetType))decodeArmBranch((u32 *)officialSVCs[0x17] + 3); CloseHandle = (Result (*)(Handle))officialSVCs[0x23]; diff --git a/k11_extension/source/svc.c b/k11_extension/source/svc.c index 5c75554..4dbd9f1 100644 --- a/k11_extension/source/svc.c +++ b/k11_extension/source/svc.c @@ -28,6 +28,7 @@ #include "synchronization.h" #include "svc.h" #include "svc/ControlMemory.h" +#include "svc/CreateThread.h" #include "svc/GetHandleInfo.h" #include "svc/GetSystemInfo.h" #include "svc/GetProcessInfo.h" @@ -67,6 +68,8 @@ void buildAlteredSvcTable(void) alteredSvcTable[0x01] = ControlMemoryHookWrapper; alteredSvcTable[0x03] = ExitProcessHookWrapper; + if (isN3DS) + alteredSvcTable[0x08] = CreateThreadHookWrapper; alteredSvcTable[0x29] = GetHandleInfoHookWrapper; alteredSvcTable[0x2A] = GetSystemInfoHookWrapper; alteredSvcTable[0x2B] = GetProcessInfoHookWrapper; diff --git a/k11_extension/source/svc/CreateThread.c b/k11_extension/source/svc/CreateThread.c new file mode 100644 index 0000000..da748e9 --- /dev/null +++ b/k11_extension/source/svc/CreateThread.c @@ -0,0 +1,37 @@ +/* +* This file is part of Luma3DS +* Copyright (C) 2016-2023 Aurora Wright, TuxSH +* +* This program 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. +* +* This program 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. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#include "svc/CreateThread.h" + +Result CreateThreadHook(Handle *outThreadHandle, u32 ep, u32 arg, u32 stackTop, s32 priority, s32 processorId) +{ + u32 flags = flagsOfProcess(currentCoreContext->objectContext.currentProcess); + if (isN3DS && CONFIG(REDIRECTAPPTHREADS) && processorId == 1 && (flags & 0xF00) == 0x100) + processorId = 2; + + return CreateThread(outThreadHandle, ep, arg, stackTop, priority, processorId); +} + diff --git a/k11_extension/source/svc/GetSystemInfo.c b/k11_extension/source/svc/GetSystemInfo.c index e45d278..c1746cc 100644 --- a/k11_extension/source/svc/GetSystemInfo.c +++ b/k11_extension/source/svc/GetSystemInfo.c @@ -39,6 +39,8 @@ Result GetSystemInfoHook(s64 *out, s32 type, s32 param) { switch(param) { + // Please do not use these, except 0, 1, and 0x200 + // Other types may get removed or reordered without notice case 0: *out = SYSTEM_VERSION(cfwInfo.versionMajor, cfwInfo.versionMinor, cfwInfo.versionBuild); break; @@ -76,11 +78,38 @@ Result GetSystemInfoHook(s64 *out, s32 type, s32 param) *out = cfwInfo.rosalinaMenuCombo; break; case 0x102: - *out = cfwInfo.screenFiltersCct; + *out = cfwInfo.topScreenFilter.cct; break; case 0x103: *out = (s64)cfwInfo.ntpTzOffetMinutes; break; + case 0x104: + *out = cfwInfo.topScreenFilter.gammaEnc; + break; + case 0x105: + *out = cfwInfo.topScreenFilter.contrastEnc; + break; + case 0x106: + *out = cfwInfo.topScreenFilter.brightnessEnc; + break; + case 0x107: + *out = (s64)cfwInfo.topScreenFilter.invert; + break; + case 0x108: + *out = cfwInfo.bottomScreenFilter.cct; + break; + case 0x109: + *out = cfwInfo.bottomScreenFilter.gammaEnc; + break; + case 0x10A: + *out = cfwInfo.bottomScreenFilter.contrastEnc; + break; + case 0x10B: + *out = cfwInfo.bottomScreenFilter.brightnessEnc; + break; + case 0x10C: + *out = (s64)cfwInfo.bottomScreenFilter.invert; + break; case 0x180: *out = cfwInfo.pluginLoaderFlags; break; diff --git a/k11_extension/source/svc/wrappers.s b/k11_extension/source/svc/wrappers.s index 4eb6ff1..6d07ccd 100644 --- a/k11_extension/source/svc/wrappers.s +++ b/k11_extension/source/svc/wrappers.s @@ -93,6 +93,19 @@ ControlMemoryEx: add sp, #20 pop {pc} +.global CreateThreadHookWrapper +.type CreateThreadHookWrapper, %function +CreateThreadHookWrapper: + push {lr} + sub sp, #12 + str r4, [sp, #4] + str r0, [sp] + add r0, sp, #8 + bl CreateThreadHook + ldr r1, [sp, #8] + add sp, sp, #12 + pop {pc} + .global ControlMemoryUnsafeWrapper .type ControlMemoryUnsafeWrapper, %function ControlMemoryUnsafeWrapper: @@ -109,4 +122,4 @@ MapProcessMemoryExWrapper: str r4, [sp, #-4]! bl MapProcessMemoryEx add sp, #4 - pop {pc} + pop {pc} \ No newline at end of file diff --git a/sysmodules/loader/source/3dsx.c b/sysmodules/loader/source/3dsx.c index 663c0e1..cb82102 100644 --- a/sysmodules/loader/source/3dsx.c +++ b/sysmodules/loader/source/3dsx.c @@ -54,15 +54,6 @@ static inline u32 TranslateAddr(u32 off, _3DSX_LoadInfo* d, u32* offsets) return d->segAddrs[2] + off - offsets[1]; } -u32 IFile_Read2(IFile *file, void* buffer, u32 size, u32 offset) -{ - Result res; - u64 total = 0; - file->pos = offset; - res = IFile_Read(file, &total, buffer, size); - return R_SUCCEEDED(res) ? total : 0; -} - bool Ldr_Get3dsxSize(u32* pSize, IFile *file) { _3DSX_Header hdr; @@ -259,21 +250,21 @@ Handle Ldr_CodesetFrom3dsx(const char* name, u32* codePages, u32 baseAddr, IFile } // Create the codeset - CodeSetInfo csinfo; - memset(&csinfo, 0, sizeof(csinfo)); - memcpy(csinfo.name, name, 8); - csinfo.program_id = tid; - csinfo.text_addr = d.segAddrs[0]; - csinfo.text_size = d.segSizes[0] >> 12; - csinfo.ro_addr = d.segAddrs[1]; - csinfo.ro_size = d.segSizes[1] >> 12; - csinfo.rw_addr = d.segAddrs[2]; - csinfo.rw_size = (d.segSizes[2] >> 12) + 1; // One extra page reserved for settings/etc - csinfo.text_size_total = csinfo.text_size; - csinfo.ro_size_total = csinfo.ro_size; - csinfo.rw_size_total = csinfo.rw_size; + CodeSetHeader csh; + memset(&csh, 0, sizeof(csh)); + memcpy(csh.name, name, 8); + csh.program_id = tid; + csh.text_addr = d.segAddrs[0]; + csh.text_size = d.segSizes[0] >> 12; + csh.ro_addr = d.segAddrs[1]; + csh.ro_size = d.segSizes[1] >> 12; + csh.rw_addr = d.segAddrs[2]; + csh.rw_size = (d.segSizes[2] >> 12) + 1; // One extra page reserved for settings/etc + csh.text_size_total = csh.text_size; + csh.ro_size_total = csh.ro_size; + csh.rw_size_total = csh.rw_size; Handle hCodeset = 0; - res = svcCreateCodeSet(&hCodeset, &csinfo, d.segPtrs[0], d.segPtrs[1], d.segPtrs[2]); + res = svcCreateCodeSet(&hCodeset, &csh, (u32)d.segPtrs[0], (u32)d.segPtrs[1], (u32)d.segPtrs[2]); if (res) { Log_PrintP("svcCreateCodeSet: %08lX", res); diff --git a/sysmodules/loader/source/hbldr.c b/sysmodules/loader/source/hbldr.c index 018614b..e1c32fc 100644 --- a/sysmodules/loader/source/hbldr.c +++ b/sysmodules/loader/source/hbldr.c @@ -34,7 +34,7 @@ extern bool isN3DS; -static const char serviceList[32][8] = +static const char serviceList[34][8] = { "APT:U", "ac:u", @@ -68,67 +68,104 @@ static const char serviceList[32][8] = "soc:U", "ssl:C", "y2r:u", + + // Discarded below 9.6: + "nfc:u", + "mvd:STD", }; -static const u64 dependencyListNativeFirm[] = +// N3DS-only titles are denoted as -x +// PM (official and reimpl) doesn't actually support recursive dependency +// loading, and some sysmodules don't even list all their dependencies, so +// we're flattening the dependency map here and listing all the sysmodules in +// an optimal boot order. +static const s16 dependencyListNativeFirmUids[] = { - 0x0004013000002402LL, //ac - 0x0004013000001502LL, //am - 0x0004013000001702LL, //cfg - 0x0004013000001802LL, //codec - 0x0004013000002702LL, //csnd - 0x0004013000001A02LL, //dsp - 0x0004013000001B02LL, //gpio - 0x0004013000001C02LL, //gsp - 0x0004013000001D02LL, //hid - 0x0004013000002902LL, //http - 0x0004013000001E02LL, //i2c - 0x0004013000003302LL, //ir - 0x0004013000001F02LL, //mcu - 0x0004013000002C02LL, //nim - 0x0004013000002D02LL, //nwm - 0x0004013000002102LL, //pdn - 0x0004013000003102LL, //ps - 0x0004013000002202LL, //ptm - 0x0004013000002E02LL, //socket - 0x0004013000002302LL, //spi - 0x0004013000002F02LL, //ssl - - // Not present on SAFE_FIRM: - 0x0004013000003402LL, //boss - 0x0004013000001602LL, //camera - 0x0004013000002802LL, //dlp - 0x0004013000002002LL, //mic - 0x0004013000002B02LL, //ndm - 0x0004013000003502LL, //news - 0x0004013000003702LL, //ro + 0x1B, // gpio + 0x1E, // i2c + 0x21, // pdn + 0x23, // spi + + 0x1F, // mcu + 0x31, // ps + 0x17, // cfg + + 0x22, // ptm + 0x80, // ns + + // The modules above were all previously launched + // when launching NS. + + 0x18, // codec (implicit dep for hid) + 0x1D, // hid + 0x27, // csnd (for camera) + 0x16, // camera + -0x42, // qtm + 0x1C, // gsp + + 0x1A, // dsp + 0x20, // mic + 0x33, // ir + + 0x2D, // nwm + 0x2E, // socket + 0x2F, // ssl + 0x29, // http + 0x24, // ac + + 0x32, // friends + + 0x15, // am + + 0x26, // cecd + 0x34, // boss + 0x2C, // nim + 0x2B, // ndm + 0x35, // news + + -0x41, // mvd + -0x40, // nfc + + 0x38, // act + 0x37, // ro + 0x28, // dlp + 0x2A, // mp }; -static const u64 dependencyListSafeFirm[] = +static const s16 dependencyListSafeFirmUids[] = { - 0x0004013000002403LL, //ac - 0x0004013000001503LL, //am - 0x0004013000001703LL, //cfg - 0x0004013000001803LL, //codec - 0x0004013000002703LL, //csnd - 0x0004013000001A03LL, //dsp - 0x0004013000001B03LL, //gpio - 0x0004013000001C03LL, //gsp - 0x0004013000001D03LL, //hid - 0x0004013000002903LL, //http - 0x0004013000001E03LL, //i2c - 0x0004013000003303LL, //ir - 0x0004013000001F03LL, //mcu - 0x0004013000002C03LL, //nim - 0x0004013000002D03LL, //nwm - 0x0004013000002103LL, //pdn - 0x0004013000003103LL, //ps - 0x0004013000002203LL, //ptm - 0x0004013000002E03LL, //socket - 0x0004013000002303LL, //spi - 0x0004013000002F03LL, //ssl - - 0x0004013000003203LL, //friends (wouldn't be launched otherwise) + 0x1B, // gpio + 0x1E, // i2c + 0x21, // pdn + 0x23, // spi + + 0x1F, // mcu + 0x31, // ps + 0x17, // cfg + + 0x22, // ptm + 0x80, // ns + + // The modules above were all previously launched + // when launching NS. + + 0x18, // codec (implicit dep for hid) + 0x1D, // hid + 0x27, // csnd + 0x1C, // gsp + + 0x1A, // dsp + 0x33, // ir + + 0x2D, // nwm + 0x2E, // socket + 0x2F, // ssl + 0x29, // http + 0x24, // ac + + 0x32, // friends + + 0x15, // am }; static const u32 kernelCaps[] = @@ -185,10 +222,35 @@ void hbldrPatchExHeaderInfo(ExHeader_Info *exhi) memset(&exhi->sci.dependencies, 0, sizeof(exhi->sci.dependencies)); u32 coreVer = OS_KernelConfig->kernel_syscore_ver; - if (coreVer == 2) - memcpy(exhi->sci.dependencies, dependencyListNativeFirm, sizeof(dependencyListNativeFirm)); - else if (coreVer == 3) - memcpy(exhi->sci.dependencies, dependencyListSafeFirm, sizeof(dependencyListSafeFirm)); + u32 deplistSize; + const s16 *deplist; + switch (coreVer) { + case 2: + deplist = dependencyListNativeFirmUids; + deplistSize = sizeof(dependencyListNativeFirmUids)/sizeof(dependencyListNativeFirmUids[0]); + break; + case 3: + deplist = dependencyListSafeFirmUids; + deplistSize = sizeof(dependencyListSafeFirmUids)/sizeof(dependencyListSafeFirmUids[0]); + break; + default: + panic(0); + break; + } + + for (u32 i = 0; i < deplistSize; i++) + { + static const u64 mask = 0x0004013000000000ull; + bool n3dsOnly = deplist[i] < 0; + u32 n3dsMask = n3dsOnly ? 0x20000000 : 0; + u32 uid = n3dsOnly ? -deplist[i] : deplist[i]; + + // Enable O3DS NFC on 9.3 and above + if (uid == 0x40 && GET_VERSION_MINOR(osGetKernelVersion()) >= 48) + n3dsMask = 0; + + exhi->sci.dependencies[i] = mask | n3dsMask | (uid << 8) | coreVer; + } ExHeader_Arm11SystemLocalCapabilities* localcaps0 = &exhi->aci.local_caps; @@ -214,8 +276,10 @@ void hbldrPatchExHeaderInfo(ExHeader_Info *exhi) localcaps0->storage_info.use_extended_savedata_access = true; // Whatever // We have a patched SM, so whatever... + // Only 32 services if not on 9.6+ NFIRM + u32 numServices = GET_VERSION_MINOR(osGetKernelVersion()) < 50 || coreVer != 2 ? 32 : 34; memset(localcaps0->service_access, 0, sizeof(localcaps0->service_access)); - memcpy(localcaps0->service_access, serviceList, sizeof(serviceList)); + memcpy(localcaps0->service_access, serviceList, 8 * numServices); localcaps0->reslimit_category = RESLIMIT_CATEGORY_APPLICATION; @@ -225,18 +289,6 @@ void hbldrPatchExHeaderInfo(ExHeader_Info *exhi) // Set kernel release version to the current kernel version kcaps0->descriptors[0] = 0xFC000000 | (osGetKernelVersion() >> 16); - - if (GET_VERSION_MINOR(osGetKernelVersion()) >= 50 && coreVer == 2) // 9.6+ NFIRM - { - u64 lastdep = sizeof(dependencyListNativeFirm)/8; - exhi->sci.dependencies[lastdep++] = 0x0004013000004002ULL; // nfc - strncpy((char*)&localcaps0->service_access[0x20], "nfc:u", 8); - if (isN3DS) - { - exhi->sci.dependencies[lastdep++] = 0x0004013020004102ULL; // mvd - strncpy((char*)&localcaps0->service_access[0x21], "mvd:STD", 8); - } - } } Result hbldrLoadProcess(Handle *outProcessHandle, const ExHeader_Info *exhi) diff --git a/sysmodules/loader/source/ifile.c b/sysmodules/loader/source/ifile.c index 10418f5..462d632 100644 --- a/sysmodules/loader/source/ifile.c +++ b/sysmodules/loader/source/ifile.c @@ -121,3 +121,17 @@ Result IFile_Write(IFile *file, u64 *total, const void *buffer, u32 len, u32 fla *total = cur; return res; } + +Result IFile_ReadAt(IFile *file, u64 *total, void *buffer, u32 offset, u32 len) +{ + *total = 0; + file->pos = offset; + return IFile_Read(file, total, buffer, len); +} + +u32 IFile_Read2(IFile *file, void *buffer, u32 size, u32 offset) +{ + u64 total = 0; + Result res = IFile_ReadAt(file, &total, buffer, offset, size); + return R_SUCCEEDED(res) ? (u32)total : 0; +} diff --git a/sysmodules/loader/source/ifile.h b/sysmodules/loader/source/ifile.h index cd0d35e..5a873a0 100644 --- a/sysmodules/loader/source/ifile.h +++ b/sysmodules/loader/source/ifile.h @@ -18,3 +18,6 @@ Result IFile_GetSize(IFile *file, u64 *size); Result IFile_SetSize(IFile *file, u64 size); Result IFile_Read(IFile *file, u64 *total, void *buffer, u32 len); Result IFile_Write(IFile *file, u64 *total, const void *buffer, u32 len, u32 flags); + +Result IFile_ReadAt(IFile *file, u64 *total, void *buffer, u32 offset, u32 len); +u32 IFile_Read2(IFile *file, void *buffer, u32 size, u32 offset); diff --git a/sysmodules/loader/source/loader.c b/sysmodules/loader/source/loader.c index 71e3732..2e2db7f 100644 --- a/sysmodules/loader/source/loader.c +++ b/sysmodules/loader/source/loader.c @@ -6,12 +6,18 @@ #include "util.h" #include "hbldr.h" +#define SYSMODULE_CXI_COOKIE_MASK 0xEEEE000000000000ull + extern u32 config, multiConfig, bootConfig; extern bool isN3DS, isSdMode, nextGamePatchDisabled; -static u64 g_cached_programHandle; +static u64 g_cached_programHandle; // for exheader info only static ExHeader_Info g_exheaderInfo; +static IFile g_cached_sysmoduleCxiFile; +static u64 g_cached_sysmoduleCxiCookie; +static Ncch g_cached_sysmoduleCxiNcch; + typedef struct ContentPath { u32 contentType; char fileName[8]; // for exefs @@ -99,6 +105,25 @@ static int lzss_decompress(u8 *end) return ret; } +static inline bool IsSysmoduleId(u64 tid) +{ + return (tid >> 32) == 0x00040130; +} + +static inline bool IsSysmoduleCxiCookie(u64 programHandle) +{ + return (programHandle >> 32) == (SYSMODULE_CXI_COOKIE_MASK >> 32); +} + +static void InvalidateCachedCxiFile(void) +{ + if (g_cached_sysmoduleCxiFile.handle != 0) + IFile_Close(&g_cached_sysmoduleCxiFile); + memset(&g_cached_sysmoduleCxiFile, 0, sizeof(IFile)); + + g_cached_sysmoduleCxiCookie = 0; +} + static Result allocateProgramMemoryWrapper(prog_addrs_t *mapped, const ExHeader_Info *exhi, const prog_addrs_t *vaddr) { memcpy(mapped, vaddr, sizeof(prog_addrs_t)); @@ -120,7 +145,39 @@ static Result loadCode(const ExHeader_Info *exhi, u64 programHandle, const prog_ const ExHeader_CodeSetInfo *csi = &exhi->sci.codeset_info; bool isCompressed = csi->flags.compress_exefs_code; - if(!CONFIG(PATCHGAMES) || !loadTitleCodeSection(titleId, (u8 *)mapped->text_addr, (u64)mapped->total_size << 12)) + // Load from CXI, and skip patches, if we were opened from it + if (IsSysmoduleCxiCookie(programHandle)) + { + u32 sz_ = 0; + bool ok = readSysmoduleCxiCode((u8 *)mapped->text_addr, &sz_, (u64)mapped->total_size << 12, &g_cached_sysmoduleCxiFile, &g_cached_sysmoduleCxiNcch); + size = sz_; + + if (!ok) + return (Result)-2; + + // Decompress + if (isCompressed) + lzss_decompress((u8 *)mapped->text_addr + size); + + // No need to keep the file open at this point + InvalidateCachedCxiFile(); + return 0; + } + + bool codeLoadedExternally = false; + if (CONFIG(PATCHGAMES)) + { + // Require both "load external FIRM & modules" and "patch games" for sysmodules + if (IsSysmoduleId(titleId)) + codeLoadedExternally = CONFIG(LOADEXTFIRMSANDMODULES); + else + codeLoadedExternally = true; + } + + if (codeLoadedExternally) + codeLoadedExternally = loadTitleCodeSection(titleId, (u8 *)mapped->text_addr, (u64)mapped->total_size << 12); + + if(!codeLoadedExternally) { archivePath.type = PATH_BINARY; archivePath.data = &programHandle; @@ -215,6 +272,29 @@ static inline bool IsHioId(u64 id) static Result GetProgramInfoImpl(ExHeader_Info *exheaderInfo, u64 programHandle) { Result res; + + // Load from CXI, and skip patches, if we were opened from it + if (IsSysmoduleCxiCookie(programHandle)) + { + u64 titleId = 0x0004013000000000ull | (u32)programHandle; + if (g_cached_sysmoduleCxiCookie != programHandle) + { + InvalidateCachedCxiFile(); + res = openSysmoduleCxi(&g_cached_sysmoduleCxiFile, titleId); + if (R_FAILED(res)) + return res; + g_cached_sysmoduleCxiCookie = programHandle; + } + + bool ok = readSysmoduleCxiNcchHeader(&g_cached_sysmoduleCxiNcch, &g_cached_sysmoduleCxiFile); + if (!ok) + return (Result)-2; + ok = readSysmoduleCxiExHeaderInfo(exheaderInfo, &g_cached_sysmoduleCxiNcch, &g_cached_sysmoduleCxiFile); + if (!ok) + return (Result)-2; + return 0; + } + TRY(IsHioId(programHandle) ? FSREG_GetProgramInfo(exheaderInfo, 1, programHandle) : PXIPM_GetProgramInfo(exheaderInfo, programHandle)); // Tweak 3dsx placeholder title exheaderInfo @@ -225,7 +305,20 @@ static Result GetProgramInfoImpl(ExHeader_Info *exheaderInfo, u64 programHandle) else { u64 originalTitleId = exheaderInfo->aci.local_caps.title_id; - if(CONFIG(PATCHGAMES) && loadTitleExheaderInfo(exheaderInfo->aci.local_caps.title_id, exheaderInfo)) + bool exhLoadedExternally = false; + if (CONFIG(PATCHGAMES)) + { + // Require both "load external FIRM & modules" and "patch games" for sysmodules + if (IsSysmoduleId(originalTitleId)) + exhLoadedExternally = CONFIG(LOADEXTFIRMSANDMODULES); + else + exhLoadedExternally = true; + } + + if (exhLoadedExternally) + exhLoadedExternally = loadTitleExheaderInfo(originalTitleId, exheaderInfo); + + if(exhLoadedExternally) exheaderInfo->aci.local_caps.title_id = originalTitleId; } @@ -255,11 +348,11 @@ static Result LoadProcessImpl(Handle *outProcessHandle, const ExHeader_Info *exh prog_addrs_t mapped; prog_addrs_t vaddr; Handle codeset; - CodeSetInfo codesetinfo; + CodeSetHeader csh; u32 dataMemSize; u32 region = 0; - u32 count; + s32 count; for (count = 0; count < 28; count++) { u32 desc = exhi->aci.kernel_caps.descriptors[count]; @@ -267,7 +360,7 @@ static Result LoadProcessImpl(Handle *outProcessHandle, const ExHeader_Info *exh region = desc & 0xF00; } if (region == 0) - return MAKERESULT(RL_PERMANENT, RS_INVALIDARG, 1, 2); + return MAKERESULT(RL_PERMANENT, RS_INVALIDARG, RM_KERNEL, 2); // allocate process memory vaddr.text_addr = csi->text.address; @@ -287,18 +380,18 @@ static Result LoadProcessImpl(Handle *outProcessHandle, const ExHeader_Info *exh u32 *code = (u32 *)mapped.text_addr; bool isHomebrew = code[0] == 0xEA000006 && code[8] == 0xE1A0400E; - memcpy(&codesetinfo.name, csi->name, 8); - codesetinfo.program_id = titleId; - codesetinfo.text_addr = vaddr.text_addr; - codesetinfo.text_size = vaddr.text_size; - codesetinfo.text_size_total = vaddr.text_size; - codesetinfo.ro_addr = vaddr.ro_addr; - codesetinfo.ro_size = vaddr.ro_size; - codesetinfo.ro_size_total = vaddr.ro_size; - codesetinfo.rw_addr = vaddr.data_addr; - codesetinfo.rw_size = vaddr.data_size; - codesetinfo.rw_size_total = dataMemSize; - res = svcCreateCodeSet(&codeset, &codesetinfo, (void *)mapped.text_addr, (void *)mapped.ro_addr, (void *)mapped.data_addr); + memcpy(&csh.name, csi->name, 8); + csh.program_id = titleId; + csh.text_addr = vaddr.text_addr; + csh.text_size = vaddr.text_size; + csh.text_size_total = vaddr.text_size; + csh.ro_addr = vaddr.ro_addr; + csh.ro_size = vaddr.ro_size; + csh.ro_size_total = vaddr.ro_size; + csh.rw_addr = vaddr.data_addr; + csh.rw_size = vaddr.data_size; + csh.rw_size_total = dataMemSize; + res = svcCreateCodeSet(&codeset, &csh, mapped.text_addr, mapped.ro_addr, mapped.data_addr); if (R_SUCCEEDED(res)) { // There are always 28 descriptors @@ -349,9 +442,30 @@ static Result RegisterProgram(u64 *programHandle, FS_ProgramInfo *title, FS_Prog } else { - TRY(PXIPM_RegisterProgram(programHandle, title, update)); - if (IsHioId(*programHandle)) // double check this is indeed *not* HIO - panic(0); + bool loadedCxiFromStorage = false; + if (IsSysmoduleId(titleId) && CONFIG(LOADEXTFIRMSANDMODULES)) + { + // Forbid having two such file handles being open at the same time + // Also reload the file even if already cached. + InvalidateCachedCxiFile(); + + res = openSysmoduleCxi(&g_cached_sysmoduleCxiFile, titleId); + if (R_SUCCEEDED(res)) + { + // A .cxi with the correct name in /luma/sysmodule exists, proceed + *programHandle = SYSMODULE_CXI_COOKIE_MASK | (u32)titleId; + g_cached_sysmoduleCxiCookie = *programHandle; + loadedCxiFromStorage = true; + } + } + + if (!loadedCxiFromStorage) + { + // Otherwise, just load as normal + TRY(PXIPM_RegisterProgram(programHandle, title, update)); + if (IsHioId(*programHandle)) // double check this is indeed *not* HIO + panic(0); + } } return res; @@ -359,7 +473,17 @@ static Result RegisterProgram(u64 *programHandle, FS_ProgramInfo *title, FS_Prog static Result UnregisterProgram(u64 programHandle) { - return IsHioId(programHandle) ? FSREG_UnloadProgram(programHandle) : PXIPM_UnregisterProgram(programHandle); + if (g_cached_programHandle == programHandle) + g_cached_programHandle = 0; + + if (IsSysmoduleCxiCookie(programHandle)) + { + if (programHandle == g_cached_sysmoduleCxiCookie) + InvalidateCachedCxiFile(); + return 0; + } + else + return IsHioId(programHandle) ? FSREG_UnloadProgram(programHandle) : PXIPM_UnregisterProgram(programHandle); } void loaderHandleCommands(void *ctx) @@ -395,9 +519,6 @@ void loaderHandleCommands(void *ctx) break; case 3: // UnregisterProgram memcpy(&programHandle, &cmdbuf[1], 8); - - if (g_cached_programHandle == programHandle) - g_cached_programHandle = 0; cmdbuf[1] = UnregisterProgram(programHandle); cmdbuf[0] = IPC_MakeHeader(3, 1, 0); break; diff --git a/sysmodules/loader/source/luma_shared_config.h b/sysmodules/loader/source/luma_shared_config.h index ac7eab2..165b130 100644 --- a/sysmodules/loader/source/luma_shared_config.h +++ b/sysmodules/loader/source/luma_shared_config.h @@ -18,10 +18,11 @@ #include <3ds/types.h> -/// Luma shared config type. +/// Luma shared config type (private!). typedef struct LumaSharedConfig { - u64 hbldr_3dsx_tid; ///< Title ID to use for 3DSX loading. - bool use_hbldr; ///< Whether or not Loader should use hb:ldr (reset to true). + u64 hbldr_3dsx_tid; ///< Title ID to use for 3DSX loading (current). + u64 selected_hbldr_3dsx_tid; ///< Title ID to use for 3DSX loading (to be moved to "current" when the current app closes). + bool use_hbldr; ///< Whether or not Loader should use hb:ldr (reset to true). } LumaSharedConfig; /// Luma shared config. diff --git a/sysmodules/loader/source/main.c b/sysmodules/loader/source/main.c index 5bef9b7..f349286 100644 --- a/sysmodules/loader/source/main.c +++ b/sysmodules/loader/source/main.c @@ -66,7 +66,9 @@ static inline void loadCFWInfo(void) isSdMode = true; } - Luma_SharedConfig->hbldr_3dsx_tid = hbldrTid == 0 ? HBLDR_DEFAULT_3DSX_TID : hbldrTid; + hbldrTid = hbldrTid == 0 ? HBLDR_DEFAULT_3DSX_TID : hbldrTid; + Luma_SharedConfig->hbldr_3dsx_tid = hbldrTid; + Luma_SharedConfig->selected_hbldr_3dsx_tid = hbldrTid; Luma_SharedConfig->use_hbldr = true; } diff --git a/sysmodules/loader/source/patcher.c b/sysmodules/loader/source/patcher.c index 75a1e72..22840ec 100644 --- a/sysmodules/loader/source/patcher.c +++ b/sysmodules/loader/source/patcher.c @@ -4,6 +4,7 @@ #include "memory.h" #include "strings.h" #include "romfsredir.h" +#include "util.h" static u32 patchMemory(u8 *start, u32 size, const void *pattern, u32 patSize, s32 offset, const void *replace, u32 repSize, u32 count) { @@ -318,6 +319,69 @@ static inline bool applyCodeIpsPatch(u64 progId, u8 *code, u32 size) return ret; } +Result openSysmoduleCxi(IFile *outFile, u64 progId) +{ + char path[] = "/luma/sysmodules/0000000000000000.cxi"; + progIdToStr(path + sizeof("/luma/sysmodules/0000000000000000") - 2, progId); + + FS_ArchiveID archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW; + return fileOpen(outFile, archiveId, path, FS_OPEN_READ); +} + +bool readSysmoduleCxiNcchHeader(Ncch *outNcchHeader, IFile *file) +{ + u64 total = 0; + return R_SUCCEEDED(IFile_ReadAt(file, &total, outNcchHeader, 0, sizeof(Ncch))) && total == sizeof(Ncch); +} + +bool readSysmoduleCxiExHeaderInfo(ExHeader_Info *outExhi, const Ncch *ncchHeader, IFile *file) +{ + u64 total = 0; + u32 size = ncchHeader->exHeaderSize; + if (size < sizeof(ExHeader_Info)) + return false; + return R_SUCCEEDED(IFile_ReadAt(file, &total, outExhi, sizeof(Ncch), size)) && total == size; +} + +bool readSysmoduleCxiCode(u8 *outCode, u32 *outSize, u32 maxSize, IFile *file, const Ncch *ncchHeader) +{ + u32 contentUnitShift = 9 + ncchHeader->flags[6]; + u32 exeFsOffset = ncchHeader->exeFsOffset << contentUnitShift; + u32 exeFsSize = ncchHeader->exeFsSize << contentUnitShift; + + if (exeFsSize < sizeof(ExeFsHeader) || exeFsOffset < 0x200 + ncchHeader->exHeaderSize) + return false; + + // Read ExeFs header + ExeFsHeader hdr; + u64 total = 0; + u32 size = sizeof(ExeFsHeader); + Result res = IFile_ReadAt(file, &total, &hdr, exeFsOffset, size); + + if (R_FAILED(res) || total != size) + return false; + + // Get .code section info + ExeFsFileHeader *codeHdr = NULL; + for (u32 i = 0; i < 8; i++) + { + ExeFsFileHeader *fileHdr = &hdr.fileHeaders[i]; + if (strncmp(fileHdr->name, ".code", 8) == 0) + codeHdr = fileHdr; + } + + if (codeHdr == NULL) + return false; + + size = codeHdr->size; + *outSize = size; + if (size > maxSize) + return false; + + res = IFile_ReadAt(file, &total, outCode, exeFsOffset + sizeof(ExeFsHeader) + codeHdr->offset, size); + return R_SUCCEEDED(res) && total == size; +} + bool loadTitleCodeSection(u64 progId, u8 *code, u32 size) { /* Here we look for "/luma/titles/[u64 titleID in hex, uppercase]/code.bin" @@ -746,7 +810,7 @@ void patchCode(u64 progId, u16 progVer, u8 *code, u32 size, u32 textSize, u32 ro 0x00, 0x26 }; - //Disable SecureInfo signature check + //Disable SecureInfo signature check (redundant) if(!patchMemory(code, textSize, pattern, sizeof(pattern), 0, @@ -784,7 +848,7 @@ void patchCode(u64 progId, u16 progVer, u8 *code, u32 size, u32 textSize, u32 ro 0x00, 0x00, 0xA0, 0xE3, 0x1E, 0xFF, 0x2F, 0xE1 //mov r0, #0; bx lr }; - //Disable CRR0 signature (RSA2048 with SHA256) check and CRO0/CRR0 SHA256 hash checks (section hashes, and hash table) + //Disable CRR0 signature (RSA2048 with SHA256) check (redundant) and CRO0/CRR0 SHA256 hash checks (section hashes, and hash table) if(!patchMemory(code, textSize, pattern, sizeof(pattern), -9, @@ -845,6 +909,7 @@ void patchCode(u64 progId, u16 progVer, u8 *code, u32 size, u32 textSize, u32 ro else if((progId & ~0xF0000001ULL) == 0x0004013000001A02LL) //DSP, SAFE_FIRM DSP { + // This patch is redundant static const u8 pattern[] = { 0xE3, 0x10, 0x10, 0x80, 0xE2 }, @@ -863,10 +928,18 @@ void patchCode(u64 progId, u16 progVer, u8 *code, u32 size, u32 textSize, u32 ro if(CONFIG(PATCHGAMES) && !nextGamePatchDisabled) { - if(!patcherApplyCodeBpsPatch(progId, code, size)) goto error; - if(!applyCodeIpsPatch(progId, code, size)) goto error; + bool isApp = ((progId >> 32) & ~0x12) == 0x00040000; + bool isApplet = (progId >> 32) == 0x00040030; + bool isSysmodule = (progId >> 32) == 0x00040130; + + bool shouldPatchIps = !isSysmodule || (isSysmodule && CONFIG(LOADEXTFIRMSANDMODULES)); + if (shouldPatchIps) + { + if(!patcherApplyCodeBpsPatch(progId, code, size)) goto error; + if(!applyCodeIpsPatch(progId, code, size)) goto error; + } - if((u32)((progId >> 0x20) & 0xFFFFFFEDULL) == 0x00040000 || isHomeMenu) + if(isApp || isApplet) { u8 mask, regionId, diff --git a/sysmodules/loader/source/patcher.h b/sysmodules/loader/source/patcher.h index 6582d18..32789ce 100644 --- a/sysmodules/loader/source/patcher.h +++ b/sysmodules/loader/source/patcher.h @@ -3,6 +3,7 @@ #include <3ds/types.h> #include <3ds/exheader.h> #include "ifile.h" +#include "util.h" #define MAKE_BRANCH(src,dst) (0xEA000000 | ((u32)((((u8 *)(dst) - (u8 *)(src)) >> 2) - 2) & 0xFFFFFF)) #define MAKE_BRANCH_LINK(src,dst) (0xEB000000 | ((u32)((((u8 *)(dst) - (u8 *)(src)) >> 2) - 2) & 0xFFFFFF)) @@ -32,8 +33,10 @@ enum singleOptions USEEMUFIRM, LOADEXTFIRMSANDMODULES, PATCHGAMES, + REDIRECTAPPTHREADS, PATCHVERSTRING, SHOWGBABOOT, + FORCEHEADPHONEOUTPUT, PATCHUNITINFO, DISABLEARM11EXCHANDLERS, ENABLESAFEFIRMROSALINA, @@ -45,3 +48,8 @@ extern bool isN3DS, isSdMode, nextGamePatchDisabled; void patchCode(u64 progId, u16 progVer, u8 *code, u32 size, u32 textSize, u32 roSize, u32 dataSize, u32 roAddress, u32 dataAddress); bool loadTitleCodeSection(u64 progId, u8 *code, u32 size); bool loadTitleExheaderInfo(u64 progId, ExHeader_Info *exheaderInfo); + +Result openSysmoduleCxi(IFile *outFile, u64 progId); +bool readSysmoduleCxiNcchHeader(Ncch *outNcchHeader, IFile *file); +bool readSysmoduleCxiExHeaderInfo(ExHeader_Info *outExhi, const Ncch *ncchHeader, IFile *file); +bool readSysmoduleCxiCode(u8 *outCode, u32 *outSize, u32 maxSize, IFile *file, const Ncch *ncchHeader); diff --git a/sysmodules/loader/source/util.h b/sysmodules/loader/source/util.h index 1e8cd0a..4459be6 100644 --- a/sysmodules/loader/source/util.h +++ b/sysmodules/loader/source/util.h @@ -11,6 +11,50 @@ #define TRY(expr) if(R_FAILED(res = (expr))) return res; #define TRYG(expr, label) if(R_FAILED(res = (expr))) goto label; +typedef struct Ncch { + u8 sig[0x100]; //RSA-2048 signature of the NCCH header, using SHA-256 + char magic[4]; //NCCH + u32 contentSize; //Media unit + u8 partitionId[8]; + u8 makerCode[2]; + u16 version; + u8 reserved1[4]; + u8 programID[8]; + u8 reserved2[0x10]; + u8 logoHash[0x20]; //Logo Region SHA-256 hash + char productCode[0x10]; + u8 exHeaderHash[0x20]; //Extended header SHA-256 hash + u32 exHeaderSize; //Extended header size + u32 reserved3; + u8 flags[8]; + u32 plainOffset; //Media unit + u32 plainSize; //Media unit + u32 logoOffset; //Media unit + u32 logoSize; //Media unit + u32 exeFsOffset; //Media unit + u32 exeFsSize; //Media unit + u32 exeFsHashSize; //Media unit + u32 reserved4; + u32 romFsOffset; //Media unit + u32 romFsSize; //Media unit + u32 romFsHashSize; //Media unit + u32 reserved5; + u8 exeFsHash[0x20]; //ExeFS superblock SHA-256 hash + u8 romFsHash[0x20]; //RomFS superblock SHA-256 hash +} Ncch; + +typedef struct ExeFsFileHeader { + char name[8]; + u32 offset; + u32 size; +} ExeFsFileHeader; + +typedef struct ExeFsHeader { + ExeFsFileHeader fileHeaders[10]; + u8 _reserved_0xa0[0xC0 - 0xA0]; + u8 fileHashes[10][32]; +} ExeFsHeader; + #ifdef XDS static void hexItoa(u64 number, char *out, u32 digits, bool uppercase) { diff --git a/sysmodules/pm/pm.rsf b/sysmodules/pm/pm.rsf index f98f095..41c1ec7 100644 --- a/sysmodules/pm/pm.rsf +++ b/sysmodules/pm/pm.rsf @@ -25,7 +25,7 @@ AccessControlInfo: DisableDebug : false EnableForceDebug : false - CanWriteSharedPage : false + CanWriteSharedPage : true # changed CanUsePrivilegedPriority : false CanUseNonAlphabetAndNumber : false PermitMainFunctionArgument : false diff --git a/sysmodules/pm/source/luma.c b/sysmodules/pm/source/luma.c index eb14d26..81d0b00 100644 --- a/sysmodules/pm/source/luma.c +++ b/sysmodules/pm/source/luma.c @@ -3,6 +3,23 @@ #include "luma.h" #include "util.h" +u32 config, multiConfig, bootConfig; + +void readLumaConfig(void) +{ + s64 out = 0; + bool isLumaWithKext = svcGetSystemInfo(&out, 0x20000, 0) == 1; + if (isLumaWithKext) + { + svcGetSystemInfo(&out, 0x10000, 3); + config = (u32)out; + svcGetSystemInfo(&out, 0x10000, 4); + multiConfig = (u32)out; + svcGetSystemInfo(&out, 0x10000, 5); + bootConfig = (u32)out; + } +} + bool hasKExt(void) { s64 val; diff --git a/sysmodules/pm/source/luma.h b/sysmodules/pm/source/luma.h index 80b80ef..d73a610 100644 --- a/sysmodules/pm/source/luma.h +++ b/sysmodules/pm/source/luma.h @@ -2,6 +2,43 @@ #include <3ds/types.h> +#define CONFIG(a) (((config >> (a)) & 1) != 0) +#define MULTICONFIG(a) ((multiConfig >> (2 * (a))) & 3) +#define BOOTCONFIG(a, b) ((bootConfig >> (a)) & (b)) + +#define BOOTCFG_NAND BOOTCONFIG(0, 7) +#define BOOTCFG_FIRM BOOTCONFIG(3, 7) +#define BOOTCFG_NOFORCEFLAG BOOTCONFIG(6, 1) +#define BOOTCFG_NTRCARDBOOT BOOTCONFIG(7, 1) + +enum multiOptions +{ + DEFAULTEMU = 0, + BRIGHTNESS, + SPLASH, + PIN, + NEWCPU, + AUTOBOOTMODE, +}; + +enum singleOptions +{ + AUTOBOOTEMU = 0, + USEEMUFIRM, + LOADEXTFIRMSANDMODULES, + PATCHGAMES, + REDIRECTAPPTHREADS, + PATCHVERSTRING, + SHOWGBABOOT, + FORCEHEADPHONEOUTPUT, + PATCHUNITINFO, + DISABLEARM11EXCHANDLERS, + ENABLESAFEFIRMROSALINA, +}; + +extern u32 config, multiConfig, bootConfig; + +void readLumaConfig(void); bool hasKExt(void); u32 getKExtSize(void); u32 getStolenSystemMemRegionSize(void); diff --git a/sysmodules/pm/source/luma_shared_config.h b/sysmodules/pm/source/luma_shared_config.h new file mode 100644 index 0000000..165b130 --- /dev/null +++ b/sysmodules/pm/source/luma_shared_config.h @@ -0,0 +1,29 @@ +/* This paricular file is licensed under the following terms: */ + +/* +* This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable +* for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it +* and redistribute it freely, subject to the following restrictions: +* +* The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +* If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +* +* Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +* This notice may not be removed or altered from any source distribution. +*/ + +#pragma once + +#include <3ds/types.h> + +/// Luma shared config type (private!). +typedef struct LumaSharedConfig { + u64 hbldr_3dsx_tid; ///< Title ID to use for 3DSX loading (current). + u64 selected_hbldr_3dsx_tid; ///< Title ID to use for 3DSX loading (to be moved to "current" when the current app closes). + bool use_hbldr; ///< Whether or not Loader should use hb:ldr (reset to true). +} LumaSharedConfig; + +/// Luma shared config. +#define Luma_SharedConfig ((volatile LumaSharedConfig *)(OS_SHAREDCFG_VADDR + 0x800)) diff --git a/sysmodules/pm/source/main.c b/sysmodules/pm/source/main.c index 9b7fedc..78f4914 100644 --- a/sysmodules/pm/source/main.c +++ b/sysmodules/pm/source/main.c @@ -13,6 +13,7 @@ #include "util.h" #include "my_thread.h" #include "service_manager.h" +#include "luma.h" static MyThread processMonitorThread, taskRunnerThread; static u8 ALIGN(8) processDataBuffer[0x40 * sizeof(ProcessData)] = {0}; @@ -34,6 +35,7 @@ Result __sync_init(void); void initSystem() { __sync_init(); + readLumaConfig(); //__libc_init_array(); // Wait for sm diff --git a/sysmodules/pm/source/process_monitor.c b/sysmodules/pm/source/process_monitor.c index c2847d6..0e05d4a 100644 --- a/sysmodules/pm/source/process_monitor.c +++ b/sysmodules/pm/source/process_monitor.c @@ -6,6 +6,7 @@ #include "reslimit.h" #include "manager.h" #include "util.h" +#include "luma_shared_config.h" static void cleanupProcess(ProcessData *process) { @@ -33,6 +34,11 @@ static void cleanupProcess(ProcessData *process) assertSuccess(resetAppMemLimit()); } g_manager.runningApplicationData = NULL; + + // We need to do this here to ensure that the ExHeader at init matches the ExHeader + // at termination at all times, otherwise the process refcounts of sysmodules + // get all messed up. + Luma_SharedConfig->hbldr_3dsx_tid = Luma_SharedConfig->selected_hbldr_3dsx_tid; } if (g_manager.debugData != NULL && process->handle == g_manager.debugData->handle) { diff --git a/sysmodules/pm/source/reslimit.c b/sysmodules/pm/source/reslimit.c index 4dad971..b057257 100644 --- a/sysmodules/pm/source/reslimit.c +++ b/sysmodules/pm/source/reslimit.c @@ -308,6 +308,11 @@ Result resetAppMemLimit(void) Result setAppCpuTimeLimit(s64 limit) { + // Prevent apps from enabling preemption on core1 (and kernel will + // redirect apps threads from core 1 to 2). + if (IS_N3DS && CONFIG(REDIRECTAPPTHREADS)) + return 0; + ResourceLimitType category = RESLIMIT_CPUTIME; return svcSetResourceLimitValues(g_manager.reslimits[0], &category, &limit, 1); } diff --git a/sysmodules/rosalina/data/config_template.ini b/sysmodules/rosalina/data/config_template.ini index daab454..5c09c02 100644 Binary files a/sysmodules/rosalina/data/config_template.ini and b/sysmodules/rosalina/data/config_template.ini differ diff --git a/sysmodules/rosalina/include/luma_config.h b/sysmodules/rosalina/include/luma_config.h new file mode 100644 index 0000000..25d26c1 --- /dev/null +++ b/sysmodules/rosalina/include/luma_config.h @@ -0,0 +1,60 @@ +/* +* This file is part of Luma3DS +* Copyright (C) 2023 Aurora Wright, TuxSH +* +* This program 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. +* +* This program 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. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#include <3ds/types.h> + +#define CONFIG(a) (((cfg->config >> (a)) & 1) != 0) +#define MULTICONFIG(a) ((cfg->multiConfig >> (2 * (a))) & 3) +#define BOOTCONFIG(a, b) ((cfg->bootConfig >> (a)) & (b)) + +enum singleOptions +{ + AUTOBOOTEMU = 0, + USEEMUFIRM, + LOADEXTFIRMSANDMODULES, + PATCHGAMES, + REDIRECTAPPTHREADS, + PATCHVERSTRING, + SHOWGBABOOT, + FORCEHEADPHONEOUTPUT, + PATCHUNITINFO, + DISABLEARM11EXCHANDLERS, + ENABLESAFEFIRMROSALINA, +}; + +enum multiOptions +{ + DEFAULTEMU = 0, + BRIGHTNESS, + SPLASH, + PIN, + NEWCPU, + AUTOBOOTMODE, +}; + +void LumaConfig_ConvertComboToString(char *out, u32 combo); +Result LumaConfig_SaveSettings(void); +void LumaConfig_RequestSaveSettings(void); diff --git a/sysmodules/rosalina/include/luma_shared_config.h b/sysmodules/rosalina/include/luma_shared_config.h index ac7eab2..165b130 100644 --- a/sysmodules/rosalina/include/luma_shared_config.h +++ b/sysmodules/rosalina/include/luma_shared_config.h @@ -18,10 +18,11 @@ #include <3ds/types.h> -/// Luma shared config type. +/// Luma shared config type (private!). typedef struct LumaSharedConfig { - u64 hbldr_3dsx_tid; ///< Title ID to use for 3DSX loading. - bool use_hbldr; ///< Whether or not Loader should use hb:ldr (reset to true). + u64 hbldr_3dsx_tid; ///< Title ID to use for 3DSX loading (current). + u64 selected_hbldr_3dsx_tid; ///< Title ID to use for 3DSX loading (to be moved to "current" when the current app closes). + bool use_hbldr; ///< Whether or not Loader should use hb:ldr (reset to true). } LumaSharedConfig; /// Luma shared config. diff --git a/sysmodules/rosalina/include/menu.h b/sysmodules/rosalina/include/menu.h index 22d369e..4656b8b 100644 --- a/sysmodules/rosalina/include/menu.h +++ b/sysmodules/rosalina/include/menu.h @@ -33,13 +33,14 @@ #define HID_PAD (REG32(0x10146000) ^ 0xFFF) - #define DEFAULT_MENU_COMBO (KEY_L | KEY_DDOWN | KEY_SELECT) #define DIRECTIONAL_KEYS (KEY_DOWN | KEY_UP | KEY_LEFT | KEY_RIGHT) #define CORE_APPLICATION 0 #define CORE_SYSTEM 1 +#define FLOAT_CONV_MULT 1e8 // for screen filters + typedef enum MenuItemAction { MENU_END = 0, METHOD = 1, @@ -75,6 +76,7 @@ extern bool preTerminationRequested; extern Handle preTerminationEvent; u32 waitInputWithTimeout(s32 msec); +u32 waitInputWithTimeoutEx(u32 *outHeldKeys, s32 msec); u32 waitInput(void); u32 waitComboWithTimeout(s32 msec); diff --git a/sysmodules/rosalina/include/menus.h b/sysmodules/rosalina/include/menus.h index 88fede1..3b7a662 100644 --- a/sysmodules/rosalina/include/menus.h +++ b/sysmodules/rosalina/include/menus.h @@ -36,6 +36,7 @@ void RosalinaMenu_TakeScreenshot(void); void RosalinaMenu_ChangeScreenBrightness(void); void RosalinaMenu_ShowCredits(void); void RosalinaMenu_ProcessList(void); +void RosalinaMenu_SaveSettings(void); void RosalinaMenu_PowerOff(void); void RosalinaMenu_Reboot(void); void RosalinaMenu_Cheats(void); diff --git a/sysmodules/rosalina/include/menus/miscellaneous.h b/sysmodules/rosalina/include/menus/miscellaneous.h index c6fd1ff..676f6f3 100644 --- a/sysmodules/rosalina/include/menus/miscellaneous.h +++ b/sysmodules/rosalina/include/menus/miscellaneous.h @@ -34,7 +34,6 @@ extern int lastNtpTzOffset; void MiscellaneousMenu_SwitchBoot3dsxTargetTitle(void); void MiscellaneousMenu_ChangeMenuCombo(void); -void MiscellaneousMenu_SaveSettings(void); void MiscellaneousMenu_InputRedirection(void); void MiscellaneousMenu_UpdateTimeDateNtp(void); void MiscellaneousMenu_NullifyUserTimeOffset(void); diff --git a/sysmodules/rosalina/include/menus/screen_filters.h b/sysmodules/rosalina/include/menus/screen_filters.h index 3fdaee1..91a9880 100644 --- a/sysmodules/rosalina/include/menus/screen_filters.h +++ b/sysmodules/rosalina/include/menus/screen_filters.h @@ -30,13 +30,19 @@ extern Menu screenFiltersMenu; -extern int screenFiltersCurrentTemperature; -extern float screenFiltersCurrentGamma; -extern float screenFiltersCurrentContrast; -extern float screenFiltersCurrentBrightness; +typedef struct ScreenFilter { + u16 cct; + bool invert; + float gamma; + float contrast; + float brightness; +} ScreenFilter; -void ScreenFiltersMenu_SetCct(int cct); -void ScreenFiltersMenu_RestoreCct(void); +extern ScreenFilter topScreenFilter; +extern ScreenFilter bottomScreenFilter; + +void ScreenFiltersMenu_RestoreSettings(void); +void ScreenFiltersMenu_LoadConfig(void); void ScreenFiltersMenu_SetDefault(void); // 6500K (default) @@ -49,3 +55,5 @@ void ScreenFiltersMenu_SetIncandescent(void); // 2700K void ScreenFiltersMenu_SetWarmIncandescent(void); // 2300K void ScreenFiltersMenu_SetCandle(void); // 1900K void ScreenFiltersMenu_SetEmber(void); // 1200K + +void ScreenFiltersMenu_AdvancedConfiguration(void); diff --git a/sysmodules/rosalina/include/shell.h b/sysmodules/rosalina/include/shell.h new file mode 100644 index 0000000..aa95dca --- /dev/null +++ b/sysmodules/rosalina/include/shell.h @@ -0,0 +1,29 @@ +/* +* This file is part of Luma3DS +* Copyright (C) 2023 Aurora Wright, TuxSH +* +* This program 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. +* +* This program 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. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#include <3ds/types.h> + +void handleShellOpened(void); diff --git a/sysmodules/rosalina/include/utils.h b/sysmodules/rosalina/include/utils.h index 681692f..d9e7739 100644 --- a/sysmodules/rosalina/include/utils.h +++ b/sysmodules/rosalina/include/utils.h @@ -42,6 +42,8 @@ #define REG32(addr) (*(vu32 *)(PA_PTR(addr))) +#define CLAMP(v, m, M) ((v) <= (m) ? (m) : (v) >= (M) ? (M) : (v)) + static inline u32 makeArmBranch(const void *src, const void *dst, bool link) // the macros for those are ugly and buggy { u32 instrBase = link ? 0xEB000000 : 0xEA000000; @@ -87,3 +89,4 @@ void formatMemoryPermission(char *outbuf, MemPerm perm); void formatUserMemoryState(char *outbuf, MemState state); u32 formatMemoryMapOfProcess(char *outbuf, u32 bufLen, Handle handle); int dateTimeToString(char *out, u64 msSince1900, bool filenameFormat); +int floatToString(char *out, float f, u32 precision, bool pad); diff --git a/sysmodules/rosalina/source/luma_config.c b/sysmodules/rosalina/source/luma_config.c new file mode 100644 index 0000000..a2a76a5 --- /dev/null +++ b/sysmodules/rosalina/source/luma_config.c @@ -0,0 +1,254 @@ +/* +* This file is part of Luma3DS +* Copyright (C) 2023 Aurora Wright, TuxSH +* +* This program 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. +* +* This program 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. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#include <3ds.h> +#include "memory.h" +#include "fmt.h" +#include "luma_config.h" +#include "screen_filters.h" +#include "config_template_ini.h" +#include "ifile.h" +#include "menus/miscellaneous.h" +#include "plugin/plgloader.h" + +typedef struct CfgData { + u16 formatVersionMajor, formatVersionMinor; + + u32 config, multiConfig, bootConfig; + u32 splashDurationMsec; + + u64 hbldr3dsxTitleId; + u32 rosalinaMenuCombo; + u32 pluginLoaderFlags; + s16 ntpTzOffetMinutes; + + ScreenFilter topScreenFilter; + ScreenFilter bottomScreenFilter; + + u64 autobootTwlTitleId; + u8 autobootCtrAppmemtype; +} CfgData; + +bool saveSettingsRequest = false; + +void LumaConfig_ConvertComboToString(char *out, u32 combo) +{ + static const char *keys[] = { + "A", "B", "Select", "Start", "Right", "Left", "Up", "Down", "R", "L", "X", "Y", + "?", "?", + "ZL", "ZR", + "?", "?", "?", "?", + "Touch", + "?", "?", "?", + "CStick Right", "CStick Left", "CStick Up", "CStick Down", + "CPad Right", "CPad Left", "CPad Up", "CPad Down", + }; + + char *outOrig = out; + out[0] = 0; + for(s32 i = 31; i >= 0; i--) + { + if(combo & (1 << i)) + { + strcpy(out, keys[i]); + out += strlen(keys[i]); + *out++ = '+'; + } + } + + if (out != outOrig) + out[-1] = 0; +} + +static size_t LumaConfig_SaveLumaIniConfigToStr(char *out, const CfgData *cfg) +{ + char lumaVerStr[64]; + char lumaRevSuffixStr[16]; + char rosalinaMenuComboStr[128]; + + const char *splashPosStr; + const char *n3dsCpuStr; + + s64 outInfo; + svcGetSystemInfo(&outInfo, 0x10000, 0); + u32 version = (u32)outInfo; + + svcGetSystemInfo(&outInfo, 0x10000, 1); + u32 commitHash = (u32)outInfo; + + svcGetSystemInfo(&outInfo, 0x10000, 0x200); + bool isRelease = (bool)outInfo; + + switch (MULTICONFIG(SPLASH)) { + default: case 0: splashPosStr = "off"; break; + case 1: splashPosStr = "before payloads"; break; + case 2: splashPosStr = "after payloads"; break; + } + + switch (MULTICONFIG(NEWCPU)) { + default: case 0: n3dsCpuStr = "off"; break; + case 1: n3dsCpuStr = "clock"; break; + case 2: n3dsCpuStr = "l2"; break; + case 3: n3dsCpuStr = "clock+l2"; break; + } + + if (GET_VERSION_REVISION(version) != 0) { + sprintf(lumaVerStr, "Luma3DS v%d.%d.%d", (int)GET_VERSION_MAJOR(version), (int)GET_VERSION_MINOR(version), (int)GET_VERSION_REVISION(version)); + } else { + sprintf(lumaVerStr, "Luma3DS v%d.%d", (int)GET_VERSION_MAJOR(version), (int)GET_VERSION_MINOR(version)); + } + + if (isRelease) { + strcpy(lumaRevSuffixStr, ""); + } else { + sprintf(lumaRevSuffixStr, "-%08lx", (u32)commitHash); + } + + LumaConfig_ConvertComboToString(rosalinaMenuComboStr, cfg->rosalinaMenuCombo); + + static const int pinOptionToDigits[] = { 0, 4, 6, 8 }; + int pinNumDigits = pinOptionToDigits[MULTICONFIG(PIN)]; + + char topScreenFilterGammaStr[32]; + char topScreenFilterContrastStr[32]; + char topScreenFilterBrightnessStr[32]; + floatToString(topScreenFilterGammaStr, cfg->topScreenFilter.gamma, 6, false); + floatToString(topScreenFilterContrastStr, cfg->topScreenFilter.contrast, 6, false); + floatToString(topScreenFilterBrightnessStr, cfg->topScreenFilter.brightness, 6, false); + + char bottomScreenFilterGammaStr[32]; + char bottomScreenFilterContrastStr[32]; + char bottomScreenFilterBrightnessStr[32]; + floatToString(bottomScreenFilterGammaStr, cfg->bottomScreenFilter.gamma, 6, false); + floatToString(bottomScreenFilterContrastStr, cfg->bottomScreenFilter.contrast, 6, false); + floatToString(bottomScreenFilterBrightnessStr, cfg->bottomScreenFilter.brightness, 6, false); + + int n = sprintf( + out, (const char *)config_template_ini, + lumaVerStr, lumaRevSuffixStr, + + (int)cfg->formatVersionMajor, (int)cfg->formatVersionMinor, + (int)CONFIG(AUTOBOOTEMU), (int)CONFIG(USEEMUFIRM), + (int)CONFIG(LOADEXTFIRMSANDMODULES), (int)CONFIG(PATCHGAMES), + (int)CONFIG(REDIRECTAPPTHREADS), (int)CONFIG(PATCHVERSTRING), + (int)CONFIG(SHOWGBABOOT), (int)CONFIG(FORCEHEADPHONEOUTPUT), + + 1 + (int)MULTICONFIG(DEFAULTEMU), 4 - (int)MULTICONFIG(BRIGHTNESS), + splashPosStr, (unsigned int)cfg->splashDurationMsec, + pinNumDigits, n3dsCpuStr, (int)MULTICONFIG(AUTOBOOTMODE), + + cfg->hbldr3dsxTitleId, rosalinaMenuComboStr, (int)(cfg->pluginLoaderFlags & 1), + (int)cfg->ntpTzOffetMinutes, + + (int)cfg->topScreenFilter.cct, (int)cfg->bottomScreenFilter.cct, + topScreenFilterGammaStr, bottomScreenFilterGammaStr, + topScreenFilterContrastStr, bottomScreenFilterContrastStr, + topScreenFilterBrightnessStr, bottomScreenFilterBrightnessStr, + (int)cfg->topScreenFilter.invert, (int)cfg->bottomScreenFilter.invert, + + cfg->autobootTwlTitleId, (int)cfg->autobootCtrAppmemtype, + + (int)CONFIG(PATCHUNITINFO), (int)CONFIG(DISABLEARM11EXCHANDLERS), + (int)CONFIG(ENABLESAFEFIRMROSALINA) + ); + + return n < 0 ? 0 : (size_t)n; +} + +void LumaConfig_RequestSaveSettings(void) { + saveSettingsRequest = true; +} + +Result LumaConfig_SaveSettings(void) +{ + char inibuf[0x2000]; + + Result res; + + IFile file; + u64 total; + + CfgData configData; + + u32 formatVersion; + u32 config, multiConfig, bootConfig; + u32 splashDurationMsec; + + u8 autobootCtrAppmemtype; + u64 autobootTwlTitleId; + + s64 out; + bool isSdMode; + + svcGetSystemInfo(&out, 0x10000, 2); + formatVersion = (u32)out; + svcGetSystemInfo(&out, 0x10000, 3); + config = (u32)out; + svcGetSystemInfo(&out, 0x10000, 4); + multiConfig = (u32)out; + svcGetSystemInfo(&out, 0x10000, 5); + bootConfig = (u32)out; + svcGetSystemInfo(&out, 0x10000, 6); + splashDurationMsec = (u32)out; + + svcGetSystemInfo(&out, 0x10000, 0x10); + autobootTwlTitleId = (u64)out; + svcGetSystemInfo(&out, 0x10000, 0x11); + autobootCtrAppmemtype = (u8)out; + + svcGetSystemInfo(&out, 0x10000, 0x203); + isSdMode = (bool)out; + + configData.formatVersionMajor = (u16)(formatVersion >> 16); + configData.formatVersionMinor = (u16)formatVersion; + configData.config = config; + configData.multiConfig = multiConfig; + configData.bootConfig = bootConfig; + configData.splashDurationMsec = splashDurationMsec; + configData.hbldr3dsxTitleId = Luma_SharedConfig->selected_hbldr_3dsx_tid; + configData.rosalinaMenuCombo = menuCombo; + configData.pluginLoaderFlags = PluginLoader__IsEnabled(); + configData.ntpTzOffetMinutes = (s16)lastNtpTzOffset; + configData.topScreenFilter = topScreenFilter; + configData.bottomScreenFilter = bottomScreenFilter; + configData.autobootTwlTitleId = autobootTwlTitleId; + configData.autobootCtrAppmemtype = autobootCtrAppmemtype; + + size_t n = LumaConfig_SaveLumaIniConfigToStr(inibuf, &configData); + FS_ArchiveID archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW; + if (n > 0) + res = IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, "/luma/config.ini"), FS_OPEN_CREATE | FS_OPEN_WRITE); + else + res = -1; + + if(R_SUCCEEDED(res)) + res = IFile_SetSize(&file, n); + if(R_SUCCEEDED(res)) + res = IFile_Write(&file, &total, inibuf, n, 0); + IFile_Close(&file); + + return res; +} diff --git a/sysmodules/rosalina/source/main.c b/sysmodules/rosalina/source/main.c index d1fe738..b170ed3 100644 --- a/sysmodules/rosalina/source/main.c +++ b/sysmodules/rosalina/source/main.c @@ -41,6 +41,7 @@ #include "minisoc.h" #include "draw.h" #include "bootdiag.h" +#include "shell.h" #include "task_runner.h" #include "plugin.h" @@ -89,10 +90,6 @@ void initSystem(void) svcGetSystemInfo(&out, 0x10000, 0x103); lastNtpTzOffset = (s16)out; - miscellaneousMenu.items[0].title = Luma_SharedConfig->hbldr_3dsx_tid == HBLDR_DEFAULT_3DSX_TID ? - "Switch the hb. title to the current app." : - "Switch the hb. title to hblauncher_loader"; - for(res = 0xD88007FA; res == (Result)0xD88007FA; svcSleepThread(500 * 1000LL)) { res = srvInit(); @@ -109,6 +106,10 @@ void initSystem(void) if (R_FAILED(FSUSER_SetPriority(-16))) svcBreak(USERBREAK_PANIC); + miscellaneousMenu.items[0].title = Luma_SharedConfig->selected_hbldr_3dsx_tid == HBLDR_DEFAULT_3DSX_TID ? + "Switch the hb. title to the current app." : + "Switch the hb. title to " HBLDR_DEFAULT_3DSX_TITLE_NAME; + // **** DO NOT init services that don't come from KIPs here **** // Instead, init the service only where it's actually init (then deinit it). @@ -168,8 +169,10 @@ static void handleShellNotification(u32 notificationId) if (notificationId == 0x213) { // Shell opened - // Note that this notification is fired on system init - ScreenFiltersMenu_RestoreCct(); + // Note that this notification is also fired on system init. + // Sequence goes like this: MCU fires notif. 0x200 on shell open + // and shell close, then NS demuxes it and fires 0x213 and 0x214. + handleShellOpened(); menuShouldExit = false; } else { // Shell closed diff --git a/sysmodules/rosalina/source/menu.c b/sysmodules/rosalina/source/menu.c index 5386d9a..f61fa44 100644 --- a/sysmodules/rosalina/source/menu.c +++ b/sysmodules/rosalina/source/menu.c @@ -32,11 +32,13 @@ #include "ifile.h" #include "menus.h" #include "utils.h" +#include "luma_config.h" #include "menus/n3ds.h" #include "menus/cheats.h" #include "minisoc.h" #include "plugin.h" #include "menus/screen_filters.h" +#include "shell.h" u32 menuCombo = 0; bool isHidInitialized = false; @@ -82,6 +84,33 @@ u32 waitInputWithTimeout(s32 msec) return keys; } +u32 waitInputWithTimeoutEx(u32 *outHeldKeys, s32 msec) +{ + s32 n = 0; + u32 keys; + + do + { + svcSleepThread(1 * 1000 * 1000LL); + Draw_Lock(); + if (!isHidInitialized || menuShouldExit) + { + keys = 0; + Draw_Unlock(); + break; + } + n++; + + hidScanInput(); + keys = convertHidKeys(hidKeysDown()) | (convertHidKeys(hidKeysDownRepeat()) & DIRECTIONAL_KEYS); + *outHeldKeys = convertHidKeys(hidKeysHeld()); + Draw_Unlock(); + } while (keys == 0 && !menuShouldExit && isHidInitialized && (msec < 0 || n < msec)); + + + return keys; +} + u32 waitInput(void) { return waitInputWithTimeout(-1); @@ -159,8 +188,14 @@ static Result menuUpdateMcuInfo(void) if (!isServiceUsable("mcu::HWC")) return -1; - res = mcuHwcInit(); + Handle *mcuHwcHandlePtr = mcuHwcGetSessionHandle(); + *mcuHwcHandlePtr = 0; + + res = srvGetServiceHandle(mcuHwcHandlePtr, "mcu::HWC"); + // Try to steal the handle if some other process is using the service (custom SVC) if (R_FAILED(res)) + res = svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, mcuHwcHandlePtr, "mcu::HWC"); + if (res != 0) return res; // Read single-byte mcu regs 0x0A to 0x0D directly @@ -191,7 +226,7 @@ static Result menuUpdateMcuInfo(void) mcuFwVersion = SYSTEM_VERSION(major - 0x10, minor, 0); } - mcuHwcExit(); + svcCloseHandle(*mcuHwcHandlePtr); return res; } @@ -232,18 +267,11 @@ void menuThreadMain(void) if(isN3DS) N3DSMenu_UpdateStatus(); - while (!isServiceUsable("ac:u") || !isServiceUsable("hid:USER")) - svcSleepThread(500 * 1000 * 1000LL); - - s64 out; - svcGetSystemInfo(&out, 0x10000, 0x102); - screenFiltersCurrentTemperature = (int)(u32)out; - if (screenFiltersCurrentTemperature < 1000 || screenFiltersCurrentTemperature > 25100) - screenFiltersCurrentTemperature = 6500; + while (!isServiceUsable("ac:u") || !isServiceUsable("hid:USER") || !isServiceUsable("gsp::Gpu") || !isServiceUsable("cdc:CHK")) + svcSleepThread(250 * 1000 * 1000LL); - // Careful about race conditions here - if (screenFiltersCurrentTemperature != 6500) - ScreenFiltersMenu_SetCct(screenFiltersCurrentTemperature); + ScreenFiltersMenu_LoadConfig(); + handleShellOpened(); hidInit(); // assume this doesn't fail isHidInitialized = true; @@ -266,7 +294,7 @@ void menuThreadMain(void) } if (saveSettingsRequest) { - SaveSettings(); + LumaConfig_SaveSettings(); saveSettingsRequest = false; } } @@ -356,7 +384,7 @@ static void menuDraw(Menu *menu, u32 selected) else Draw_DrawFormattedString(SCREEN_BOT_WIDTH - 10 - SPACING_X * 15, 10, COLOR_WHITE, "%15s", ""); - if(R_SUCCEEDED(mcuInfoRes)) + if(mcuInfoRes == 0) { u32 voltageInt = (u32)batteryVoltage; u32 voltageFrac = (u32)(batteryVoltage * 100.0f) % 100u; diff --git a/sysmodules/rosalina/source/menus.c b/sysmodules/rosalina/source/menus.c index 7471e4a..2d81543 100644 --- a/sysmodules/rosalina/source/menus.c +++ b/sysmodules/rosalina/source/menus.c @@ -41,6 +41,7 @@ #include "fmt.h" #include "process_patches.h" #include "luminance.h" +#include "luma_config.h" Menu rosalinaMenu = { "Rosalina menu", @@ -55,6 +56,7 @@ Menu rosalinaMenu = { { "Screen filters...", MENU, .menu = &screenFiltersMenu }, { "New 3DS menu...", MENU, .menu = &N3DSMenu, .visibility = &menuCheckN3ds }, { "Miscellaneous options...", MENU, .menu = &miscellaneousMenu }, + { "Save settings", METHOD, .method = &RosalinaMenu_SaveSettings }, { "Power off", METHOD, .method = &RosalinaMenu_PowerOff }, { "Reboot", METHOD, .method = &RosalinaMenu_Reboot }, { "Credits", METHOD, .method = &RosalinaMenu_ShowCredits }, @@ -72,6 +74,28 @@ bool rosalinaMenuShouldShowDebugInfo(void) return out == 0; } +void RosalinaMenu_SaveSettings(void) +{ + Result res = LumaConfig_SaveSettings(); + Draw_Lock(); + Draw_ClearFramebuffer(); + Draw_FlushFramebuffer(); + Draw_Unlock(); + + do + { + Draw_Lock(); + Draw_DrawString(10, 10, COLOR_TITLE, "Save settings"); + if(R_SUCCEEDED(res)) + Draw_DrawString(10, 30, COLOR_WHITE, "Operation succeeded."); + else + Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Operation failed (0x%08lx).", res); + Draw_FlushFramebuffer(); + Draw_Unlock(); + } + while(!(waitInput() & KEY_B) && !menuShouldExit); +} + void RosalinaMenu_ShowDebugInfo(void) { Draw_Lock(); @@ -149,7 +173,7 @@ void RosalinaMenu_ShowCredits(void) Draw_Lock(); Draw_DrawString(10, 10, COLOR_TITLE, "Rosalina -- Luma3DS credits"); - u32 posY = Draw_DrawString(10, 30, COLOR_WHITE, "Luma3DS (c) 2016-2022 AuroraWright, TuxSH") + SPACING_Y; + u32 posY = Draw_DrawString(10, 30, COLOR_WHITE, "Luma3DS (c) 2016-2023 AuroraWright, TuxSH") + SPACING_Y; posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "3DSX loading code by fincs"); posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Networking code & basic GDB functionality by Stary"); diff --git a/sysmodules/rosalina/source/menus/miscellaneous.c b/sysmodules/rosalina/source/menus/miscellaneous.c index c2fc386..5ca1a48 100644 --- a/sysmodules/rosalina/source/menus/miscellaneous.c +++ b/sysmodules/rosalina/source/menus/miscellaneous.c @@ -26,6 +26,7 @@ #include <3ds.h> #include "menus/miscellaneous.h" +#include "luma_config.h" #include "input_redirection.h" #include "ntp.h" #include "memory.h" @@ -37,35 +38,7 @@ #include "pmdbgext.h" #include "plugin.h" #include "process_patches.h" -#include "screen_filters.h" -#include "config_template_ini.h" -#define CONFIG(a) (((cfg->config >> (a)) & 1) != 0) -#define MULTICONFIG(a) ((cfg->multiConfig >> (2 * (a))) & 3) -#define BOOTCONFIG(a, b) ((cfg->bootConfig >> (a)) & (b)) - -enum singleOptions -{ - AUTOBOOTEMU = 0, - USEEMUFIRM, - LOADEXTFIRMSANDMODULES, - PATCHGAMES, - PATCHVERSTRING, - SHOWGBABOOT, - PATCHUNITINFO, - DISABLEARM11EXCHANDLERS, - ENABLESAFEFIRMROSALINA, -}; - -enum multiOptions -{ - DEFAULTEMU = 0, - BRIGHTNESS, - SPLASH, - PIN, - NEWCPU, - AUTOBOOTMODE, -}; typedef struct DspFirmSegmentHeader { u32 offset; u32 loadAddrHalfwords; @@ -91,22 +64,6 @@ typedef struct DspFirm { u8 data[]; } DspFirm; -typedef struct CfgData { - u16 formatVersionMajor, formatVersionMinor; - - u32 config, multiConfig, bootConfig; - u32 splashDurationMsec; - - u64 hbldr3dsxTitleId; - u32 rosalinaMenuCombo; - u32 pluginLoaderFlags; - u16 screenFiltersCct; - s16 ntpTzOffetMinutes; - - u64 autobootTwlTitleId; - u8 autobootCtrAppmemtype; -} CfgData; - Menu miscellaneousMenu = { "Miscellaneous options menu", { @@ -116,12 +73,11 @@ Menu miscellaneousMenu = { { "Update time and date via NTP", METHOD, .method = &MiscellaneousMenu_UpdateTimeDateNtp }, { "Nullify user time offset", METHOD, .method = &MiscellaneousMenu_NullifyUserTimeOffset }, { "Dump DSP firmware", METHOD, .method = &MiscellaneousMenu_DumpDspFirm }, - { "Save settings", METHOD, .method = &MiscellaneousMenu_SaveSettings }, {}, } }; + int lastNtpTzOffset = 0; -bool saveSettingsRequest = false; static inline bool compareTids(u64 tidA, u64 tidB) { @@ -133,20 +89,19 @@ void MiscellaneousMenu_SwitchBoot3dsxTargetTitle(void) { Result res; char failureReason[64]; - u64 currentTid = Luma_SharedConfig->hbldr_3dsx_tid; + u64 currentTid = Luma_SharedConfig->selected_hbldr_3dsx_tid; u64 newTid = currentTid; + FS_ProgramInfo progInfo; + u32 pid; + u32 launchFlags; + res = PMDBG_GetCurrentAppInfo(&progInfo, &pid, &launchFlags); + bool appRunning = R_SUCCEEDED(res); + if(compareTids(currentTid, HBLDR_DEFAULT_3DSX_TID)) { - FS_ProgramInfo progInfo; - u32 pid; - u32 launchFlags; - res = PMDBG_GetCurrentAppInfo(&progInfo, &pid, &launchFlags); - if(R_SUCCEEDED(res)) - { + if(appRunning) newTid = progInfo.programId; - Luma_SharedConfig->hbldr_3dsx_tid = progInfo.programId; - } else { res = -1; @@ -159,7 +114,18 @@ void MiscellaneousMenu_SwitchBoot3dsxTargetTitle(void) newTid = HBLDR_DEFAULT_3DSX_TID; } - Luma_SharedConfig->hbldr_3dsx_tid = newTid; + Luma_SharedConfig->selected_hbldr_3dsx_tid = newTid; + + // Move "selected" field to "current" if no app is currently running. + // Otherwise, PM will do it on app exit. + // There's a small possibility of race condition but it shouldn't matter + // here. + // We need to do that to ensure that the ExHeader at init matches the ExHeader + // at termination at all times, otherwise the process refcounts of sysmodules + // get all messed up. + if (!appRunning) + Luma_SharedConfig->hbldr_3dsx_tid = newTid; + if (compareTids(newTid, HBLDR_DEFAULT_3DSX_TID)) miscellaneousMenu.items[0].title = "Switch the hb. title to the current app."; else @@ -185,35 +151,6 @@ void MiscellaneousMenu_SwitchBoot3dsxTargetTitle(void) while(!(waitInput() & KEY_B) && !menuShouldExit); } -static void MiscellaneousMenu_ConvertComboToString(char *out, u32 combo) -{ - static const char *keys[] = { - "A", "B", "Select", "Start", "Right", "Left", "Up", "Down", "R", "L", "X", "Y", - "?", "?", - "ZL", "ZR", - "?", "?", "?", "?", - "Touch", - "?", "?", "?", - "CStick Right", "CStick Left", "CStick Up", "CStick Down", - "CPad Right", "CPad Left", "CPad Up", "CPad Down", - }; - - char *outOrig = out; - out[0] = 0; - for(s32 i = 31; i >= 0; i--) - { - if(combo & (1 << i)) - { - strcpy(out, keys[i]); - out += strlen(keys[i]); - *out++ = '+'; - } - } - - if (out != outOrig) - out[-1] = 0; -} - void MiscellaneousMenu_ChangeMenuCombo(void) { char comboStrOrig[128], comboStr[128]; @@ -224,7 +161,7 @@ void MiscellaneousMenu_ChangeMenuCombo(void) Draw_FlushFramebuffer(); Draw_Unlock(); - MiscellaneousMenu_ConvertComboToString(comboStrOrig, menuCombo); + LumaConfig_ConvertComboToString(comboStrOrig, menuCombo); Draw_Lock(); Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu"); @@ -233,7 +170,7 @@ void MiscellaneousMenu_ChangeMenuCombo(void) posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Please enter the new combo:"); menuCombo = waitCombo(); - MiscellaneousMenu_ConvertComboToString(comboStr, menuCombo); + LumaConfig_ConvertComboToString(comboStr, menuCombo); do { @@ -251,178 +188,6 @@ void MiscellaneousMenu_ChangeMenuCombo(void) while(!(waitInput() & KEY_B) && !menuShouldExit); } -static size_t saveLumaIniConfigToStr(char *out, const CfgData *cfg) -{ - char lumaVerStr[64]; - char lumaRevSuffixStr[16]; - char rosalinaMenuComboStr[128]; - - const char *splashPosStr; - const char *n3dsCpuStr; - - s64 outInfo; - svcGetSystemInfo(&outInfo, 0x10000, 0); - u32 version = (u32)outInfo; - - svcGetSystemInfo(&outInfo, 0x10000, 1); - u32 commitHash = (u32)outInfo; - - svcGetSystemInfo(&outInfo, 0x10000, 0x200); - bool isRelease = (bool)outInfo; - - switch (MULTICONFIG(SPLASH)) { - default: case 0: splashPosStr = "off"; break; - case 1: splashPosStr = "before payloads"; break; - case 2: splashPosStr = "after payloads"; break; - } - - switch (MULTICONFIG(NEWCPU)) { - default: case 0: n3dsCpuStr = "off"; break; - case 1: n3dsCpuStr = "clock"; break; - case 2: n3dsCpuStr = "l2"; break; - case 3: n3dsCpuStr = "clock+l2"; break; - } - - if (GET_VERSION_REVISION(version) != 0) { - sprintf(lumaVerStr, "Luma3DS v%d.%d.%d", (int)GET_VERSION_MAJOR(version), (int)GET_VERSION_MINOR(version), (int)GET_VERSION_REVISION(version)); - } else { - sprintf(lumaVerStr, "Luma3DS v%d.%d", (int)GET_VERSION_MAJOR(version), (int)GET_VERSION_MINOR(version)); - } - - if (isRelease) { - strcpy(lumaRevSuffixStr, ""); - } else { - sprintf(lumaRevSuffixStr, "-%08lx", (u32)commitHash); - } - - MiscellaneousMenu_ConvertComboToString(rosalinaMenuComboStr, cfg->rosalinaMenuCombo); - - static const int pinOptionToDigits[] = { 0, 4, 6, 8 }; - int pinNumDigits = pinOptionToDigits[MULTICONFIG(PIN)]; - - int n = sprintf( - out, (const char *)config_template_ini, - lumaVerStr, lumaRevSuffixStr, - - (int)cfg->formatVersionMajor, (int)cfg->formatVersionMinor, - (int)CONFIG(AUTOBOOTEMU), (int)CONFIG(USEEMUFIRM), - (int)CONFIG(LOADEXTFIRMSANDMODULES), (int)CONFIG(PATCHGAMES), - (int)CONFIG(PATCHVERSTRING), (int)CONFIG(SHOWGBABOOT), - - 1 + (int)MULTICONFIG(DEFAULTEMU), 4 - (int)MULTICONFIG(BRIGHTNESS), - splashPosStr, (unsigned int)cfg->splashDurationMsec, - pinNumDigits, n3dsCpuStr, (int)MULTICONFIG(AUTOBOOTMODE), - - cfg->hbldr3dsxTitleId, rosalinaMenuComboStr, (int)(cfg->pluginLoaderFlags & 1), - (int)cfg->screenFiltersCct, (int)cfg->ntpTzOffetMinutes, - - cfg->autobootTwlTitleId, (int)cfg->autobootCtrAppmemtype, - - (int)CONFIG(PATCHUNITINFO), (int)CONFIG(DISABLEARM11EXCHANDLERS), - (int)CONFIG(ENABLESAFEFIRMROSALINA) - ); - - return n < 0 ? 0 : (size_t)n; -} - -void RequestSaveSettings(void) { - saveSettingsRequest = true; -} - -Result SaveSettings(void) -{ - char inibuf[0x2000]; - - Result res; - - IFile file; - u64 total; - - CfgData configData; - - u32 formatVersion; - u32 config, multiConfig, bootConfig; - u32 splashDurationMsec; - - u8 autobootCtrAppmemtype; - u64 autobootTwlTitleId; - - s64 out; - bool isSdMode; - - svcGetSystemInfo(&out, 0x10000, 2); - formatVersion = (u32)out; - svcGetSystemInfo(&out, 0x10000, 3); - config = (u32)out; - svcGetSystemInfo(&out, 0x10000, 4); - multiConfig = (u32)out; - svcGetSystemInfo(&out, 0x10000, 5); - bootConfig = (u32)out; - svcGetSystemInfo(&out, 0x10000, 6); - splashDurationMsec = (u32)out; - - svcGetSystemInfo(&out, 0x10000, 0x10); - autobootTwlTitleId = (u64)out; - svcGetSystemInfo(&out, 0x10000, 0x11); - autobootCtrAppmemtype = (u8)out; - - svcGetSystemInfo(&out, 0x10000, 0x203); - isSdMode = (bool)out; - - configData.formatVersionMajor = (u16)(formatVersion >> 16); - configData.formatVersionMinor = (u16)formatVersion; - configData.config = config; - configData.multiConfig = multiConfig; - configData.bootConfig = bootConfig; - configData.splashDurationMsec = splashDurationMsec; - configData.hbldr3dsxTitleId = Luma_SharedConfig->hbldr_3dsx_tid; - configData.rosalinaMenuCombo = menuCombo; - configData.pluginLoaderFlags = PluginLoader__IsEnabled(); - configData.screenFiltersCct = (u16)screenFiltersCurrentTemperature; - configData.ntpTzOffetMinutes = (s16)lastNtpTzOffset; - configData.autobootTwlTitleId = autobootTwlTitleId; - configData.autobootCtrAppmemtype = autobootCtrAppmemtype; - - size_t n = saveLumaIniConfigToStr(inibuf, &configData); - FS_ArchiveID archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW; - if (n > 0) - res = IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, "/luma/config.ini"), FS_OPEN_CREATE | FS_OPEN_WRITE); - else - res = -1; - - if(R_SUCCEEDED(res)) - res = IFile_SetSize(&file, n); - if(R_SUCCEEDED(res)) - res = IFile_Write(&file, &total, inibuf, n, 0); - IFile_Close(&file); - - IFile_Close(&file); - return res; -} - -void MiscellaneousMenu_SaveSettings(void) -{ - Result res = SaveSettings(); - - Draw_Lock(); - Draw_ClearFramebuffer(); - Draw_FlushFramebuffer(); - Draw_Unlock(); - - do - { - Draw_Lock(); - Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu"); - if(R_SUCCEEDED(res)) - Draw_DrawString(10, 30, COLOR_WHITE, "Operation succeeded."); - else - Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Operation failed (0x%08lx).", res); - Draw_FlushFramebuffer(); - Draw_Unlock(); - } - while(!(waitInput() & KEY_B) && !menuShouldExit); -} - void MiscellaneousMenu_InputRedirection(void) { bool done = false; @@ -671,10 +436,23 @@ static Result MiscellaneousMenu_DumpDspFirmCallback(Handle procHandle, u32 textS // Dump to SD card (no point in dumping to CTRNAND as 3dsx stuff doesn't work there) IFile file; - res = IFile_Open( - &file, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), - fsMakePath(PATH_ASCII, "/3ds/dspfirm.cdc"), FS_OPEN_CREATE | FS_OPEN_WRITE - ); + FS_Archive archive; + + // Create sdmc:/3ds directory if it doesn't exist yet + res = FSUSER_OpenArchive(&archive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")); + if(R_SUCCEEDED(res)) + { + res = FSUSER_CreateDirectory(archive, fsMakePath(PATH_ASCII, "/3ds"), 0); + if((u32)res == 0xC82044BE) // directory already exists + res = 0; + FSUSER_CloseArchive(archive); + } + + if (R_SUCCEEDED(res)) + res = IFile_Open( + &file, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), + fsMakePath(PATH_ASCII, "/3ds/dspfirm.cdc"), FS_OPEN_CREATE | FS_OPEN_WRITE + ); u64 total; if(R_SUCCEEDED(res)) diff --git a/sysmodules/rosalina/source/menus/process_list.c b/sysmodules/rosalina/source/menus/process_list.c index aceec77..cbe0801 100644 --- a/sysmodules/rosalina/source/menus/process_list.c +++ b/sysmodules/rosalina/source/menus/process_list.c @@ -58,6 +58,10 @@ static inline int ProcessListMenu_FormatInfoLine(char *out, const ProcessInfo *i const char *checkbox; GDBContext *ctx = NULL; + s64 tmp; + svcGetSystemInfo(&tmp, 0x10000, 0x200); + bool isRelease = tmp != 0; + if(gdbServer.super.running) { GDB_LockAllContexts(&gdbServer); @@ -84,15 +88,13 @@ static inline int ProcessListMenu_FormatInfoLine(char *out, const ProcessInfo *i else if ((ctx->flags & GDB_FLAG_SELECTED) && (ctx->localPort >= GDB_PORT_BASE && ctx->localPort < GDB_PORT_BASE + MAX_DEBUG)) { checkbox = "(W) "; - sprintf(commentBuf, "Port: %hu", ctx->localPort); + sprintf(commentBuf, "Port: %hu ", ctx->localPort); } } - else + else if (!isRelease) { -#ifdef ROSALINA_PRINT_PROCESS_CREATION_TIME - sprintf(commentBuf, "%lums\n", info->creationTimeMs); -#endif + sprintf(commentBuf, "%lums \n", info->creationTimeMs); } if (gdbServer.super.running) diff --git a/sysmodules/rosalina/source/menus/screen_filters.c b/sysmodules/rosalina/source/menus/screen_filters.c index 84c00ca..d9b5fc1 100644 --- a/sysmodules/rosalina/source/menus/screen_filters.c +++ b/sysmodules/rosalina/source/menus/screen_filters.c @@ -42,60 +42,97 @@ typedef union { u32 raw; } Pixel; -int screenFiltersCurrentTemperature = 6500; -float screenFiltersCurrentGamma = 1.0f; -float screenFiltersCurrentContrast = 1.0f; -float screenFiltersCurrentBrightness = 0.0f; +ScreenFilter topScreenFilter; +ScreenFilter bottomScreenFilter; static inline bool ScreenFiltersMenu_IsDefaultSettings(void) { - bool ret = screenFiltersCurrentTemperature == 6500; - ret = ret && screenFiltersCurrentGamma == 1.0f; - ret = ret && screenFiltersCurrentContrast == 1.0f; - ret = ret && screenFiltersCurrentBrightness == 0.0f; - return ret; + static const ScreenFilter defaultFilter = { 6500, false, 1.0f, 1.0f, 0.0f }; + bool ok1 = memcmp(&topScreenFilter, &defaultFilter, sizeof(defaultFilter)) == 0; + bool ok2 = memcmp(&bottomScreenFilter, &defaultFilter, sizeof(defaultFilter)) == 0; + return ok1 && ok2; } static inline u8 ScreenFiltersMenu_GetColorLevel(int inLevel, float wp, float a, float b, float g) { float level = inLevel / 255.0f; - level = powf(a * wp * level + b, g); + level = a * wp * level + b; + level = powf(CLAMP(level, 0.0f, 1.0f), g); s32 levelInt = (s32)(255.0f * level + 0.5f); // round to nearest integer return levelInt <= 0 ? 0 : levelInt >= 255 ? 255 : (u8)levelInt; // clamp } -static void ScreenFiltersMenu_ApplyColorSettings(void) +static u8 ScreenFilterMenu_CalculatePolynomialColorLutComponent(const float coeffs[][3], u32 component, float gamma, u32 dim, int inLevel) { - GPU_FB_TOP_COL_LUT_INDEX = 0; - GPU_FB_BOTTOM_COL_LUT_INDEX = 0; + float x = inLevel / 255.0f; + float level = 0.0f; + float xN = 1.0f; - float wp[3]; - colorramp_get_white_point(wp, screenFiltersCurrentTemperature); - float a = screenFiltersCurrentContrast; - float g = screenFiltersCurrentGamma; - float b = screenFiltersCurrentBrightness; + // Compute a_n * x^n + ... a_0, then clamp, then exponentiate by "gamma" and clamp again + for (u32 i = 0; i < dim + 1; i++) + { + level += coeffs[i][component] * xN; + xN *= x; + } + + level = powf(CLAMP(level, 0.0f, 1.0f), gamma); + s32 levelInt = (s32)(255.0f * level + 0.5f); // round up + return (u8)CLAMP(levelInt, 0, 255); // clamp again just to be sure +} + +static void ScreenFilterMenu_WritePolynomialColorLut(bool top, const float coeffs[][3], bool invert, float gamma, u32 dim) +{ + if (top) + GPU_FB_TOP_COL_LUT_INDEX = 0; + else + GPU_FB_BOTTOM_COL_LUT_INDEX = 0; for (int i = 0; i <= 255; i++) { Pixel px; - px.r = ScreenFiltersMenu_GetColorLevel(i, wp[0], a, b, g); - px.g = ScreenFiltersMenu_GetColorLevel(i, wp[1], a, b, g); - px.b = ScreenFiltersMenu_GetColorLevel(i, wp[2], a, b, g); + int inLevel = invert ? 255 - i : i; + px.r = ScreenFilterMenu_CalculatePolynomialColorLutComponent(coeffs, 0, gamma, dim, inLevel); + px.g = ScreenFilterMenu_CalculatePolynomialColorLutComponent(coeffs, 1, gamma, dim, inLevel); + px.b = ScreenFilterMenu_CalculatePolynomialColorLutComponent(coeffs, 2, gamma, dim, inLevel); px.z = 0; - GPU_FB_TOP_COL_LUT_ELEM = px.raw; - GPU_FB_BOTTOM_COL_LUT_ELEM = px.raw; + + if (top) + GPU_FB_TOP_COL_LUT_ELEM = px.raw; + else + GPU_FB_BOTTOM_COL_LUT_ELEM = px.raw; } } -void ScreenFiltersMenu_SetCct(int cct) +static void ScreenFiltersMenu_ApplyColorSettings(bool top) { - screenFiltersCurrentTemperature = cct; - ScreenFiltersMenu_ApplyColorSettings(); + const ScreenFilter *filter = top ? &topScreenFilter : &bottomScreenFilter; + + float wp[3]; + colorramp_get_white_point(wp, filter->cct); + float a = filter->contrast; + float b = filter->brightness; + float g = filter->gamma; + bool inv = filter->invert; + + float poly[][3] = { + { b, b, b }, // x^0 + { a * wp[0], a * wp[1], a * wp[2] }, // x^1 + }; + + ScreenFilterMenu_WritePolynomialColorLut(top, poly, inv, g, 1); +} + +static void ScreenFiltersMenu_SetCct(u16 cct) +{ + topScreenFilter.cct = cct; + bottomScreenFilter.cct = cct; + ScreenFiltersMenu_ApplyColorSettings(true); + ScreenFiltersMenu_ApplyColorSettings(false); } Menu screenFiltersMenu = { "Screen filters menu", { - { "[6500K] Default", METHOD, .method = &ScreenFiltersMenu_SetDefault }, + { "[6500K] Default temperature", METHOD, .method = &ScreenFiltersMenu_SetDefault }, { "[10000K] Aquarium", METHOD, .method = &ScreenFiltersMenu_SetAquarium }, { "[7500K] Overcast Sky", METHOD, .method = &ScreenFiltersMenu_SetOvercastSky }, { "[5500K] Daylight", METHOD, .method = &ScreenFiltersMenu_SetDaylight }, @@ -105,6 +142,7 @@ Menu screenFiltersMenu = { { "[2300K] Warm Incandescent", METHOD, .method = &ScreenFiltersMenu_SetWarmIncandescent }, { "[1900K] Candle", METHOD, .method = &ScreenFiltersMenu_SetCandle }, { "[1200K] Ember", METHOD, .method = &ScreenFiltersMenu_SetEmber }, + { "Advanced configuration", METHOD, .method = &ScreenFiltersMenu_AdvancedConfiguration }, {}, } }; @@ -115,15 +153,80 @@ void ScreenFiltersMenu_Set##name(void)\ ScreenFiltersMenu_SetCct(temp);\ } -void ScreenFiltersMenu_RestoreCct(void) +void ScreenFiltersMenu_RestoreSettings(void) { + // Precondition: menu has not been entered + // Not initialized/default: return if (ScreenFiltersMenu_IsDefaultSettings()) return; // Wait for GSP to restore the CCT table svcSleepThread(20 * 1000 * 1000LL); - ScreenFiltersMenu_ApplyColorSettings(); + + // Pause GSP, then wait a bit to ensure GPU commands complete + // We need to ensure no GPU stuff is running when changing + // the gamma table (otherwise colors become and stay glitched). + svcKernelSetState(0x10000, 2); + svcSleepThread(5 * 1000 * 100LL); + + ScreenFiltersMenu_ApplyColorSettings(true); + ScreenFiltersMenu_ApplyColorSettings(false); + + // Unpause GSP + svcKernelSetState(0x10000, 2); + svcSleepThread(5 * 1000 * 100LL); +} + +void ScreenFiltersMenu_LoadConfig(void) +{ + s64 out = 0; + + svcGetSystemInfo(&out, 0x10000, 0x102); + topScreenFilter.cct = (u16)out; + if (topScreenFilter.cct < 1000 || topScreenFilter.cct > 25100) + topScreenFilter.cct = 6500; + + svcGetSystemInfo(&out, 0x10000, 0x104); + topScreenFilter.gamma = (float)(out / FLOAT_CONV_MULT); + if (topScreenFilter.gamma < 0.0f || topScreenFilter.gamma > 1411.0f) + topScreenFilter.gamma = 1.0f; + + svcGetSystemInfo(&out, 0x10000, 0x105); + topScreenFilter.contrast = (float)(out / FLOAT_CONV_MULT); + if (topScreenFilter.contrast < 0.0f || topScreenFilter.contrast > 255.0f) + topScreenFilter.contrast = 1.0f; + + svcGetSystemInfo(&out, 0x10000, 0x106); + topScreenFilter.brightness = (float)(out / FLOAT_CONV_MULT); + if (topScreenFilter.brightness < -1.0f || topScreenFilter.brightness > 1.0f) + topScreenFilter.brightness = 0.0f; + + svcGetSystemInfo(&out, 0x10000, 0x107); + topScreenFilter.invert = (bool)out; + + svcGetSystemInfo(&out, 0x10000, 0x108); + bottomScreenFilter.cct = (u16)out; + if (bottomScreenFilter.cct < 1000 || bottomScreenFilter.cct > 25100) + bottomScreenFilter.cct = 6500; + + svcGetSystemInfo(&out, 0x10000, 0x109); + bottomScreenFilter.gamma = (float)(out / FLOAT_CONV_MULT); + if (bottomScreenFilter.gamma < 0.0f || bottomScreenFilter.gamma > 1411.0f) + bottomScreenFilter.gamma = 1.0f; + + svcGetSystemInfo(&out, 0x10000, 0x10A); + bottomScreenFilter.contrast = (float)(out / FLOAT_CONV_MULT); + if (bottomScreenFilter.contrast < 0.0f || bottomScreenFilter.contrast > 255.0f) + bottomScreenFilter.contrast = 1.0f; + + svcGetSystemInfo(&out, 0x10000, 0x10B); + bottomScreenFilter.brightness = (float)(out / FLOAT_CONV_MULT); + if (bottomScreenFilter.brightness < -1.0f || bottomScreenFilter.brightness > 1.0f) + bottomScreenFilter.brightness = 0.0f; + + svcGetSystemInfo(&out, 0x10000, 0x10C); + bottomScreenFilter.invert = (bool)out; } DEF_CCT_SETTER(6500, Default) @@ -137,3 +240,123 @@ DEF_CCT_SETTER(2700, Incandescent) DEF_CCT_SETTER(2300, WarmIncandescent) DEF_CCT_SETTER(1900, Candle) DEF_CCT_SETTER(1200, Ember) + +static void ScreenFiltersMenu_ClampFilter(ScreenFilter *filter) +{ + filter->cct = CLAMP(filter->cct, 1000, 25100); + filter->gamma = CLAMP(filter->gamma, 0.0f, 1411.0f); // ln(255) / ln(254/255): (254/255)^1411 <= 1/255 + filter->contrast = CLAMP(filter->contrast, 0.0f, 255.0f); + filter->brightness = CLAMP(filter->brightness, -1.0f, 1.0f); +} + +static void ScreenFiltersMenu_AdvancedConfigurationChangeValue(int pos, int mult, bool sync) +{ + ScreenFilter *filter = pos >= 5 ? &bottomScreenFilter : &topScreenFilter; + ScreenFilter *otherFilter = pos >= 5 ? &topScreenFilter : &bottomScreenFilter; + + int otherMult = sync ? mult : 0; + + switch (pos % 5) + { + case 0: + filter->cct += 100 * mult; + otherFilter->cct += 100 * otherMult; + break; + case 1: + filter->gamma += 0.01f * mult; + otherFilter->gamma += 0.01f * otherMult; + break; + case 2: + filter->contrast += 0.01f * mult; + otherFilter->contrast += 0.01f * otherMult; + break; + case 3: + filter->brightness += 0.01f * mult; + otherFilter->brightness += 0.01f * otherMult; + break; + case 4: + filter->invert = !filter->invert; + otherFilter->invert = sync ? !otherFilter->invert : otherFilter->invert; + break; + } + + // Clamp + ScreenFiltersMenu_ClampFilter(&topScreenFilter); + ScreenFiltersMenu_ClampFilter(&bottomScreenFilter); + + // Update the LUT + ScreenFiltersMenu_ApplyColorSettings(true); + ScreenFiltersMenu_ApplyColorSettings(false); +} + +static u32 ScreenFiltersMenu_AdvancedConfigurationHelper(const ScreenFilter *filter, int offset, int pos, u32 posY) +{ + char buf[64]; + + Draw_DrawCharacter(10, posY, COLOR_TITLE, pos == offset++ ? '>' : ' '); + posY = Draw_DrawFormattedString(30, posY, COLOR_WHITE, "Temperature: %12dK \n", filter->cct); + + floatToString(buf, filter->gamma, 2, true); + Draw_DrawCharacter(10, posY, COLOR_TITLE, pos == offset++ ? '>' : ' '); + posY = Draw_DrawFormattedString(30, posY, COLOR_WHITE, "Gamma: %13s \n", buf); + + floatToString(buf, filter->contrast, 2, true); + Draw_DrawCharacter(10, posY, COLOR_TITLE, pos == offset++ ? '>' : ' '); + posY = Draw_DrawFormattedString(30, posY, COLOR_WHITE, "Contrast: %13s \n", buf); + + floatToString(buf, filter->brightness, 2, true); + Draw_DrawCharacter(10, posY, COLOR_TITLE, pos == offset++ ? '>' : ' '); + posY = Draw_DrawFormattedString(30, posY, COLOR_WHITE, "Brightness: %13s \n", buf); + + Draw_DrawCharacter(10, posY, COLOR_TITLE, pos == offset++ ? '>' : ' '); + posY = Draw_DrawFormattedString(30, posY, COLOR_WHITE, "Invert: %13s \n", filter->invert ? "true" : "false"); + + return posY; +} + +void ScreenFiltersMenu_AdvancedConfiguration(void) +{ + u32 posY; + u32 input = 0; + u32 held = 0; + + int pos = 0; + int mult = 1; + + bool sync = true; + + do + { + Draw_Lock(); + Draw_DrawString(10, 10, COLOR_TITLE, "Screen filters menu"); + + posY = 30; + posY = Draw_DrawString(10, posY, COLOR_WHITE, "Use left/right to increase/decrease the sel. value.\n"); + posY = Draw_DrawString(10, posY, COLOR_WHITE, "Hold R to change the value faster.\n"); + posY = Draw_DrawFormattedString(10, posY, COLOR_WHITE, "Update both screens: %s (L to toggle) \n", sync ? "yes" : "no") + SPACING_Y; + + posY = Draw_DrawString(10, posY, COLOR_WHITE, "Top screen:\n"); + posY = ScreenFiltersMenu_AdvancedConfigurationHelper(&topScreenFilter, 0, pos, posY) + SPACING_Y; + + posY = Draw_DrawString(10, posY, COLOR_WHITE, "Bottom screen:\n"); + posY = ScreenFiltersMenu_AdvancedConfigurationHelper(&bottomScreenFilter, 5, pos, posY) + SPACING_Y; + + input = waitInputWithTimeoutEx(&held, -1); + mult = (held & KEY_R) ? 10 : 1; + + if (input & KEY_L) + sync = !sync; + if (input & KEY_LEFT) + ScreenFiltersMenu_AdvancedConfigurationChangeValue(pos, -mult, sync); + if (input & KEY_RIGHT) + ScreenFiltersMenu_AdvancedConfigurationChangeValue(pos, mult, sync); + if (input & KEY_UP) + pos = (10 + pos - 1) % 10; + if (input & KEY_DOWN) + pos = (pos + 1) % 10; + + Draw_FlushFramebuffer(); + Draw_Unlock(); + } + while(!(input & (KEY_A | KEY_B)) && !menuShouldExit); +} diff --git a/sysmodules/rosalina/source/plugin/plgloader.c b/sysmodules/rosalina/source/plugin/plgloader.c index a8f33c0..499fc4a 100644 --- a/sysmodules/rosalina/source/plugin/plgloader.c +++ b/sysmodules/rosalina/source/plugin/plgloader.c @@ -1,6 +1,7 @@ #include <3ds.h> #include "ifile.h" #include "utils.h" // for makeARMBranch +#include "luma_config.h" #include "plugin.h" #include "fmt.h" #include "menu.h" @@ -55,7 +56,7 @@ bool PluginLoader__IsEnabled(void) void PluginLoader__MenuCallback(void) { PluginLoaderCtx.isEnabled = !PluginLoaderCtx.isEnabled; - RequestSaveSettings(); + LumaConfig_RequestSaveSettings(); PluginLoader__UpdateMenu(); } @@ -163,7 +164,7 @@ void PluginLoader__HandleCommands(void *_ctx) if (cmdbuf[1] != ctx->isEnabled) { ctx->isEnabled = cmdbuf[1]; - RequestSaveSettings(); + LumaConfig_RequestSaveSettings(); PluginLoader__UpdateMenu(); } diff --git a/sysmodules/rosalina/source/shell.c b/sysmodules/rosalina/source/shell.c new file mode 100644 index 0000000..b04d702 --- /dev/null +++ b/sysmodules/rosalina/source/shell.c @@ -0,0 +1,82 @@ +/* +* This file is part of Luma3DS +* Copyright (C) 2023 Aurora Wright, TuxSH +* +* This program 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. +* +* This program 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. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#include <3ds.h> +#include "shell.h" +#include "utils.h" +#include "screen_filters.h" +#include "luma_config.h" + +static void forceHeadphoneOutput(void) +{ + // DSP/Codec sysmodule already have a way to force headphone output, + // but it's only for when the shell is closed (applied on shell close, + // cleared on shell opened); that mechanism is usually used by apps + // which have a "jukebox" feature (e.g Pokémon SMD). + + // This whole thing here is fragile and doesn't mesh well with the "codec" + // sysmodule. For example, inserting then removing HPs will undo what this + // function does. + + // TODO: stop opening and closing cdc:CHK (and mcu::HWC), which + // unecessarily spawns and despawns threads. + + // Wait for CSND to do its job + svcSleepThread(20 * 1000 * 1000LL); + + Handle *cdcChkHandlePtr = cdcChkGetSessionHandle(); + *cdcChkHandlePtr = 0; + + Result res = srvGetServiceHandle(cdcChkHandlePtr, "cdc:CHK"); + // Try to steal the handle if some other process is using the service (custom SVC) + if (R_FAILED(res)) + res = svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, cdcChkHandlePtr, "cdc:CHK"); + + if (R_FAILED(res)) + return; + + u8 reg = 0x30; // Enable override selection (always set), then select HP. + res = CDCCHK_WriteRegisters2(100, 69, ®, 1); + + svcCloseHandle(*cdcChkHandlePtr); +} + +void handleShellOpened(void) +{ + s64 out = 0; + svcGetSystemInfo(&out, 0x10000, 3); + u32 config = (u32)out; + + // We need to check here if GSP has done its init stuff, in particular + // clock and reset, otherwise we'll cause core1 to be in a waitstate + // forever (if we access a GPU reg while the GPU block's clock is off). + // (GSP does its init before registering its services) + if (isServiceUsable("gsp::Gpu")) + ScreenFiltersMenu_RestoreSettings(); + + if ((config & BIT(FORCEHEADPHONEOUTPUT)) != 0 && isServiceUsable("cdc:CHK")) + forceHeadphoneOutput(); +} diff --git a/sysmodules/rosalina/source/utils.c b/sysmodules/rosalina/source/utils.c index 237989c..fa42c48 100644 --- a/sysmodules/rosalina/source/utils.c +++ b/sysmodules/rosalina/source/utils.c @@ -28,6 +28,8 @@ #include "csvc.h" #include #include +#include +#include <3ds.h> void formatMemoryPermission(char *outbuf, MemPerm perm) { @@ -161,3 +163,41 @@ int dateTimeToString(char *out, u64 msSince1900, bool filenameFormat) else return sprintf(out, "%04lu-%02lu-%02lu %02lu:%02lu:%02lu", year, month, days, hours, minutes, seconds); } + +int floatToString(char *out, float f, u32 precision, bool pad) +{ + // Floating point stuff is cringe + if (isnanf(f)) + return sprintf(out, "NaN"); + else if (isinff(f) && f >= -0.0f) + return sprintf(out, "inf"); + else if (isinff(f)) + return sprintf(out, "-inf"); + + static const u64 pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + precision = precision >= 6 ? 6 : precision; // result inaccurate after 1e-6 + + u64 mult = pow10[precision]; + double f2 = fabs((double)f) * mult + 0.5; + const char *sign = f2 >= 0.0f ? "" : "-"; + u64 f3 = (u64)f2; + + u64 intPart = f3 / mult; + u64 fracPart = f3 % mult; + + if (pad) + return sprintf(out, "%s%llu.%0*llu", sign, intPart, (int)precision, fracPart); + else + { + int n = sprintf(out, "%s%llu", sign, intPart); + if (fracPart == 0) + return n; + + n += sprintf(out + n, ".%0*llu", (int)precision, fracPart); + + int n2 = n - 1; + while (out[n2] == '0') + out[n2--] = '\0'; + return n2; + } +}