diff --git a/arm9/data/config_template.ini b/arm9/data/config_template.ini
index 697b4fe..1dd2da7 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 bd7761f..ec5e039 100644
--- a/arm9/source/config.c
+++ b/arm9/source/config.c
@@ -65,6 +65,7 @@ static const char *singleOptionIniNamesBoot[] = {
"app_syscore_threads_on_core_2",
"show_system_settings_string",
"show_gba_boot_screen",
+ "force_headphone_output",
};
static const char *singleOptionIniNamesMisc[] = {
@@ -581,7 +582,7 @@ static size_t saveLumaIniConfigToStr(char *out)
(int)CONFIG(AUTOBOOTEMU), (int)CONFIG(USEEMUFIRM),
(int)CONFIG(LOADEXTFIRMSANDMODULES), (int)CONFIG(PATCHGAMES),
(int)CONFIG(REDIRECTAPPTHREADS), (int)CONFIG(PATCHVERSTRING),
- (int)CONFIG(SHOWGBABOOT),
+ (int)CONFIG(SHOWGBABOOT), (int)CONFIG(FORCEHEADPHONEOUTPUT),
1 + (int)MULTICONFIG(DEFAULTEMU), 4 - (int)MULTICONFIG(BRIGHTNESS),
splashPosStr, (unsigned int)cfg->splashDurationMsec,
@@ -759,6 +760,7 @@ void configMenu(bool oldPinStatus, u32 oldPinMode)
"( ) 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"
@@ -830,8 +832,8 @@ 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"
@@ -857,6 +859,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;
@@ -892,6 +903,7 @@ void configMenu(bool oldPinStatus, u32 oldPinMode)
{ .visible = ISN3DS },
{ .visible = true },
{ .visible = true },
+ { .visible = true },
};
//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 7e7dae0..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 4
+#define CONFIG_VERSIONMINOR 5
#define BOOTCFG_NAND BOOTCONFIG(0, 7)
#define BOOTCFG_FIRM BOOTCONFIG(3, 7)
@@ -62,6 +62,7 @@ enum singleOptions
REDIRECTAPPTHREADS,
PATCHVERSTRING,
SHOWGBABOOT,
+ FORCEHEADPHONEOUTPUT,
PATCHUNITINFO,
DISABLEARM11EXCHANDLERS,
ENABLESAFEFIRMROSALINA,
diff --git a/k11_extension/include/config.h b/k11_extension/include/config.h
index 07d12de..b367b38 100644
--- a/k11_extension/include/config.h
+++ b/k11_extension/include/config.h
@@ -33,6 +33,7 @@ enum singleOptions
REDIRECTAPPTHREADS,
PATCHVERSTRING,
SHOWGBABOOT,
+ FORCEHEADPHONEOUTPUT,
PATCHUNITINFO,
DISABLEARM11EXCHANDLERS,
ENABLESAFEFIRMROSALINA,
diff --git a/sysmodules/loader/source/patcher.h b/sysmodules/loader/source/patcher.h
index 86b32eb..9472431 100644
--- a/sysmodules/loader/source/patcher.h
+++ b/sysmodules/loader/source/patcher.h
@@ -36,6 +36,7 @@ enum singleOptions
REDIRECTAPPTHREADS,
PATCHVERSTRING,
SHOWGBABOOT,
+ FORCEHEADPHONEOUTPUT,
PATCHUNITINFO,
DISABLEARM11EXCHANDLERS,
ENABLESAFEFIRMROSALINA,
diff --git a/sysmodules/pm/source/luma.h b/sysmodules/pm/source/luma.h
index 54e6c9e..d73a610 100644
--- a/sysmodules/pm/source/luma.h
+++ b/sysmodules/pm/source/luma.h
@@ -30,6 +30,7 @@ enum singleOptions
REDIRECTAPPTHREADS,
PATCHVERSTRING,
SHOWGBABOOT,
+ FORCEHEADPHONEOUTPUT,
PATCHUNITINFO,
DISABLEARM11EXCHANDLERS,
ENABLESAFEFIRMROSALINA,
diff --git a/sysmodules/rosalina/data/config_template.ini b/sysmodules/rosalina/data/config_template.ini
index 697b4fe..1dd2da7 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
index 81ea50a..2a5a44e 100644
--- a/sysmodules/rosalina/include/luma_config.h
+++ b/sysmodules/rosalina/include/luma_config.h
@@ -39,6 +39,7 @@ enum singleOptions
REDIRECTAPPTHREADS,
PATCHVERSTRING,
SHOWGBABOOT,
+ FORCEHEADPHONEOUTPUT,
PATCHUNITINFO,
DISABLEARM11EXCHANDLERS,
ENABLESAFEFIRMROSALINA,
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/source/luma_config.c b/sysmodules/rosalina/source/luma_config.c
index 1cba607..a2cb809 100644
--- a/sysmodules/rosalina/source/luma_config.c
+++ b/sysmodules/rosalina/source/luma_config.c
@@ -150,7 +150,7 @@ static size_t LumaConfig_SaveLumaIniConfigToStr(char *out, const CfgData *cfg)
(int)CONFIG(AUTOBOOTEMU), (int)CONFIG(USEEMUFIRM),
(int)CONFIG(LOADEXTFIRMSANDMODULES), (int)CONFIG(PATCHGAMES),
(int)CONFIG(REDIRECTAPPTHREADS), (int)CONFIG(PATCHVERSTRING),
- (int)CONFIG(SHOWGBABOOT),
+ (int)CONFIG(SHOWGBABOOT), (int)CONFIG(FORCEHEADPHONEOUTPUT),
1 + (int)MULTICONFIG(DEFAULTEMU), 4 - (int)MULTICONFIG(BRIGHTNESS),
splashPosStr, (unsigned int)cfg->splashDurationMsec,
diff --git a/sysmodules/rosalina/source/main.c b/sysmodules/rosalina/source/main.c
index 6bd77cb..eb0bbef 100644
--- a/sysmodules/rosalina/source/main.c
+++ b/sysmodules/rosalina/source/main.c
@@ -40,6 +40,7 @@
#include "minisoc.h"
#include "draw.h"
#include "bootdiag.h"
+#include "shell.h"
#include "task_runner.h"
@@ -165,13 +166,7 @@ static void handleShellNotification(u32 notificationId)
// 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.
-
- // 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();
+ handleShellOpened();
menuShouldExit = false;
} else {
// Shell closed
diff --git a/sysmodules/rosalina/source/menu.c b/sysmodules/rosalina/source/menu.c
index 9ebc4a2..f3aafe0 100644
--- a/sysmodules/rosalina/source/menu.c
+++ b/sysmodules/rosalina/source/menu.c
@@ -36,6 +36,7 @@
#include "menus/cheats.h"
#include "minisoc.h"
#include "menus/screen_filters.h"
+#include "shell.h"
u32 menuCombo = 0;
bool isHidInitialized = false;
@@ -261,10 +262,11 @@ void menuThreadMain(void)
if(isN3DS)
N3DSMenu_UpdateStatus();
- while (!isServiceUsable("ac:u") || !isServiceUsable("hid:USER") || !isServiceUsable("gsp::Gpu"))
+ while (!isServiceUsable("ac:u") || !isServiceUsable("hid:USER") || !isServiceUsable("gsp::Gpu") || !isServiceUsable("cdc:CHK"))
svcSleepThread(250 * 1000 * 1000LL);
ScreenFiltersMenu_LoadConfig();
+ handleShellOpened();
hidInit(); // assume this doesn't fail
isHidInitialized = true;
diff --git a/sysmodules/rosalina/source/menus/screen_filters.c b/sysmodules/rosalina/source/menus/screen_filters.c
index f80d984..20aa629 100644
--- a/sysmodules/rosalina/source/menus/screen_filters.c
+++ b/sysmodules/rosalina/source/menus/screen_filters.c
@@ -227,8 +227,6 @@ void ScreenFiltersMenu_LoadConfig(void)
svcGetSystemInfo(&out, 0x10000, 0x10C);
bottomScreenFilter.invert = (bool)out;
-
- ScreenFiltersMenu_RestoreSettings();
}
DEF_CCT_SETTER(6500, Default)
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();
+}