diff --git a/.gitattributes b/.gitattributes index 280866b..71d51fa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ *.xml text eol=lf +*.ini text eol=lf diff --git a/arm9/Makefile b/arm9/Makefile index 272765a..843f8ad 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -162,10 +162,16 @@ memory.o strings.o: CFLAGS += -O3 patches.o config.o: CFLAGS += -DCONFIG_TITLE="\"$(APP_TITLE) $(REVISION) configuration\""\ -DVERSION_MAJOR="$(VERSION_MAJOR)" -DVERSION_MINOR="$(VERSION_MINOR)"\ -DVERSION_BUILD="$(VERSION_BUILD)" -DISRELEASE="$(IS_RELEASE)" -DCOMMIT_HASH="0x$(COMMIT)" +config.o ini.o: CFLAGS += -DINI_HANDLER_LINENO=1 -DINI_STOP_ON_FIRST_ERROR=1 #--------------------------------------------------------------------------------- # you need a rule like this for each extension you use as binary data #--------------------------------------------------------------------------------- %.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) +#--------------------------------------------------------------------------------- +%.ini.o %_ini.h: %.ini #--------------------------------------------------------------------------------- @echo $(notdir $<) @$(bin2o) diff --git a/arm9/data/config_template.ini b/arm9/data/config_template.ini new file mode 100644 index 0000000..2960701 Binary files /dev/null and b/arm9/data/config_template.ini differ diff --git a/arm9/source/config.c b/arm9/source/config.c index d5c8ea9..5d6770a 100644 --- a/arm9/source/config.c +++ b/arm9/source/config.c @@ -25,6 +25,7 @@ */ #include +#include #include "config.h" #include "memory.h" #include "fs.h" @@ -35,6 +36,9 @@ #include "buttons.h" #include "pin.h" #include "i2c.h" +#include "ini.h" + +#include "config_template_ini.h" // note that it has an extra NUL byte inserted #define MAKE_LUMA_VERSION_MCU(major, minor, build) (u16)(((major) & 0xFF) << 8 | ((minor) & 0x1F) << 5 | ((build) & 7)) @@ -45,6 +49,403 @@ static CfgData oldConfig; static CfgDataMcu configDataMcu; static_assert(sizeof(CfgDataMcu) > 0, "wrong data size"); +// INI parsing +// =========================================================== + +static const char *singleOptionIniNamesBoot[] = { + "autoboot_emunand", + "use_emunand_firm_if_r_pressed", + "enable_external_firm_and_modules", + "enable_game_patching", + "show_system_settings_string", + "show_gba_boot_screen", +}; + +static const char *singleOptionIniNamesMisc[] = { + "use_dev_unitinfo", + "disable_arm11_exception_handlers", + "enable_safe_firm_rosalina", +}; + +static const char *keyNames[] = { + "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", +}; + +static int parseBoolOption(bool *out, const char *val) +{ + *out = false; + if (strlen(val) != 1) { + return -1; + } + + if (val[0] == '0') { + return 0; + } else if (val[0] == '1') { + *out = true; + return 0; + } else { + return -1; + } +} + +static int parseDecIntOption(s64 *out, const char *val, s64 minval, s64 maxval) +{ + *out = 0; + size_t numDigits = strlen(val); + s64 res = 0; + size_t i = 0; + + s64 sign = 1; + if (numDigits >= 2) { + if (val[0] == '+') { + ++i; + } else if (val[0] == '-') { + sign = -1; + ++i; + } + } + + for (; i < numDigits; i++) { + u64 n = (u64)(val[i] - '0'); + if (n > 9) { + return -1; + } + + res = 10*res + n; + } + + res *= sign; + if (res <= maxval && res >= minval) { + *out = res; + return 0; + } else { + return -1; + } +} + +static int parseHexIntOption(u64 *out, const char *val, u64 minval, u64 maxval) +{ + *out = 0; + size_t numDigits = strlen(val); + u64 res = 0; + + for (size_t i = 0; i < numDigits; i++) { + char c = val[i]; + if ((u64)(c - '0') <= 9) { + res = 16*res + (u64)(c - '0'); + } else if ((u64)(c - 'a') <= 5) { + res = 16*res + (u64)(c - 'a' + 10); + } else if ((u64)(c - 'A') <= 5) { + res = 16*res + (u64)(c - 'A' + 10); + } else { + return -1; + } + } + + if (res <= maxval && res >= minval) { + *out = res; + return 0; + } else { + return -1; + } +} + +static int parseKeyComboOption(u32 *out, const char *val) +{ + const char *startpos = val; + const char *endpos; + + *out = 0; + u32 keyCombo = 0; + do { + // Copy the button name (note that 16 chars is longer than any of the key names) + char name[17]; + endpos = strchr(startpos, '+'); + size_t n = endpos == NULL ? 16 : endpos - startpos; + n = n > 16 ? 16 : n; + strncpy(name, startpos, n); + name[n] = '\0'; + + if (strcmp(name, "?") == 0) { + // Lol no, bail out + return -1; + } + + bool found = false; + for (size_t i = 0; i < sizeof(keyNames)/sizeof(keyNames[0]); i++) { + if (strcasecmp(keyNames[i], name) == 0) { + found = true; + keyCombo |= 1u << i; + } + } + + if (!found) { + return -1; + } + + if (endpos != NULL) { + startpos = endpos + 1; + } + } while(endpos != NULL && *startpos != '\0'); + + if (*startpos == '\0') { + // Trailing '+' + return -1; + } else { + *out = keyCombo; + return 0; + } +} + +static void menuComboToString(char *out, u32 combo) +{ + char *outOrig = out; + out[0] = 0; + for(int i = 31; i >= 0; i--) + { + if(combo & (1 << i)) + { + strcpy(out, keyNames[i]); + out += strlen(keyNames[i]); + *out++ = '+'; + } + } + + if (out != outOrig) + out[-1] = 0; +} + +static bool hasIniParseError = false; +static int iniParseErrorLine = 0; + +#define CHECK_PARSE_OPTION(res) do { if((res) < 0) { hasIniParseError = true; iniParseErrorLine = lineno; return 0; } } while(false) + +static int configIniHandler(void* user, const char* section, const char* name, const char* value, int lineno) +{ + CfgData *cfg = (CfgData *)user; + if (strcmp(section, "meta") == 0) { + if (strcmp(name, "config_version_major") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 0, 0xFFFF)); + cfg->formatVersionMajor = (u16)opt; + return 1; + } else if (strcmp(name, "config_version_minor") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 0, 0xFFFF)); + cfg->formatVersionMinor = (u16)opt; + return 1; + } else { + CHECK_PARSE_OPTION(-1); + } + } else if (strcmp(section, "boot") == 0) { + // Simple options displayed on the Luma3DS boot screen + for (size_t i = 0; i < sizeof(singleOptionIniNamesBoot)/sizeof(singleOptionIniNamesBoot[0]); i++) { + if (strcmp(name, singleOptionIniNamesBoot[i]) == 0) { + bool opt; + CHECK_PARSE_OPTION(parseBoolOption(&opt, value)); + cfg->config |= (u32)opt << i; + return 1; + } + } + + // Multi-choice options displayed on the Luma3DS boot screen + + if (strcmp(name, "default_emunand_number") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 1, 4)); + cfg->multiConfig |= (opt - 1) << (2 * (u32)DEFAULTEMU); + return 1; + } else if (strcmp(name, "brightness_level") == 0) { + s64 opt; + CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 1, 4)); + cfg->multiConfig |= (4 - opt) << (2 * (u32)BRIGHTNESS); + return 1; + } else if (strcmp(name, "splash_position") == 0) { + if (strcasecmp(value, "off") == 0) { + cfg->multiConfig |= 0 << (2 * (u32)SPLASH); + return 1; + } else if (strcasecmp(value, "before payloads") == 0) { + cfg->multiConfig |= 1 << (2 * (u32)SPLASH); + return 1; + } else if (strcasecmp(value, "after payloads") == 0) { + cfg->multiConfig |= 2 << (2 * (u32)SPLASH); + return 1; + } else { + CHECK_PARSE_OPTION(-1); + } + } else if (strcmp(name, "splash_duration_ms") == 0) { + // Not displayed in the menu anymore, but more configurable + s64 opt; + CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 0, 0xFFFFFFFFu)); + cfg->splashDurationMsec = (u32)opt; + return 1; + } + else if (strcmp(name, "pin_lock_num_digits") == 0) { + s64 opt; + u32 encodedOpt; + CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 0, 8)); + // Only allow for 0 (off), 4, 6 or 8 'digits' + switch (opt) { + case 0: encodedOpt = 0; break; + case 4: encodedOpt = 1; break; + case 6: encodedOpt = 2; break; + case 8: encodedOpt = 3; break; + default: { + CHECK_PARSE_OPTION(-1); + } + } + cfg->multiConfig |= encodedOpt << (2 * (u32)PIN); + return 1; + } else if (strcmp(name, "app_launch_new_3ds_cpu") == 0) { + if (strcasecmp(value, "off") == 0) { + cfg->multiConfig |= 0 << (2 * (u32)NEWCPU); + return 1; + } else if (strcasecmp(value, "clock") == 0) { + cfg->multiConfig |= 1 << (2 * (u32)NEWCPU); + return 1; + } else if (strcasecmp(value, "l2") == 0) { + cfg->multiConfig |= 2 << (2 * (u32)NEWCPU); + return 1; + } else if (strcasecmp(value, "clock+l2") == 0) { + cfg->multiConfig |= 3 << (2 * (u32)NEWCPU); + return 1; + } else { + CHECK_PARSE_OPTION(-1); + } + } + CHECK_PARSE_OPTION(-1); + } else if (strcmp(section, "rosalina") == 0) { + // Rosalina options + if (strcmp(name, "hbldr_3dsx_titleid") == 0) { + u64 opt; + CHECK_PARSE_OPTION(parseHexIntOption(&opt, value, 0, 0xFFFFFFFFFFFFFFFFull)); + cfg->hbldr3dsxTitleId = opt; + return 1; + } else if (strcmp(name, "rosalina_menu_combo") == 0) { + u32 opt; + CHECK_PARSE_OPTION(parseKeyComboOption(&opt, value)); + cfg->rosalinaMenuCombo = opt; + 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 if (strcmp(section, "misc") == 0) { + for (size_t i = 0; i < sizeof(singleOptionIniNamesMisc)/sizeof(singleOptionIniNamesMisc[0]); i++) { + if (strcmp(name, singleOptionIniNamesMisc[i]) == 0) { + bool opt; + CHECK_PARSE_OPTION(parseBoolOption(&opt, value)); + cfg->config |= (u32)opt << (i + (u32)PATCHUNITINFO); + return 1; + } + } + CHECK_PARSE_OPTION(-1); + } else { + CHECK_PARSE_OPTION(-1); + } +} + +static size_t saveLumaIniConfigToStr(char *out) +{ + const CfgData *cfg = &configData; + + char lumaVerStr[64]; + char lumaRevSuffixStr[16]; + char rosalinaMenuComboStr[128]; + + const char *splashPosStr; + const char *n3dsCpuStr; + + 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 (VERSION_BUILD != 0) { + sprintf(lumaVerStr, "Luma3DS v%d.%d.%d", (int)VERSION_MAJOR, (int)VERSION_MINOR, (int)VERSION_BUILD); + } else { + sprintf(lumaVerStr, "Luma3DS v%d.%d", (int)VERSION_MAJOR, (int)VERSION_MINOR); + } + + if (ISRELEASE) { + strcpy(lumaRevSuffixStr, ""); + } else { + sprintf(lumaRevSuffixStr, "-%08lx", (u32)COMMIT_HASH); + } + + menuComboToString(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)CONFIG_VERSIONMAJOR, (int)CONFIG_VERSIONMINOR, + (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, + + cfg->hbldr3dsxTitleId, rosalinaMenuComboStr, + (int)cfg->screenFiltersCct, (int)cfg->ntpTzOffetMinutes, + + (int)CONFIG(PATCHUNITINFO), (int)CONFIG(DISABLEARM11EXCHANDLERS), + (int)CONFIG(ENABLESAFEFIRMROSALINA) + ); + + return n < 0 ? 0 : (size_t)n; +} + +static char tmpIniBuffer[0x2000]; + +static bool readLumaIniConfig(void) +{ + u32 rd = fileRead(tmpIniBuffer, "config.ini", sizeof(tmpIniBuffer) - 1); + if (rd == 0) return false; + + tmpIniBuffer[rd] = '\0'; + + return ini_parse_string(tmpIniBuffer, &configIniHandler, &configData) >= 0 && !hasIniParseError; +} + +static bool writeLumaIniConfig(void) +{ + size_t n = saveLumaIniConfigToStr(tmpIniBuffer); + return n != 0 && fileWrite(tmpIniBuffer, "config.ini", n); +} + +// =========================================================== + static void writeConfigMcu(void) { u8 data[sizeof(CfgDataMcu)]; @@ -113,12 +514,19 @@ bool readConfig(void) if (!ret) return false; - if(fileRead(&configData, CONFIG_FILE, sizeof(CfgData)) != sizeof(CfgData) || - memcmp(configData.magic, "CONF", 4) != 0 || + ret = readLumaIniConfig(); + if(!ret || configData.formatVersionMajor != CONFIG_VERSIONMAJOR || configData.formatVersionMinor != CONFIG_VERSIONMINOR) { memset(&configData, 0, sizeof(CfgData)); + configData.formatVersionMajor = CONFIG_VERSIONMAJOR; + configData.formatVersionMinor = CONFIG_VERSIONMINOR; + configData.config |= 1 << PATCHGAMES; + configData.splashDurationMsec = 3000; + configData.hbldr3dsxTitleId = 0x000400000D921E00ull; + configData.rosalinaMenuCombo = 1u << 9 | 1u << 7 | 1u << 2; // L+Start+Select + configData.screenFiltersCct = 6500; // default temp, no-op ret = false; } else @@ -137,17 +545,11 @@ void writeConfig(bool isConfigOptions) (!isConfigOptions && configData.bootConfig == oldConfig.bootConfig))) return; if(needConfig == CREATE_CONFIGURATION) - { - memcpy(configData.magic, "CONF", 4); - configData.formatVersionMajor = CONFIG_VERSIONMAJOR; - configData.formatVersionMinor = CONFIG_VERSIONMINOR; - needConfig = MODIFY_CONFIGURATION; - } if (!isConfigOptions) writeConfigMcu(); - else if(!fileWrite(&configData, CONFIG_FILE, sizeof(CfgData))) + else if(!writeLumaIniConfig()) error("Error writing the configuration file"); } @@ -156,7 +558,6 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) static const char *multiOptionsText[] = { "Default EmuNAND: 1( ) 2( ) 3( ) 4( )", "Screen brightness: 4( ) 3( ) 2( ) 1( )", "Splash: Off( ) Before( ) After( ) payloads", - "Splash duration: 1( ) 3( ) 5( ) 7( ) seconds", "PIN lock: Off( ) 4( ) 6( ) 8( ) digits", "New 3DS CPU: Off( ) Clock( ) L2( ) Clock+L2( )", }; @@ -167,9 +568,6 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) "( ) Enable game patching", "( ) Show NAND or user string in System Settings", "( ) Show GBA boot screen in patched AGB_FIRM", - "( ) Set developer UNITINFO", - "( ) Disable Arm11 exception handlers", - "( ) Enable Rosalina on SAFE_FIRM", }; static const char *optionsDescription[] = { "Select the default EmuNAND.\n\n" @@ -184,12 +582,9 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) "(intended for splashes that display\n" "button hints).\n\n" "\t* 'After payloads' displays it\n" - "afterwards.", - - "Select how long the splash screen\n" - "displays.\n\n" - "This has no effect if the splash\n" - "screen is not enabled.", + "afterwards.\n\n" + "Edit the duration in config.ini (3s\n" + "default).", "Activate a PIN lock.\n\n" "The PIN will be asked each time\n" @@ -254,30 +649,6 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) "Enable showing the GBA boot screen\n" "when booting GBA games.", - - "Make the console be always detected\n" - "as a development unit, and conversely.\n" - "(which breaks online features, amiibo\n" - "and retail CIAs, but allows installing\n" - "and booting some developer software).\n\n" - "Only select this if you know what you\n" - "are doing!", - - "Disables the fatal error exception\n" - "handlers for the Arm11 CPU.\n\n" - "Note: Disabling the exception handlers\n" - "will disqualify you from submitting\n" - "issues or bug reports to the Luma3DS\n" - "GitHub repository!", - - "Enables Rosalina, the kernel ext.\n" - "and sysmodule reimplementations on\n" - "SAFE_FIRM (New 3DS only).\n\n" - "Also suppresses QTM error 0xF96183FE,\n" - "allowing to use 8.1-11.3 N3DS on\n" - "New 2DS XL consoles.\n\n" - "Only select this if you know what you\n" - "are doing!", }; FirmwareSource nandType = FIRMWARE_SYSNAND; @@ -295,7 +666,6 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) } multiOptions[] = { { .visible = nandType == FIRMWARE_EMUNAND }, { .visible = true }, - { .visible = true }, { .visible = true }, { .visible = true }, { .visible = ISN3DS }, @@ -312,9 +682,6 @@ void configMenu(bool oldPinStatus, u32 oldPinMode) { .visible = true }, { .visible = true }, { .visible = true }, - { .visible = true }, - { .visible = true }, - { .visible = ISN3DS }, }; //Calculate the amount of the various kinds of options and pre-select the first single one diff --git a/arm9/source/config.h b/arm9/source/config.h index 6b00bc1..9810465 100644 --- a/arm9/source/config.h +++ b/arm9/source/config.h @@ -33,8 +33,8 @@ #define BOOTCONFIG(a, b) ((configData.bootConfig >> (a)) & (b)) #define CONFIG_FILE "config.bin" -#define CONFIG_VERSIONMAJOR 2 -#define CONFIG_VERSIONMINOR 5 +#define CONFIG_VERSIONMAJOR 3 +#define CONFIG_VERSIONMINOR 0 #define BOOTCFG_NAND BOOTCONFIG(0, 7) #define BOOTCFG_FIRM BOOTCONFIG(3, 7) @@ -46,7 +46,6 @@ enum multiOptions DEFAULTEMU = 0, BRIGHTNESS, SPLASH, - SPLASH_DURATION, PIN, NEWCPU }; diff --git a/arm9/source/draw.c b/arm9/source/draw.c index 3ec6c1c..5e70081 100644 --- a/arm9/source/draw.c +++ b/arm9/source/draw.c @@ -58,8 +58,7 @@ bool loadSplash(void) swapFramebuffers(true); - u32 durationIndex = MULTICONFIG(SPLASH_DURATION); - wait(1000ULL + (durationIndex * 2000ULL)); + wait(configData.splashDurationMsec); return true; } diff --git a/arm9/source/fs.c b/arm9/source/fs.c index 30c4422..cf5f364 100644 --- a/arm9/source/fs.c +++ b/arm9/source/fs.c @@ -447,5 +447,9 @@ bool doLumaUpgradeProcess(void) // Try to backup essential files ok2 = backupEssentialFiles(); + // Clean up some of the old files + fileDelete("0:/luma/config.bin"); + fileDelete("1:/rw/luma/config.bin"); + return ok && ok2; } diff --git a/arm9/source/ini.c b/arm9/source/ini.c new file mode 100644 index 0000000..f8a3ea3 --- /dev/null +++ b/arm9/source/ini.c @@ -0,0 +1,298 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#if INI_CUSTOM_ALLOCATOR +#include +void* ini_malloc(size_t size); +void ini_free(void* ptr); +void* ini_realloc(void* ptr, size_t size); +#else +#include +#define ini_malloc malloc +#define ini_free free +#define ini_realloc realloc +#endif +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to NUL at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Similar to strncpy, but ensures dest (size bytes) is + NUL-terminated, and doesn't pad with NULs. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) + dest[i] = src[i]; + dest[i] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + int max_line = INI_MAX_LINE; +#else + char* line; + size_t max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC && !INI_USE_STACK + char* new_line; + size_t offset; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)ini_malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, (int)max_line, stream) != NULL) { +#if INI_ALLOW_REALLOC && !INI_USE_STACK + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = ini_realloc(line, max_line); + if (!new_line) { + ini_free(line); + return -2; + } + line = new_line; + if (reader(line + offset, (int)(max_line - offset), stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; +#if INI_CALL_HANDLER_ON_NEW_SECTION + if (!HANDLER(user, section, NULL, NULL) && !error) + error = lineno; +#endif + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ +#if INI_ALLOW_NO_VALUE + *end = '\0'; + name = rstrip(start); + if (!HANDLER(user, section, name, NULL) && !error) + error = lineno; +#else + error = lineno; +#endif + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + ini_free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} diff --git a/arm9/source/ini.h b/arm9/source/ini.h new file mode 100644 index 0000000..78015d1 --- /dev/null +++ b/arm9/source/ini.h @@ -0,0 +1,157 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef INI_H +#define INI_H + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See https://github.com/benhoyt/inih/issues/21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +#define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Nonzero to call the handler at the start of each new section (with + name and value NULL). Default is to only call the handler on + each name=value pair. */ +#ifndef INI_CALL_HANDLER_ON_NEW_SECTION +#define INI_CALL_HANDLER_ON_NEW_SECTION 0 +#endif + +/* Nonzero to allow a name without a value (no '=' or ':' on the line) and + call the handler with value NULL in this case. Default is to treat + no-value lines as an error. */ +#ifndef INI_ALLOW_NO_VALUE +#define INI_ALLOW_NO_VALUE 0 +#endif + +/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory + allocation functions (INI_USE_STACK must also be 0). These functions must + have the same signatures as malloc/free/realloc and behave in a similar + way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ +#ifndef INI_CUSTOM_ALLOCATOR +#define INI_CUSTOM_ALLOCATOR 0 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* INI_H */ diff --git a/arm9/source/patches.c b/arm9/source/patches.c index a22e85c..9a0fb75 100644 --- a/arm9/source/patches.c +++ b/arm9/source/patches.c @@ -129,8 +129,11 @@ u32 installK11Extension(u8 *pos, u32 size, bool needToInitSd, u32 baseK11VA, u32 u16 configFormatVersionMajor, configFormatVersionMinor; u32 config, multiConfig, bootConfig; + u32 splashDurationMsec; u64 hbldr3dsxTitleId; u32 rosalinaMenuCombo; + u16 screenFiltersCct; + s16 ntpTzOffetMinutes; } info; }; @@ -201,8 +204,11 @@ u32 installK11Extension(u8 *pos, u32 size, bool needToInitSd, u32 baseK11VA, u32 info->config = configData.config; info->multiConfig = configData.multiConfig; info->bootConfig = configData.bootConfig; + info->splashDurationMsec = configData.splashDurationMsec; info->hbldr3dsxTitleId = configData.hbldr3dsxTitleId; info->rosalinaMenuCombo = configData.rosalinaMenuCombo; + info->screenFiltersCct = configData.screenFiltersCct; + info->ntpTzOffetMinutes = configData.ntpTzOffetMinutes; info->versionMajor = VERSION_MAJOR; info->versionMinor = VERSION_MINOR; info->versionBuild = VERSION_BUILD; diff --git a/arm9/source/types.h b/arm9/source/types.h index cba2d10..e8ebd53 100644 --- a/arm9/source/types.h +++ b/arm9/source/types.h @@ -61,14 +61,16 @@ typedef volatile s64 vs64; #define ISN3DS (CFG11_SOCINFO & 2) #define ISDEVUNIT (CFG_UNITINFO != 0) -typedef struct __attribute__((packed, aligned(4))) -{ - char magic[4]; +typedef struct { u16 formatVersionMajor, formatVersionMinor; u32 config, multiConfig, bootConfig; + u32 splashDurationMsec; + u64 hbldr3dsxTitleId; u32 rosalinaMenuCombo; + u16 screenFiltersCct; + s16 ntpTzOffetMinutes; } CfgData; typedef struct diff --git a/k11_extension/include/config.h b/k11_extension/include/config.h index 1970796..a482de0 100644 --- a/k11_extension/include/config.h +++ b/k11_extension/include/config.h @@ -19,7 +19,6 @@ enum multiOptions DEFAULTEMU = 0, BRIGHTNESS, SPLASH, - SPLASH_DURATION, PIN, NEWCPU }; diff --git a/k11_extension/include/globals.h b/k11_extension/include/globals.h index 2a320bf..5272d04 100644 --- a/k11_extension/include/globals.h +++ b/k11_extension/include/globals.h @@ -127,8 +127,11 @@ typedef struct CfwInfo u16 configFormatVersionMajor, configFormatVersionMinor; u32 config, multiConfig, bootConfig; + u32 splashDurationMsec; u64 hbldr3dsxTitleId; u32 rosalinaMenuCombo; + u16 screenFiltersCct; + s16 ntpTzOffetMinutes; } CfwInfo; extern CfwInfo cfwInfo; diff --git a/k11_extension/include/svc/GetCFWInfo.h b/k11_extension/include/svc/GetCFWInfo.h deleted file mode 100644 index e695b2f..0000000 --- a/k11_extension/include/svc/GetCFWInfo.h +++ /dev/null @@ -1,35 +0,0 @@ -/* -* This file is part of Luma3DS -* Copyright (C) 2016-2020 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. -*/ - -#pragma once - -#include "utils.h" -#include "kernel.h" -#include "svc.h" -#include "globals.h" - -// DEPRECATED -Result GetCFWInfo(CfwInfo *out); diff --git a/k11_extension/source/svc.c b/k11_extension/source/svc.c index 3045794..84f32cb 100644 --- a/k11_extension/source/svc.c +++ b/k11_extension/source/svc.c @@ -32,7 +32,6 @@ #include "svc/GetSystemInfo.h" #include "svc/GetProcessInfo.h" #include "svc/GetThreadInfo.h" -#include "svc/GetCFWInfo.h" #include "svc/ConnectToPort.h" #include "svc/SendSyncRequest.h" #include "svc/Break.h" @@ -105,8 +104,6 @@ void *svcHook(u8 *pageEnd) return GetThreadInfoHookWrapper; case 0x2D: return ConnectToPortHookWrapper; - case 0x2E: - return GetCFWInfo; // DEPRECATED case 0x32: return SendSyncRequestHook; case 0x3C: diff --git a/k11_extension/source/svc/GetCFWInfo.c b/k11_extension/source/svc/GetCFWInfo.c deleted file mode 100644 index fcbb34b..0000000 --- a/k11_extension/source/svc/GetCFWInfo.c +++ /dev/null @@ -1,33 +0,0 @@ -/* -* This file is part of Luma3DS -* Copyright (C) 2016-2020 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/GetCFWInfo.h" - -// DEPRECATED -Result GetCFWInfo(CfwInfo *out) -{ - return kernelToUsrMemcpy8(out, &cfwInfo, 16) ? 0 : 0xE0E01BF5; -} diff --git a/k11_extension/source/svc/GetSystemInfo.c b/k11_extension/source/svc/GetSystemInfo.c index f06b662..f5f6bbf 100644 --- a/k11_extension/source/svc/GetSystemInfo.c +++ b/k11_extension/source/svc/GetSystemInfo.c @@ -57,6 +57,9 @@ Result GetSystemInfoHook(s64 *out, s32 type, s32 param) case 5: *out = cfwInfo.bootConfig; break; + case 6: + *out = cfwInfo.splashDurationMsec; + break; case 0x100: *out = (s64)cfwInfo.hbldr3dsxTitleId; @@ -64,6 +67,12 @@ Result GetSystemInfoHook(s64 *out, s32 type, s32 param) case 0x101: *out = cfwInfo.rosalinaMenuCombo; break; + case 0x102: + *out = cfwInfo.screenFiltersCct; + break; + case 0x103: + *out = (s64)cfwInfo.ntpTzOffetMinutes; + break; case 0x200: // isRelease *out = cfwInfo.flags & 1; diff --git a/sysmodules/loader/source/patcher.h b/sysmodules/loader/source/patcher.h index f024824..ac6c3bf 100644 --- a/sysmodules/loader/source/patcher.h +++ b/sysmodules/loader/source/patcher.h @@ -21,7 +21,6 @@ enum multiOptions DEFAULTEMU = 0, BRIGHTNESS, SPLASH, - SPLASH_DURATION, PIN, NEWCPU }; diff --git a/sysmodules/rosalina/Makefile b/sysmodules/rosalina/Makefile index b152056..016b11a 100644 --- a/sysmodules/rosalina/Makefile +++ b/sysmodules/rosalina/Makefile @@ -19,7 +19,7 @@ include $(DEVKITARM)/3ds_rules TARGET := $(notdir $(CURDIR)) BUILD := build SOURCES := source source/gdb source/menus source/redshift -DATA := source/gdb/xml +DATA := source/gdb/xml data INCLUDES := include include/gdb include/menus include/redshift #--------------------------------------------------------------------------------- @@ -144,6 +144,11 @@ $(OFILES_SRC) : $(HFILES_BIN) @$(bin2o) #--------------------------------------------------------------------------------- %.xml.o %_xml.h: %.xml +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) +#--------------------------------------------------------------------------------- +%.ini.o %_ini.h: %.ini #--------------------------------------------------------------------------------- @echo $(notdir $<) @$(bin2o) diff --git a/sysmodules/rosalina/data/config_template.ini b/sysmodules/rosalina/data/config_template.ini new file mode 100644 index 0000000..2960701 Binary files /dev/null and b/sysmodules/rosalina/data/config_template.ini differ diff --git a/sysmodules/rosalina/include/menus/miscellaneous.h b/sysmodules/rosalina/include/menus/miscellaneous.h index 4ecb914..c6fd1ff 100644 --- a/sysmodules/rosalina/include/menus/miscellaneous.h +++ b/sysmodules/rosalina/include/menus/miscellaneous.h @@ -30,6 +30,7 @@ #include "menu.h" extern Menu miscellaneousMenu; +extern int lastNtpTzOffset; void MiscellaneousMenu_SwitchBoot3dsxTargetTitle(void); void MiscellaneousMenu_ChangeMenuCombo(void); diff --git a/sysmodules/rosalina/include/menus/screen_filters.h b/sysmodules/rosalina/include/menus/screen_filters.h index d624cb4..8d35ee3 100644 --- a/sysmodules/rosalina/include/menus/screen_filters.h +++ b/sysmodules/rosalina/include/menus/screen_filters.h @@ -32,6 +32,7 @@ extern Menu screenFiltersMenu; extern int screenFiltersCurrentTemperature; +void ScreenFiltersMenu_SetCct(int cct); void ScreenFiltersMenu_RestoreCct(void); void ScreenFiltersMenu_SetDefault(void); // 6500K (default) diff --git a/sysmodules/rosalina/source/main.c b/sysmodules/rosalina/source/main.c index f8d2d16..d1d102e 100644 --- a/sysmodules/rosalina/source/main.c +++ b/sysmodules/rosalina/source/main.c @@ -80,7 +80,6 @@ void initSystem(void) mappableInit(0x10000000, 0x14000000); isN3DS = svcGetSystemInfo(&out, 0x10001, 0) == 0; - svcGetSystemInfo(&out, 0x10000, 0x100); Luma_SharedConfig->hbldr_3dsx_tid = out == 0 ? HBLDR_DEFAULT_3DSX_TID : (u64)out; Luma_SharedConfig->use_hbldr = true; @@ -88,6 +87,9 @@ void initSystem(void) svcGetSystemInfo(&out, 0x10000, 0x101); menuCombo = out == 0 ? DEFAULT_MENU_COMBO : (u32)out; + 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"; diff --git a/sysmodules/rosalina/source/menu.c b/sysmodules/rosalina/source/menu.c index 7e392f7..58e36f3 100644 --- a/sysmodules/rosalina/source/menu.c +++ b/sysmodules/rosalina/source/menu.c @@ -35,6 +35,7 @@ #include "menus/n3ds.h" #include "menus/cheats.h" #include "minisoc.h" +#include "menus/screen_filters.h" u32 menuCombo = 0; bool isHidInitialized = false; @@ -143,7 +144,7 @@ u32 waitCombo(void) } static MyThread menuThread; -static u8 ALIGN(8) menuThreadStack[0x1000]; +static u8 ALIGN(8) menuThreadStack[0x3000]; static float batteryPercentage; static float batteryVoltage; @@ -217,7 +218,7 @@ u32 menuCountItems(const Menu *menu) MyThread *menuCreateThread(void) { - if(R_FAILED(MyThread_Create(&menuThread, menuThreadMain, menuThreadStack, 0x1000, 52, CORE_SYSTEM))) + if(R_FAILED(MyThread_Create(&menuThread, menuThreadMain, menuThreadStack, 0x3000, 52, CORE_SYSTEM))) svcBreak(USERBREAK_PANIC); return &menuThread; } @@ -230,6 +231,14 @@ void menuThreadMain(void) while (!isServiceUsable("ac:u") || !isServiceUsable("hid:USER")) svcSleepThread(500 * 1000 * 1000LL); + s64 out; + svcGetSystemInfo(&out, 0x10000, 0x102); + screenFiltersCurrentTemperature = (int)(u32)out; + + // Careful about race conditions here + if (screenFiltersCurrentTemperature != 6500) + ScreenFiltersMenu_SetCct(screenFiltersCurrentTemperature); + hidInit(); // assume this doesn't fail isHidInitialized = true; diff --git a/sysmodules/rosalina/source/menus/miscellaneous.c b/sysmodules/rosalina/source/menus/miscellaneous.c index ce0e6e2..5c81a51 100644 --- a/sysmodules/rosalina/source/menus/miscellaneous.c +++ b/sysmodules/rosalina/source/menus/miscellaneous.c @@ -37,7 +37,34 @@ #include "ifile.h" #include "pmdbgext.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 +}; typedef struct DspFirmSegmentHeader { u32 offset; u32 loadAddrHalfwords; @@ -63,6 +90,18 @@ typedef struct DspFirm { u8 data[]; } DspFirm; +typedef struct CfgData { + u16 formatVersionMajor, formatVersionMinor; + + u32 config, multiConfig, bootConfig; + u32 splashDurationMsec; + + u64 hbldr3dsxTitleId; + u32 rosalinaMenuCombo; + u16 screenFiltersCct; + s16 ntpTzOffetMinutes; +} CfgData; + Menu miscellaneousMenu = { "Miscellaneous options menu", { @@ -76,6 +115,7 @@ Menu miscellaneousMenu = { {}, } }; +int lastNtpTzOffset = 0; void MiscellaneousMenu_SwitchBoot3dsxTargetTitle(void) { @@ -192,54 +232,130 @@ 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, + + cfg->hbldr3dsxTitleId, rosalinaMenuComboStr, + (int)cfg->screenFiltersCct, (int)cfg->ntpTzOffetMinutes, + + (int)CONFIG(PATCHUNITINFO), (int)CONFIG(DISABLEARM11EXCHANDLERS), + (int)CONFIG(ENABLESAFEFIRMROSALINA) + ); + + return n < 0 ? 0 : (size_t)n; +} + void MiscellaneousMenu_SaveSettings(void) { + char inibuf[0x2000]; + Result res; IFile file; u64 total; - struct PACKED ALIGN(4) - { - char magic[4]; - u16 formatVersionMajor, formatVersionMinor; - - u32 config, multiConfig, bootConfig; - u64 hbldr3dsxTitleId; - u32 rosalinaMenuCombo; - } configData; + CfgData configData; u32 formatVersion; u32 config, multiConfig, bootConfig; + u32 splashDurationMsec; + s64 out; bool isSdMode; - if(R_FAILED(svcGetSystemInfo(&out, 0x10000, 2))) svcBreak(USERBREAK_ASSERT); + svcGetSystemInfo(&out, 0x10000, 2); formatVersion = (u32)out; - if(R_FAILED(svcGetSystemInfo(&out, 0x10000, 3))) svcBreak(USERBREAK_ASSERT); + svcGetSystemInfo(&out, 0x10000, 3); config = (u32)out; - if(R_FAILED(svcGetSystemInfo(&out, 0x10000, 4))) svcBreak(USERBREAK_ASSERT); + svcGetSystemInfo(&out, 0x10000, 4); multiConfig = (u32)out; - if(R_FAILED(svcGetSystemInfo(&out, 0x10000, 5))) svcBreak(USERBREAK_ASSERT); + svcGetSystemInfo(&out, 0x10000, 5); bootConfig = (u32)out; - if(R_FAILED(svcGetSystemInfo(&out, 0x10000, 0x203))) svcBreak(USERBREAK_ASSERT); + svcGetSystemInfo(&out, 0x10000, 6); + splashDurationMsec = (u32)out; + svcGetSystemInfo(&out, 0x10000, 0x203); isSdMode = (bool)out; - memcpy(configData.magic, "CONF", 4); 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.screenFiltersCct = (u16)screenFiltersCurrentTemperature; + configData.ntpTzOffetMinutes = (s16)lastNtpTzOffset; + size_t n = saveLumaIniConfigToStr(inibuf, &configData); FS_ArchiveID archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW; - res = IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, "/luma/config.bin"), FS_OPEN_CREATE | FS_OPEN_WRITE); + 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, sizeof(configData)); + res = IFile_SetSize(&file, n); if(R_SUCCEEDED(res)) - res = IFile_Write(&file, &total, &configData, sizeof(configData), 0); + res = IFile_Write(&file, &total, inibuf, n, 0); IFile_Close(&file); Draw_Lock(); @@ -390,8 +506,9 @@ void MiscellaneousMenu_UpdateTimeDateNtp(void) res = srvIsServiceRegistered(&isSocURegistered, "soc:U"); cantStart = R_FAILED(res) || !isSocURegistered; - int utcOffset = 12; - int utcOffsetMinute = 0; + int dt = 12*60 + lastNtpTzOffset; + int utcOffset = dt / 60; + int utcOffsetMinute = dt%60; int absOffset; do { @@ -405,8 +522,8 @@ void MiscellaneousMenu_UpdateTimeDateNtp(void) input = waitInput(); - if(input & KEY_LEFT) utcOffset = (24 + utcOffset - 1) % 24; // ensure utcOffset >= 0 - if(input & KEY_RIGHT) utcOffset = (utcOffset + 1) % 24; + if(input & KEY_LEFT) utcOffset = (27 + utcOffset - 1) % 27; // ensure utcOffset >= 0 + if(input & KEY_RIGHT) utcOffset = (utcOffset + 1) % 27; if(input & KEY_UP) utcOffsetMinute = (utcOffsetMinute + 1) % 60; if(input & KEY_DOWN) utcOffsetMinute = (60 + utcOffsetMinute - 1) % 60; Draw_FlushFramebuffer(); @@ -418,6 +535,7 @@ void MiscellaneousMenu_UpdateTimeDateNtp(void) return; utcOffset -= 12; + lastNtpTzOffset = 60 * utcOffset + utcOffsetMinute; res = srvIsServiceRegistered(&isSocURegistered, "soc:U"); cantStart = R_FAILED(res) || !isSocURegistered; diff --git a/sysmodules/rosalina/source/menus/screen_filters.c b/sysmodules/rosalina/source/menus/screen_filters.c index 516bd9d..045fae3 100644 --- a/sysmodules/rosalina/source/menus/screen_filters.c +++ b/sysmodules/rosalina/source/menus/screen_filters.c @@ -45,7 +45,7 @@ typedef union { static u16 g_c[0x600]; static Pixel g_px[0x400]; -int screenFiltersCurrentTemperature = -1; +int screenFiltersCurrentTemperature = 6500; static void ScreenFiltersMenu_WriteLut(const Pixel* lut) { @@ -90,7 +90,7 @@ static void ScreenFiltersMenu_ApplyColorSettings(color_setting_t* cs) ScreenFiltersMenu_WriteLut(g_px); } -static void ScreenFiltersMenu_SetCct(int cct) +void ScreenFiltersMenu_SetCct(int cct) { color_setting_t cs; memset(&cs, 0, sizeof(cs)); @@ -102,6 +102,7 @@ static void ScreenFiltersMenu_SetCct(int cct) cs.brightness = 1.0F;*/ ScreenFiltersMenu_ApplyColorSettings(&cs); + screenFiltersCurrentTemperature = cct; } Menu screenFiltersMenu = { @@ -124,14 +125,13 @@ Menu screenFiltersMenu = { #define DEF_CCT_SETTER(temp, name)\ void ScreenFiltersMenu_Set##name(void)\ {\ - screenFiltersCurrentTemperature = temp;\ ScreenFiltersMenu_SetCct(temp);\ } void ScreenFiltersMenu_RestoreCct(void) { - // Not initialized: return - if (screenFiltersCurrentTemperature == -1) + // Not initialized/default: return + if (screenFiltersCurrentTemperature == 6500) return; // Wait for GSP to restore the CCT table