2016-04-05 05:27:28 +02:00
/*
2016-07-05 16:05:53 +02:00
* This file is part of Luma3DS
2017-06-05 02:02:04 +02:00
* Copyright ( C ) 2016 - 2017 Aurora Wright , TuxSH
2016-07-05 16:05:53 +02:00
*
* 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 < http : //www.gnu.org/licenses/>.
*
2017-06-05 02:02:04 +02:00
* 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 .
2016-04-05 05:27:28 +02:00
*/
# include "config.h"
2016-08-27 16:00:15 +02:00
# include "memory.h"
# include "fs.h"
2016-04-05 05:27:28 +02:00
# include "utils.h"
2016-06-10 21:48:22 +02:00
# include "screen.h"
2016-04-05 05:27:28 +02:00
# include "draw.h"
# include "buttons.h"
2016-08-28 15:50:11 +02:00
# include "pin.h"
2016-04-05 05:27:28 +02:00
2016-10-10 01:34:53 +02:00
CfgData configData ;
2017-04-13 03:38:36 +02:00
ConfigurationStatus needConfig ;
2017-06-18 22:31:21 +02:00
static CfgData oldConfig ;
2016-10-10 01:34:53 +02:00
2016-10-08 01:46:41 +02:00
bool readConfig ( void )
2016-08-27 16:00:15 +02:00
{
2017-04-13 03:38:36 +02:00
bool ret ;
2016-10-12 02:28:08 +02:00
if ( fileRead ( & configData , CONFIG_FILE , sizeof ( CfgData ) ) ! = sizeof ( CfgData ) | |
2016-08-27 16:00:15 +02:00
memcmp ( configData . magic , " CONF " , 4 ) ! = 0 | |
configData . formatVersionMajor ! = CONFIG_VERSIONMAJOR | |
configData . formatVersionMinor ! = CONFIG_VERSIONMINOR )
{
2017-06-18 22:31:21 +02:00
memset ( & configData , 0 , sizeof ( CfgData ) ) ;
2016-11-15 19:29:48 +01:00
2017-04-13 03:38:36 +02:00
ret = false ;
2016-08-27 16:00:15 +02:00
}
2017-04-13 03:38:36 +02:00
else ret = true ;
2017-06-18 22:31:21 +02:00
oldConfig = configData ;
2016-08-27 16:00:15 +02:00
2017-04-13 03:38:36 +02:00
return ret ;
2016-08-27 16:00:15 +02:00
}
2017-06-06 02:12:18 +02:00
void writeConfig ( bool isConfigOptions )
2016-08-27 16:00:15 +02:00
{
2017-06-22 15:49:23 +02:00
//If the configuration is different from previously, overwrite it.
2017-06-18 22:31:21 +02:00
if ( needConfig ! = CREATE_CONFIGURATION & & ( ( isConfigOptions & & configData . config = = oldConfig . config & & configData . multiConfig = = oldConfig . multiConfig ) | |
2017-06-22 15:49:23 +02:00
( ! isConfigOptions & & configData . bootConfig = = oldConfig . bootConfig ) ) ) return ;
2016-11-15 19:29:48 +01:00
if ( needConfig = = CREATE_CONFIGURATION )
2016-08-27 16:00:15 +02:00
{
2016-11-15 19:29:48 +01:00
memcpy ( configData . magic , " CONF " , 4 ) ;
configData . formatVersionMajor = CONFIG_VERSIONMAJOR ;
configData . formatVersionMinor = CONFIG_VERSIONMINOR ;
2017-06-06 02:12:18 +02:00
needConfig = MODIFY_CONFIGURATION ;
2016-11-15 19:29:48 +01:00
}
2016-08-31 16:11:44 +02:00
2016-11-15 19:29:48 +01:00
if ( ! fileWrite ( & configData , CONFIG_FILE , sizeof ( CfgData ) ) )
error ( " Error writing the configuration file " ) ;
2016-08-27 16:00:15 +02:00
}
2017-05-20 05:09:48 +02:00
void configMenu ( bool oldPinStatus , u32 oldPinMode )
2016-04-05 05:27:28 +02:00
{
2017-08-16 18:02:35 +02:00
static const char * multiOptionsText [ ] = { " Default EmuNAND: 1( ) 2( ) 3( ) 4( ) " ,
" Screen brightness: 4( ) 3( ) 2( ) 1( ) " ,
" Splash: Off( ) Before( ) After( ) payloads " ,
" PIN lock: Off( ) 4( ) 6( ) 8( ) digits " ,
" New 3DS CPU: Off( ) Clock( ) L2( ) Clock+L2( ) " ,
} ;
static const char * singleOptionsText [ ] = { " ( ) Autoboot EmuNAND " ,
" ( ) Use EmuNAND FIRM if booting with R " ,
" ( ) Enable loading external FIRMs and modules " ,
" ( ) Enable game patching " ,
" ( ) Show NAND or user string in System Settings " ,
" ( ) Show GBA boot screen in patched AGB_FIRM " ,
" ( ) Patch ARM9 access " ,
" ( ) Set developer UNITINFO " ,
" ( ) Disable ARM11 exception handlers " ,
} ;
static const char * optionsDescription [ ] = { " Select the default EmuNAND. \n \n "
" It will be booted when no \n "
" directional pad buttons are pressed. " ,
" Select the screen brightness. " ,
" Enable splash screen support. \n \n "
" \t * 'Before payloads' displays it \n "
" before booting payloads \n "
" (intended for splashes that display \n "
" button hints). \n \n "
" \t * 'After payloads' displays it \n "
" afterwards. " ,
" Activate a PIN lock. \n \n "
" The PIN will be asked each time \n "
" Luma3DS boots. \n \n "
" 4, 6 or 8 digits can be selected. \n \n "
" The ABXY buttons and the directional \n "
" pad buttons can be used as keys. \n \n "
" A message can also be displayed \n "
" (refer to the wiki for instructions). " ,
" Select the New 3DS CPU mode. \n \n "
" This won't apply to \n "
" New 3DS exclusive/enhanced games. \n \n "
" 'Clock+L2' can cause issues with some \n "
" games. " ,
" If enabled, an EmuNAND \n "
" will be launched on boot. \n \n "
" Otherwise, SysNAND will. \n \n "
" Hold L on boot to switch NAND. \n \n "
" To use a different EmuNAND from the \n "
" default, hold a directional pad button \n "
" (Up/Right/Down/Left equal EmuNANDs \n "
" 1/2/3/4). " ,
" If enabled, when holding R on boot \n "
" SysNAND will be booted with an \n "
" EmuNAND FIRM. \n \n "
" Otherwise, an EmuNAND will be booted \n "
" with the SysNAND FIRM. \n \n "
" To use a different EmuNAND from the \n "
" default, hold a directional pad button \n "
" (Up/Right/Down/Left equal EmuNANDs \n "
" 1/2/3/4), also add A if you have \n "
" a matching payload. " ,
" Enable loading external FIRMs and \n "
" system modules. \n \n "
" This isn't needed in most cases. \n \n "
" Refer to the wiki for instructions. " ,
" Enable overriding the region and \n "
" language configuration and the usage \n "
" 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 "
" Refer to the wiki for instructions. " ,
" Enable showing the current NAND/FIRM: \n \n "
" \t * Sys = SysNAND \n "
" \t * Emu = EmuNAND 1 \n "
" \t * EmuX = EmuNAND X \n "
" \t * SysE = SysNAND with EmuNAND 1 FIRM \n "
" \t * SyEX = SysNAND with EmuNAND X FIRM \n "
" \t * EmuS = EmuNAND 1 with SysNAND FIRM \n "
" \t * EmXS = EmuNAND X with SysNAND FIRM \n \n "
" or a user-defined custom string in \n "
" System Settings. \n \n "
" Refer to the wiki for instructions. " ,
" Enable showing the GBA boot screen \n "
" when booting GBA games. " ,
" Disable ARM9 exheader access checks. \n \n "
" Only select this if you know what you \n "
" are doing! " ,
" 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! "
} ;
2016-09-13 23:01:38 +02:00
2016-04-12 23:18:07 +02:00
struct multiOption {
2016-09-13 14:54:14 +02:00
u32 posXs [ 4 ] ;
u32 posY ;
2016-04-05 05:27:28 +02:00
u32 enabled ;
2016-10-08 01:46:41 +02:00
bool visible ;
2016-04-12 23:18:07 +02:00
} multiOptions [ ] = {
2016-10-10 01:34:53 +02:00
{ . posXs = { 19 , 24 , 29 , 34 } , . visible = isSdMode } ,
2016-10-08 01:46:41 +02:00
{ . posXs = { 21 , 26 , 31 , 36 } , . visible = true } ,
{ . posXs = { 12 , 22 , 31 , 0 } , . visible = true } ,
{ . posXs = { 14 , 19 , 24 , 29 } , . visible = true } ,
2016-10-10 01:34:53 +02:00
{ . posXs = { 17 , 26 , 32 , 44 } , . visible = ISN3DS } ,
2016-10-08 01:46:41 +02:00
} ;
struct singleOption {
u32 posY ;
bool enabled ;
bool visible ;
} singleOptions [ ] = {
2016-10-10 01:34:53 +02:00
{ . visible = isSdMode } ,
{ . visible = isSdMode } ,
2016-10-08 01:46:41 +02:00
{ . visible = true } ,
2016-10-11 16:52:51 +02:00
{ . visible = true } ,
2016-10-08 01:46:41 +02:00
{ . visible = true } ,
{ . visible = true } ,
{ . visible = true } ,
2017-06-08 02:36:53 +02:00
{ . visible = true } ,
2017-05-18 01:05:56 +02:00
{ . visible = true }
2016-04-12 23:18:07 +02:00
} ;
//Calculate the amount of the various kinds of options and pre-select the first single one
u32 multiOptionsAmount = sizeof ( multiOptions ) / sizeof ( struct multiOption ) ,
2016-10-08 01:46:41 +02:00
singleOptionsAmount = sizeof ( singleOptions ) / sizeof ( struct singleOption ) ,
2016-04-13 15:19:35 +02:00
totalIndexes = multiOptionsAmount + singleOptionsAmount - 1 ,
2016-11-13 18:10:59 +01:00
selectedOption ,
singleSelected ;
2016-11-13 18:13:12 +01:00
bool isMultiOption = false ;
2016-04-05 05:27:28 +02:00
2016-04-12 23:18:07 +02:00
//Parse the existing options
for ( u32 i = 0 ; i < multiOptionsAmount ; i + + )
multiOptions [ i ] . enabled = MULTICONFIG ( i ) ;
for ( u32 i = 0 ; i < singleOptionsAmount ; i + + )
singleOptions [ i ] . enabled = CONFIG ( i ) ;
2016-04-05 23:01:46 +02:00
2016-09-12 18:58:10 +02:00
initScreens ( ) ;
2017-08-19 01:40:55 +02:00
u32 endPos = drawFormattedString ( true , 10 , 10 , COLOR_TITLE , " %s%s \n %s " , CONFIG_TITLE , isNTRCARDBoot ? " \n Booted from NTRCARD " : " " , " Press A to select, START to save " ) ;
endPos + = SPACING_Y ;
2016-09-12 18:58:10 +02:00
2016-04-05 23:01:46 +02:00
//Character to display a selected option
char selected = ' x ' ;
2016-04-13 00:33:03 +02:00
//Display all the multiple choice options in white
2016-04-12 23:18:07 +02:00
for ( u32 i = 0 ; i < multiOptionsAmount ; i + + )
2016-04-05 23:01:46 +02:00
{
2016-11-15 19:29:48 +01:00
if ( ! multiOptions [ i ] . visible ) continue ;
multiOptions [ i ] . posY = endPos + SPACING_Y ;
2017-05-07 17:58:44 +02:00
endPos = drawString ( true , 10 , multiOptions [ i ] . posY , COLOR_WHITE , multiOptionsText [ i ] ) ;
drawCharacter ( true , 10 + multiOptions [ i ] . posXs [ multiOptions [ i ] . enabled ] * SPACING_X , multiOptions [ i ] . posY , COLOR_WHITE , selected ) ;
2016-04-05 23:01:46 +02:00
}
2016-04-05 05:27:28 +02:00
2016-04-13 15:19:35 +02:00
endPos + = SPACING_Y / 2 ;
2016-04-05 05:27:28 +02:00
2016-04-13 00:33:03 +02:00
//Display all the normal options in white except for the first one
2016-10-13 00:52:18 +02:00
for ( u32 i = 0 , color = COLOR_RED ; i < singleOptionsAmount ; i + + )
2016-04-12 23:18:07 +02:00
{
2016-11-15 19:29:48 +01:00
if ( ! singleOptions [ i ] . visible ) continue ;
2016-10-13 00:52:18 +02:00
2016-11-15 19:29:48 +01:00
singleOptions [ i ] . posY = endPos + SPACING_Y ;
2017-05-07 17:58:44 +02:00
endPos = drawString ( true , 10 , singleOptions [ i ] . posY , color , singleOptionsText [ i ] ) ;
if ( singleOptions [ i ] . enabled ) drawCharacter ( true , 10 + SPACING_X , singleOptions [ i ] . posY , color , selected ) ;
2016-11-15 19:29:48 +01:00
if ( color = = COLOR_RED )
{
singleSelected = i ;
selectedOption = i + multiOptionsAmount ;
color = COLOR_WHITE ;
2016-10-08 01:46:41 +02:00
}
2016-04-12 23:18:07 +02:00
}
2016-04-05 05:27:28 +02:00
2017-05-07 17:58:44 +02:00
drawString ( false , 10 , 10 , COLOR_WHITE , optionsDescription [ selectedOption ] ) ;
2016-09-13 23:01:38 +02:00
2016-04-12 23:18:07 +02:00
//Boring configuration menu
2016-11-15 19:29:48 +01:00
while ( true )
2016-04-12 23:18:07 +02:00
{
2016-11-15 19:29:48 +01:00
u32 pressed ;
2016-04-13 15:19:35 +02:00
do
{
2016-11-13 22:13:24 +01:00
pressed = waitInput ( true ) ;
2016-04-05 05:27:28 +02:00
}
while ( ! ( pressed & MENU_BUTTONS ) ) ;
2016-11-15 19:29:48 +01:00
if ( pressed = = BUTTON_START ) break ;
2016-04-12 23:18:07 +02:00
if ( pressed ! = BUTTON_A )
2016-04-05 05:27:28 +02:00
{
2016-04-13 15:19:35 +02:00
//Remember the previously selected option
u32 oldSelectedOption = selectedOption ;
2016-10-08 01:46:41 +02:00
while ( true )
2016-04-12 23:18:07 +02:00
{
2016-10-08 01:46:41 +02:00
switch ( pressed )
{
case BUTTON_UP :
selectedOption = ! selectedOption ? totalIndexes : selectedOption - 1 ;
break ;
case BUTTON_DOWN :
selectedOption = selectedOption = = totalIndexes ? 0 : selectedOption + 1 ;
break ;
case BUTTON_LEFT :
pressed = BUTTON_DOWN ;
selectedOption = 0 ;
break ;
case BUTTON_RIGHT :
pressed = BUTTON_UP ;
selectedOption = totalIndexes ;
break ;
2016-11-13 22:31:00 +01:00
default :
break ;
2016-10-08 01:46:41 +02:00
}
if ( selectedOption < multiOptionsAmount )
{
2016-11-15 19:29:48 +01:00
if ( ! multiOptions [ selectedOption ] . visible ) continue ;
isMultiOption = true ;
break ;
2016-11-13 18:10:59 +01:00
}
else
{
singleSelected = selectedOption - multiOptionsAmount ;
2016-11-15 19:29:48 +01:00
if ( ! singleOptions [ singleSelected ] . visible ) continue ;
isMultiOption = false ;
break ;
2016-10-08 01:46:41 +02:00
}
2016-04-12 23:18:07 +02:00
}
2016-04-13 15:19:35 +02:00
if ( selectedOption = = oldSelectedOption ) continue ;
2016-04-13 01:08:13 +02:00
//The user moved to a different option, print the old option in white and the new one in red. Only print 'x's if necessary
2016-04-12 23:18:07 +02:00
if ( oldSelectedOption < multiOptionsAmount )
{
2017-05-07 17:58:44 +02:00
drawString ( true , 10 , multiOptions [ oldSelectedOption ] . posY , COLOR_WHITE , multiOptionsText [ oldSelectedOption ] ) ;
drawCharacter ( true , 10 + multiOptions [ oldSelectedOption ] . posXs [ multiOptions [ oldSelectedOption ] . enabled ] * SPACING_X , multiOptions [ oldSelectedOption ] . posY , COLOR_WHITE , selected ) ;
2016-04-12 23:18:07 +02:00
}
else
{
u32 singleOldSelected = oldSelectedOption - multiOptionsAmount ;
2017-05-07 17:58:44 +02:00
drawString ( true , 10 , singleOptions [ singleOldSelected ] . posY , COLOR_WHITE , singleOptionsText [ singleOldSelected ] ) ;
if ( singleOptions [ singleOldSelected ] . enabled ) drawCharacter ( true , 10 + SPACING_X , singleOptions [ singleOldSelected ] . posY , COLOR_WHITE , selected ) ;
2016-04-12 23:18:07 +02:00
}
2016-04-05 05:27:28 +02:00
2017-05-07 17:58:44 +02:00
if ( isMultiOption ) drawString ( true , 10 , multiOptions [ selectedOption ] . posY , COLOR_RED , multiOptionsText [ selectedOption ] ) ;
else drawString ( true , 10 , singleOptions [ singleSelected ] . posY , COLOR_RED , singleOptionsText [ singleSelected ] ) ;
2016-09-13 23:01:38 +02:00
2017-05-07 17:58:44 +02:00
drawString ( false , 10 , 10 , COLOR_BLACK , optionsDescription [ oldSelectedOption ] ) ;
drawString ( false , 10 , 10 , COLOR_WHITE , optionsDescription [ selectedOption ] ) ;
2016-04-12 23:18:07 +02:00
}
else
{
2016-04-13 15:19:35 +02:00
//The selected option's status changed, print the 'x's accordingly
2016-11-13 18:10:59 +01:00
if ( isMultiOption )
2016-04-12 23:18:07 +02:00
{
u32 oldEnabled = multiOptions [ selectedOption ] . enabled ;
2017-05-07 17:58:44 +02:00
drawCharacter ( true , 10 + multiOptions [ selectedOption ] . posXs [ oldEnabled ] * SPACING_X , multiOptions [ selectedOption ] . posY , COLOR_BLACK , selected ) ;
2016-05-28 16:13:22 +02:00
multiOptions [ selectedOption ] . enabled = ( oldEnabled = = 3 | | ! multiOptions [ selectedOption ] . posXs [ oldEnabled + 1 ] ) ? 0 : oldEnabled + 1 ;
2016-07-01 20:27:28 +02:00
2016-09-11 19:17:56 +02:00
if ( selectedOption = = BRIGHTNESS ) updateBrightness ( multiOptions [ BRIGHTNESS ] . enabled ) ;
2016-04-12 23:18:07 +02:00
}
else
{
2016-11-13 18:10:59 +01:00
bool oldEnabled = singleOptions [ singleSelected ] . enabled ;
singleOptions [ singleSelected ] . enabled = ! oldEnabled ;
2017-05-07 17:58:44 +02:00
if ( oldEnabled ) drawCharacter ( true , 10 + SPACING_X , singleOptions [ singleSelected ] . posY , COLOR_BLACK , selected ) ;
2016-04-12 23:18:07 +02:00
}
}
2016-04-13 15:19:35 +02:00
//In any case, if the current option is enabled (or a multiple choice option is selected) we must display a red 'x'
2017-05-07 17:58:44 +02:00
if ( isMultiOption ) drawCharacter ( true , 10 + multiOptions [ selectedOption ] . posXs [ multiOptions [ selectedOption ] . enabled ] * SPACING_X , multiOptions [ selectedOption ] . posY , COLOR_RED , selected ) ;
else if ( singleOptions [ singleSelected ] . enabled ) drawCharacter ( true , 10 + SPACING_X , singleOptions [ singleSelected ] . posY , COLOR_RED , selected ) ;
2016-04-05 05:27:28 +02:00
}
//Parse and write the new configuration
2017-06-18 22:31:21 +02:00
configData . multiConfig = 0 ;
2016-04-12 23:18:07 +02:00
for ( u32 i = 0 ; i < multiOptionsAmount ; i + + )
2017-06-18 22:31:21 +02:00
configData . multiConfig | = multiOptions [ i ] . enabled < < ( i * 2 ) ;
configData . config = 0 ;
2016-04-12 23:18:07 +02:00
for ( u32 i = 0 ; i < singleOptionsAmount ; i + + )
2017-06-18 22:31:21 +02:00
configData . config | = ( singleOptions [ i ] . enabled ? 1 : 0 ) < < i ;
2016-04-05 05:27:28 +02:00
2016-09-19 14:57:36 +02:00
u32 newPinMode = MULTICONFIG ( PIN ) ;
if ( newPinMode ! = 0 ) newPin ( oldPinStatus & & newPinMode = = oldPinMode , newPinMode ) ;
2016-10-12 02:28:08 +02:00
else if ( oldPinStatus ) fileDelete ( PIN_FILE ) ;
2016-08-28 15:50:11 +02:00
2017-06-06 02:12:18 +02:00
writeConfig ( true ) ;
2016-08-28 15:50:11 +02:00
while ( HID_PAD & PIN_BUTTONS ) ;
2016-11-29 20:11:30 +01:00
wait ( 2000ULL ) ;
2017-01-21 21:20:48 +01:00
}