2017-06-05 02:02:04 +02:00
/*
* This file is part of Luma3DS
2022-03-13 18:00:00 +00:00
* Copyright ( C ) 2016 - 2021 Aurora Wright , TuxSH
2017-06-05 02:02:04 +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/>.
*
* 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 "menus/miscellaneous.h"
2023-02-05 16:34:37 +00:00
# include "luma_config.h"
2017-06-05 02:02:04 +02:00
# include "input_redirection.h"
2019-06-03 00:43:44 +02:00
# include "ntp.h"
2017-06-05 02:02:04 +02:00
# include "memory.h"
# include "draw.h"
# include "fmt.h"
2020-04-25 13:17:23 +01:00
# include "utils.h" // for makeArmBranch
2017-06-05 02:02:04 +02:00
# include "minisoc.h"
2017-06-18 22:31:21 +02:00
# include "ifile.h"
2019-03-30 18:12:54 +01:00
# include "pmdbgext.h"
2023-07-14 20:08:07 +02:00
# include "plugin.h"
2021-01-16 21:16:32 +00:00
# include "process_patches.h"
typedef struct DspFirmSegmentHeader {
u32 offset ;
u32 loadAddrHalfwords ;
u32 size ;
u8 _0x0C [ 3 ] ;
u8 memType ;
u8 hash [ 0x20 ] ;
} DspFirmSegmentHeader ;
typedef struct DspFirm {
u8 signature [ 0x100 ] ;
char magic [ 4 ] ;
u32 totalSize ; // no more than 0x10000
u16 layoutBitfield ;
u8 _0x10A [ 3 ] ;
u8 surroundSegmentMemType ;
u8 numSegments ; // no more than 10
u8 flags ;
u32 surroundSegmentLoadAddrHalfwords ;
u32 surroundSegmentSize ;
u8 _0x118 [ 8 ] ;
DspFirmSegmentHeader segmentHdrs [ 10 ] ;
u8 data [ ] ;
} DspFirm ;
2017-06-05 02:02:04 +02:00
Menu miscellaneousMenu = {
" Miscellaneous options menu " ,
{
{ " Switch the hb. title to the current app. " , METHOD , . method = & MiscellaneousMenu_SwitchBoot3dsxTargetTitle } ,
2019-06-03 00:43:44 +02:00
{ " Change the menu combo " , METHOD , . method = & MiscellaneousMenu_ChangeMenuCombo } ,
2017-06-05 02:02:04 +02:00
{ " Start InputRedirection " , METHOD , . method = & MiscellaneousMenu_InputRedirection } ,
2021-01-13 23:55:55 +00:00
{ " Update time and date via NTP " , METHOD , . method = & MiscellaneousMenu_UpdateTimeDateNtp } ,
{ " Nullify user time offset " , METHOD , . method = & MiscellaneousMenu_NullifyUserTimeOffset } ,
2021-01-16 21:16:32 +00:00
{ " Dump DSP firmware " , METHOD , . method = & MiscellaneousMenu_DumpDspFirm } ,
2020-05-17 16:42:44 +01:00
{ } ,
2017-06-05 02:02:04 +02:00
}
} ;
2023-02-05 16:34:37 +00:00
2022-03-28 20:30:02 +01:00
int lastNtpTzOffset = 0 ;
2017-06-05 02:02:04 +02:00
2023-01-07 01:58:11 +00:00
static inline bool compareTids ( u64 tidA , u64 tidB )
{
// Just like p9 clears them, ignore platform/N3DS bits
return ( ( tidA ^ tidB ) & ~ 0xF0000000ull ) = = 0 ;
}
2017-06-05 02:02:04 +02:00
void MiscellaneousMenu_SwitchBoot3dsxTargetTitle ( void )
{
Result res ;
char failureReason [ 64 ] ;
2023-02-08 16:14:59 +00:00
u64 currentTid = Luma_SharedConfig - > selected_hbldr_3dsx_tid ;
2023-01-07 01:58:11 +00:00
u64 newTid = currentTid ;
2017-06-05 02:02:04 +02:00
2023-02-08 16:14:59 +00:00
FS_ProgramInfo progInfo ;
u32 pid ;
u32 launchFlags ;
res = PMDBG_GetCurrentAppInfo ( & progInfo , & pid , & launchFlags ) ;
bool appRunning = R_SUCCEEDED ( res ) ;
2023-01-07 01:58:11 +00:00
if ( compareTids ( currentTid , HBLDR_DEFAULT_3DSX_TID ) )
2017-06-05 02:02:04 +02:00
{
2023-02-08 16:14:59 +00:00
if ( appRunning )
2023-01-07 01:58:11 +00:00
newTid = progInfo . programId ;
2017-06-05 02:02:04 +02:00
else
{
res = - 1 ;
strcpy ( failureReason , " no suitable process found " ) ;
}
}
else
{
res = 0 ;
2023-01-07 01:58:11 +00:00
newTid = HBLDR_DEFAULT_3DSX_TID ;
2017-06-05 02:02:04 +02:00
}
2023-02-08 16:14:59 +00:00
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 ;
2023-01-07 01:58:11 +00:00
if ( compareTids ( newTid , HBLDR_DEFAULT_3DSX_TID ) )
miscellaneousMenu . items [ 0 ] . title = " Switch the hb. title to the current app. " ;
else
miscellaneousMenu . items [ 0 ] . title = " Switch the hb. title to " HBLDR_DEFAULT_3DSX_TITLE_NAME ;
2017-06-05 02:02:04 +02:00
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 (%s). " , failureReason ) ;
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
}
2020-05-15 20:00:13 +01:00
while ( ! ( waitInput ( ) & KEY_B ) & & ! menuShouldExit ) ;
2017-06-05 02:02:04 +02:00
}
void MiscellaneousMenu_ChangeMenuCombo ( void )
{
2020-05-15 02:06:52 +01:00
char comboStrOrig [ 128 ] , comboStr [ 128 ] ;
2017-06-05 02:02:04 +02:00
u32 posY ;
Draw_Lock ( ) ;
Draw_ClearFramebuffer ( ) ;
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
2023-02-05 16:34:37 +00:00
LumaConfig_ConvertComboToString ( comboStrOrig , menuCombo ) ;
2017-06-05 02:02:04 +02:00
Draw_Lock ( ) ;
Draw_DrawString ( 10 , 10 , COLOR_TITLE , " Miscellaneous options menu " ) ;
posY = Draw_DrawFormattedString ( 10 , 30 , COLOR_WHITE , " The current menu combo is: %s " , comboStrOrig ) ;
posY = Draw_DrawString ( 10 , posY + SPACING_Y , COLOR_WHITE , " Please enter the new combo: " ) ;
2017-06-06 01:21:52 +02:00
menuCombo = waitCombo ( ) ;
2023-02-05 16:34:37 +00:00
LumaConfig_ConvertComboToString ( comboStr , menuCombo ) ;
2017-06-05 02:02:04 +02:00
do
{
Draw_Lock ( ) ;
Draw_DrawString ( 10 , 10 , COLOR_TITLE , " Miscellaneous options menu " ) ;
posY = Draw_DrawFormattedString ( 10 , 30 , COLOR_WHITE , " The current menu combo is: %s " , comboStrOrig ) ;
posY = Draw_DrawFormattedString ( 10 , posY + SPACING_Y , COLOR_WHITE , " Please enter the new combo: %s " , comboStr ) + SPACING_Y ;
2017-06-18 22:31:21 +02:00
2017-06-05 02:02:04 +02:00
posY = Draw_DrawString ( 10 , posY + SPACING_Y , COLOR_WHITE , " Successfully changed the menu combo. " ) ;
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
}
2020-05-15 20:00:13 +01:00
while ( ! ( waitInput ( ) & KEY_B ) & & ! menuShouldExit ) ;
2017-06-05 02:02:04 +02:00
}
void MiscellaneousMenu_InputRedirection ( void )
{
bool done = false ;
Result res ;
char buf [ 65 ] ;
bool wasEnabled = inputRedirectionEnabled ;
2017-06-06 21:04:13 +02:00
bool cantStart = false ;
2017-06-05 02:02:04 +02:00
if ( wasEnabled )
{
2020-04-28 01:31:29 +01:00
res = InputRedirection_Disable ( 5 * 1000 * 1000 * 1000LL ) ;
2017-06-05 02:02:04 +02:00
if ( res ! = 0 )
2018-05-24 00:55:38 +02:00
sprintf ( buf , " Failed to stop InputRedirection (0x%08lx). " , ( u32 ) res ) ;
2017-06-05 02:02:04 +02:00
else
2017-08-06 23:51:52 +03:00
miscellaneousMenu . items [ 2 ] . title = " Start InputRedirection " ;
2017-06-05 02:02:04 +02:00
}
else
{
2018-01-18 20:44:54 +01:00
s64 dummyInfo ;
bool isN3DS = svcGetSystemInfo ( & dummyInfo , 0x10001 , 0 ) = = 0 ;
bool isSocURegistered ;
2017-06-05 02:02:04 +02:00
2018-01-18 20:44:54 +01:00
res = srvIsServiceRegistered ( & isSocURegistered , " soc:U " ) ;
cantStart = R_FAILED ( res ) | | ! isSocURegistered ;
2017-06-05 02:02:04 +02:00
if ( ! cantStart & & isN3DS )
{
2018-01-18 20:44:54 +01:00
bool isIrRstRegistered ;
res = srvIsServiceRegistered ( & isIrRstRegistered , " ir:rst " ) ;
cantStart = R_FAILED ( res ) | | ! isIrRstRegistered ;
2017-06-05 02:02:04 +02:00
}
}
2017-06-18 22:31:21 +02:00
Draw_Lock ( ) ;
Draw_ClearFramebuffer ( ) ;
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
2017-06-05 02:02:04 +02:00
do
{
Draw_Lock ( ) ;
Draw_DrawString ( 10 , 10 , COLOR_TITLE , " Miscellaneous options menu " ) ;
if ( ! wasEnabled & & cantStart )
2018-01-18 20:44:54 +01:00
Draw_DrawString ( 10 , 30 , COLOR_WHITE , " Can't start the input redirection before the system \n has finished loading. " ) ;
2017-06-05 02:02:04 +02:00
else if ( ! wasEnabled )
{
Draw_DrawString ( 10 , 30 , COLOR_WHITE , " Starting InputRedirection... " ) ;
if ( ! done )
{
res = InputRedirection_DoOrUndoPatches ( ) ;
if ( R_SUCCEEDED ( res ) )
{
res = svcCreateEvent ( & inputRedirectionThreadStartedEvent , RESET_STICKY ) ;
if ( R_SUCCEEDED ( res ) )
{
2020-04-28 01:31:29 +01:00
inputRedirectionCreateThread ( ) ;
2017-06-05 02:02:04 +02:00
res = svcWaitSynchronization ( inputRedirectionThreadStartedEvent , 10 * 1000 * 1000 * 1000LL ) ;
if ( res = = 0 )
res = ( Result ) inputRedirectionStartResult ;
if ( res ! = 0 )
2020-04-27 21:58:40 +01:00
{
svcCloseHandle ( inputRedirectionThreadStartedEvent ) ;
2017-06-05 02:02:04 +02:00
InputRedirection_DoOrUndoPatches ( ) ;
2020-04-27 21:58:40 +01:00
inputRedirectionEnabled = false ;
}
inputRedirectionStartResult = 0 ;
2017-06-05 02:02:04 +02:00
}
}
if ( res ! = 0 )
2018-05-24 00:55:38 +02:00
sprintf ( buf , " Starting InputRedirection... failed (0x%08lx). " , ( u32 ) res ) ;
2017-06-05 02:02:04 +02:00
else
2017-08-06 23:51:52 +03:00
miscellaneousMenu . items [ 2 ] . title = " Stop InputRedirection " ;
2017-06-05 02:02:04 +02:00
done = true ;
}
if ( res = = 0 )
Draw_DrawString ( 10 , 30 , COLOR_WHITE , " Starting InputRedirection... OK. " ) ;
else
Draw_DrawString ( 10 , 30 , COLOR_WHITE , buf ) ;
}
else
{
if ( res = = 0 )
2020-07-15 22:24:08 +01:00
{
u32 posY = 30 ;
posY = Draw_DrawString ( 10 , posY , COLOR_WHITE , " InputRedirection stopped successfully. \n \n " ) ;
if ( isN3DS )
{
posY = Draw_DrawString (
10 ,
posY ,
COLOR_WHITE ,
" This might cause a key press to be repeated in \n "
" Home Menu for no reason. \n \n "
" Just pressing ZL/ZR on the console is enough to fix \n this. \n "
) ;
}
}
2017-06-05 02:02:04 +02:00
else
Draw_DrawString ( 10 , 30 , COLOR_WHITE , buf ) ;
}
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
}
2020-05-15 20:00:13 +01:00
while ( ! ( waitInput ( ) & KEY_B ) & & ! menuShouldExit ) ;
2017-06-05 02:02:04 +02:00
}
2019-06-03 00:43:44 +02:00
2021-01-13 23:55:55 +00:00
void MiscellaneousMenu_UpdateTimeDateNtp ( void )
2019-06-03 00:43:44 +02:00
{
u32 posY ;
u32 input = 0 ;
Result res ;
bool cantStart = false ;
bool isSocURegistered ;
2022-03-15 20:24:49 +00:00
u64 msSince1900 , samplingTick ;
2019-06-03 00:43:44 +02:00
res = srvIsServiceRegistered ( & isSocURegistered , " soc:U " ) ;
cantStart = R_FAILED ( res ) | | ! isSocURegistered ;
2022-03-28 20:30:02 +01:00
int dt = 12 * 60 + lastNtpTzOffset ;
int utcOffset = dt / 60 ;
int utcOffsetMinute = dt % 60 ;
2019-06-03 00:43:44 +02:00
int absOffset ;
2024-09-22 07:36:19 +08:00
Draw_Lock ( ) ;
Draw_ClearFramebuffer ( ) ;
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
2019-06-03 00:43:44 +02:00
do
{
Draw_Lock ( ) ;
Draw_DrawString ( 10 , 10 , COLOR_TITLE , " Miscellaneous options menu " ) ;
absOffset = utcOffset - 12 ;
absOffset = absOffset < 0 ? - absOffset : absOffset ;
2019-09-16 17:13:23 +09:30
posY = Draw_DrawFormattedString ( 10 , 30 , COLOR_WHITE , " Current UTC offset: %c%02d%02d " , utcOffset < 12 ? ' - ' : ' + ' , absOffset , utcOffsetMinute ) ;
posY = Draw_DrawFormattedString ( 10 , posY + SPACING_Y , COLOR_WHITE , " Use DPAD Left/Right to change hour offset. \n Use DPAD Up/Down to change minute offset. \n Press A when done. " ) + SPACING_Y ;
2019-06-03 00:43:44 +02:00
2024-09-22 07:36:19 +08:00
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
2019-06-03 00:43:44 +02:00
2024-09-22 07:36:19 +08:00
input = waitInput ( ) ;
2022-03-28 20:30:02 +01:00
if ( input & KEY_LEFT ) utcOffset = ( 27 + utcOffset - 1 ) % 27 ; // ensure utcOffset >= 0
if ( input & KEY_RIGHT ) utcOffset = ( utcOffset + 1 ) % 27 ;
2020-05-15 02:06:52 +01:00
if ( input & KEY_UP ) utcOffsetMinute = ( utcOffsetMinute + 1 ) % 60 ;
if ( input & KEY_DOWN ) utcOffsetMinute = ( 60 + utcOffsetMinute - 1 ) % 60 ;
2019-06-03 00:43:44 +02:00
}
2020-05-15 20:00:13 +01:00
while ( ! ( input & ( KEY_A | KEY_B ) ) & & ! menuShouldExit ) ;
2019-06-03 00:43:44 +02:00
2020-05-15 02:06:52 +01:00
if ( input & KEY_B )
2019-06-03 00:43:44 +02:00
return ;
utcOffset - = 12 ;
2022-03-28 20:30:02 +01:00
lastNtpTzOffset = 60 * utcOffset + utcOffsetMinute ;
2019-06-03 00:43:44 +02:00
res = srvIsServiceRegistered ( & isSocURegistered , " soc:U " ) ;
cantStart = R_FAILED ( res ) | | ! isSocURegistered ;
res = 0 ;
if ( ! cantStart )
{
2022-03-15 20:24:49 +00:00
res = ntpGetTimeStamp ( & msSince1900 , & samplingTick ) ;
2019-06-03 00:43:44 +02:00
if ( R_SUCCEEDED ( res ) )
{
2022-03-15 20:24:49 +00:00
msSince1900 + = 1000 * ( 3600 * utcOffset + 60 * utcOffsetMinute ) ;
res = ntpSetTimeDate ( msSince1900 , samplingTick ) ;
2019-06-03 00:43:44 +02:00
}
}
do
{
Draw_Lock ( ) ;
Draw_DrawString ( 10 , 10 , COLOR_TITLE , " Miscellaneous options menu " ) ;
absOffset = utcOffset ;
absOffset = absOffset < 0 ? - absOffset : absOffset ;
Draw_DrawFormattedString ( 10 , 30 , COLOR_WHITE , " Current UTC offset: %c%02d " , utcOffset < 0 ? ' - ' : ' + ' , absOffset ) ;
if ( cantStart )
2019-06-29 17:05:43 +02:00
Draw_DrawFormattedString ( 10 , posY + 2 * SPACING_Y , COLOR_WHITE , " Can't sync time/date before the system \n has finished loading. " ) + SPACING_Y ;
2019-06-03 00:43:44 +02:00
else if ( R_FAILED ( res ) )
2019-06-29 17:05:43 +02:00
Draw_DrawFormattedString ( 10 , posY + 2 * SPACING_Y , COLOR_WHITE , " Operation failed (%08lx). " , ( u32 ) res ) + SPACING_Y ;
2019-06-03 00:43:44 +02:00
else
2021-01-13 23:55:55 +00:00
Draw_DrawFormattedString ( 10 , posY + 2 * SPACING_Y , COLOR_WHITE , " Time/date updated successfully. " ) + SPACING_Y ;
2019-06-03 00:43:44 +02:00
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
}
2024-09-22 07:36:19 +08:00
while ( ! ( waitInput ( ) & KEY_B ) & & ! menuShouldExit ) ;
2020-04-18 00:29:37 +01:00
}
2021-01-13 23:55:55 +00:00
void MiscellaneousMenu_NullifyUserTimeOffset ( void )
{
Result res = ntpNullifyUserTimeOffset ( ) ;
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. \n \n Please reboot to finalize the changes. " ) ;
else
Draw_DrawFormattedString ( 10 , 30 , COLOR_WHITE , " Operation failed (0x%08lx). " , res ) ;
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
}
while ( ! ( waitInput ( ) & KEY_B ) & & ! menuShouldExit ) ;
}
2021-01-16 21:16:32 +00:00
static Result MiscellaneousMenu_DumpDspFirmCallback ( Handle procHandle , u32 textSz , u32 roSz , u32 rwSz )
{
( void ) procHandle ;
Result res = 0 ;
// NOTE: we suppose .text, .rodata, .data+.bss are contiguous & in that order
u32 rwStart = 0x00100000 + textSz + roSz ;
u32 rwEnd = rwStart + rwSz ;
// Locate the DSP firm (it's in .data, not .rodata, suprisingly)
u32 magic ;
memcpy ( & magic , " DSP1 " , 4 ) ;
const u32 * off = ( u32 * ) rwStart ;
for ( ; off < ( u32 * ) rwEnd & & * off ! = magic ; off + + ) ;
if ( off > = ( u32 * ) rwEnd | | off < ( u32 * ) ( rwStart + 0x100 ) )
return - 2 ;
// Do some sanity checks
const DspFirm * firm = ( const DspFirm * ) ( ( u32 ) off - 0x100 ) ;
if ( firm - > totalSize > 0x10000 | | firm - > numSegments > 10 )
return - 3 ;
if ( ( u32 ) firm + firm - > totalSize > = rwEnd )
return - 3 ;
// Dump to SD card (no point in dumping to CTRNAND as 3dsx stuff doesn't work there)
IFile file ;
2023-02-06 01:27:25 +00:00
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
) ;
2021-01-16 21:16:32 +00:00
u64 total ;
if ( R_SUCCEEDED ( res ) )
res = IFile_Write ( & file , & total , firm , firm - > totalSize , 0 ) ;
if ( R_SUCCEEDED ( res ) )
res = IFile_SetSize ( & file , firm - > totalSize ) ; // truncate accordingly
IFile_Close ( & file ) ;
return res ;
}
void MiscellaneousMenu_DumpDspFirm ( void )
{
Result res = OperateOnProcessByName ( " menu " , MiscellaneousMenu_DumpDspFirmCallback ) ;
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 , " DSP firm. successfully written to /3ds/dspfirm.cdc \n on the SD card. " ) ;
else
Draw_DrawFormattedString (
10 , 30 , COLOR_WHITE ,
" Operation failed (0x%08lx). \n \n Make sure that Home Menu is running and that your \n SD card is inserted. " ,
res
) ;
Draw_FlushFramebuffer ( ) ;
Draw_Unlock ( ) ;
}
while ( ! ( waitInput ( ) & KEY_B ) & & ! menuShouldExit ) ;
}