#include "gui/wxgui.h" #include "gui/GeneralSettings2.h" #include "gui/CemuApp.h" #include "gui/helpers/wxControlObject.h" #include "util/helpers/helpers.h" #include "Cafe/OS/libs/snd_core/ax.h" #include #include #include #include #include #include #include "config/CemuConfig.h" #include "config/NetworkSettings.h" #include "audio/IAudioAPI.h" #if BOOST_OS_WINDOWS #include "audio/DirectSoundAPI.h" #include "audio/XAudio27API.h" #endif #include "audio/CubebAPI.h" #include "audio/IAudioInputAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #if ENABLE_METAL #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #endif #include "Cafe/Account/Account.h" #include #include "util/helpers/SystemException.h" #include "gui/dialogs/CreateAccount/wxCreateAccountDialog.h" #if BOOST_OS_WINDOWS #include #endif #include "config/LaunchSettings.h" #include "config/ActiveSettings.h" #include "gui/helpers/wxHelpers.h" #include "resource/embedded/resources.h" #include "Cafe/CafeSystem.h" #include "Cemu/ncrypto/ncrypto.h" #include "Cafe/TitleList/TitleList.h" #include "wxHelper.h" #include "util/ScreenSaver/ScreenSaver.h" const wxString kDirectSound("DirectSound"); const wxString kXAudio27("XAudio2.7"); const wxString kXAudio2("XAudio2"); const wxString kCubeb("Cubeb"); const wxString kPropertyPersistentId("PersistentId"); const wxString kPropertyMiiName("MiiName"); const wxString kPropertyBirthday("Birthday"); const wxString kPropertyGender("Gender"); const wxString kPropertyEmail("Email"); const wxString kPropertyCountry("Country"); wxDEFINE_EVENT(wxEVT_ACCOUNTLIST_REFRESH, wxCommandEvent); class wxDeviceDescription : public wxClientData { public: wxDeviceDescription(const IAudioAPI::DeviceDescriptionPtr& description) : m_description(description) {} const IAudioAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; } private: IAudioAPI::DeviceDescriptionPtr m_description; }; class wxInputDeviceDescription : public wxClientData { public: wxInputDeviceDescription(const IAudioInputAPI::DeviceDescriptionPtr& description) : m_description(description) {} const IAudioInputAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; } private: IAudioInputAPI::DeviceDescriptionPtr m_description; }; class wxVulkanUUID : public wxClientData { public: wxVulkanUUID(const VulkanRenderer::DeviceInfo& info) : m_device_info(info) {} const VulkanRenderer::DeviceInfo& GetDeviceInfo() const { return m_device_info; } private: VulkanRenderer::DeviceInfo m_device_info; }; #if ENABLE_METAL class wxMetalUUID : public wxClientData { public: wxMetalUUID(const MetalRenderer::DeviceInfo& info) : m_device_info(info) {} const MetalRenderer::DeviceInfo& GetDeviceInfo() const { return m_device_info; } private: MetalRenderer::DeviceInfo m_device_info; }; #endif class wxAccountData : public wxClientData { public: wxAccountData(const Account& account) : m_account(account) {} Account& GetAccount() { return m_account; } const Account& GetAccount() const { return m_account; } private: Account m_account; }; wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook); auto* general_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto* box = new wxStaticBox(panel, wxID_ANY, _("Interface")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); { auto* first_row = new wxFlexGridSizer(0, 2, 0, 0); first_row->SetFlexibleDirection(wxBOTH); first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); first_row->Add(new wxStaticText(box, wxID_ANY, _("Language"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString language_choices[] = { _("Default") }; m_language = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(language_choices), language_choices); m_language->SetSelection(0); m_language->SetToolTip(_("Changes the interface language of Cemu\nAvailable languages are stored in the translation directory\nA restart will be required after changing the language")); for (const auto& language : wxGetApp().GetLanguages()) { m_language->Append(language->DescriptionNative); } first_row->Add(m_language, 0, wxALL | wxEXPAND, 5); box_sizer->Add(first_row, 1, wxEXPAND, 5); } { auto* second_row = new wxFlexGridSizer(0, 3, 0, 0); second_row->SetFlexibleDirection(wxBOTH); second_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); sint32 checkboxCount = 0; auto CountRowElement = [&]() { checkboxCount++; if(checkboxCount != 2) return; second_row->AddSpacer(10); checkboxCount = 0; }; auto InsertEmptyRow = [&]() { while(checkboxCount != 0) CountRowElement(); second_row->AddSpacer(10); second_row->AddSpacer(10); second_row->AddSpacer(10); }; const int topflag = wxALIGN_CENTER_VERTICAL | wxALL; m_save_window_position_size = new wxCheckBox(box, wxID_ANY, _("Remember main window position")); m_save_window_position_size->SetToolTip(_("Restores the last known window position and size when starting Cemu")); second_row->Add(m_save_window_position_size, 0, topflag, 5); CountRowElement(); //second_row->AddSpacer(10); m_save_padwindow_position_size = new wxCheckBox(box, wxID_ANY, _("Remember pad window position")); m_save_padwindow_position_size->SetToolTip(_("Restores the last known pad window position and size when opening it")); second_row->Add(m_save_padwindow_position_size, 0, topflag, 5); CountRowElement(); const int botflag = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM; m_discord_presence = new wxCheckBox(box, wxID_ANY, _("Discord Presence")); m_discord_presence->SetToolTip(_("Enables the Discord Rich Presence feature\nYou will also need to enable it in the Discord settings itself!")); second_row->Add(m_discord_presence, 0, botflag, 5); CountRowElement(); #ifndef ENABLE_DISCORD_RPC m_discord_presence->Disable(); #endif //second_row->AddSpacer(10); m_fullscreen_menubar = new wxCheckBox(box, wxID_ANY, _("Fullscreen menu bar")); m_fullscreen_menubar->SetToolTip(_("Displays the menu bar when Cemu is running in fullscreen mode and the mouse cursor is moved to the top")); second_row->Add(m_fullscreen_menubar, 0, botflag, 5); CountRowElement(); m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot")); m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder")); second_row->Add(m_save_screenshot, 0, botflag, 5); CountRowElement(); m_disable_screensaver = new wxCheckBox(box, wxID_ANY, _("Disable screen saver")); m_disable_screensaver->SetToolTip(_("Prevents the system from activating the screen saver or going to sleep while running a game.")); second_row->Add(m_disable_screensaver, 0, botflag, 5); CountRowElement(); // Enable/disable feral interactive gamemode #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode")); m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed.")); second_row->Add(m_feral_gamemode, 0, botflag, 5); CountRowElement(); #endif // temporary workaround because feature crashes on macOS #if BOOST_OS_MACOS m_disable_screensaver->Enable(false); #endif m_play_boot_sound = new wxCheckBox(box, wxID_ANY, _("Enable intro sound")); m_play_boot_sound->SetToolTip(_("Play bootSound file while compiling shaders/pipelines.")); second_row->Add(m_play_boot_sound, 0, botflag, 5); CountRowElement(); m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates")); m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup")); second_row->Add(m_auto_update, 0, botflag, 5); CountRowElement(); m_receive_untested_releases = new wxCheckBox(box, wxID_ANY, _("Receive untested updates")); m_receive_untested_releases->SetToolTip(_("When checking for updates, include brand new and untested releases. These may contain bugs!")); second_row->Add(m_receive_untested_releases, 0, botflag, 5); #if BOOST_OS_LINUX if (!std::getenv("APPIMAGE")) { m_auto_update->Disable(); } #endif box_sizer->Add(second_row, 0, wxEXPAND, 5); } general_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto* outerMlcBox = new wxStaticBox(panel, wxID_ANY, _("Custom MLC path")); auto* box_sizer_mlc = new wxStaticBoxSizer(outerMlcBox, wxVERTICAL); box_sizer_mlc->Add(new wxStaticText(box_sizer_mlc->GetStaticBox(), wxID_ANY, _("You can configure a custom path for the emulated internal Wii U storage (MLC).\nThis is where Cemu stores saves, accounts and other Wii U system files."), wxDefaultPosition, wxDefaultSize, 0), 0, wxALL, 5); auto* mlcPathLineSizer = new wxBoxSizer(wxHORIZONTAL); m_mlc_path = new wxTextCtrl(outerMlcBox, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY); m_mlc_path->SetMinSize(wxSize(150, -1)); m_mlc_path->SetToolTip(_("The mlc directory contains your save games and installed game update/dlc data")); mlcPathLineSizer->Add(m_mlc_path, 1, wxALL | wxEXPAND, 5); auto* changePath = new wxButton(outerMlcBox, wxID_ANY, "Change"); changePath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathSelect, this); mlcPathLineSizer->Add(changePath, 0, wxALL, 5); if (LaunchSettings::GetMLCPath().has_value()) changePath->Disable(); auto* clearPath = new wxButton(outerMlcBox, wxID_ANY, "Clear custom path"); clearPath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathClear, this); mlcPathLineSizer->Add(clearPath, 0, wxALL, 5); if (LaunchSettings::GetMLCPath().has_value() || !ActiveSettings::IsCustomMlcPath()) clearPath->Disable(); box_sizer_mlc->Add(mlcPathLineSizer, 0, wxEXPAND, 5); general_panel_sizer->Add(box_sizer_mlc, 0, wxEXPAND | wxALL, 5); } { auto* general_gamepath_box = new wxStaticBox(panel, wxID_ANY, _("Game Paths")); auto* general_gamepath_sizer = new wxStaticBoxSizer(general_gamepath_box, wxVERTICAL); m_game_paths = new wxListBox(general_gamepath_box, wxID_ANY); m_game_paths->SetMinSize(wxSize(150, 100)); m_game_paths->SetToolTip(_("Add the root directory of your game(s). It will scan all directories in it for games")); general_gamepath_sizer->Add(m_game_paths, 1, wxALL | wxEXPAND, 5); auto* general_gamepath_buttons = new wxFlexGridSizer(0, 2, 0, 0); general_gamepath_buttons->SetFlexibleDirection(wxBOTH); general_gamepath_buttons->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); auto* general_gamepath_add_button = new wxButton(general_gamepath_box, wxID_ANY, _("Add")); general_gamepath_add_button->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAddPathClicked, this); general_gamepath_add_button->SetToolTip(_("Adds a game path to scan for games displayed in the game list\nIf you have unpacked games, make sure to select the root folder of a game")); general_gamepath_buttons->Add(general_gamepath_add_button, 0, wxALL, 5); auto* general_gamepath_remove_button = new wxButton(general_gamepath_box, wxID_ANY, _("Remove")); general_gamepath_remove_button->Bind(wxEVT_BUTTON, &GeneralSettings2::OnRemovePathClicked, this); general_gamepath_remove_button->SetToolTip(_("Removes the currently selected game path from the game list")); general_gamepath_buttons->Add(general_gamepath_remove_button, 0, wxALL, 5); general_gamepath_sizer->Add(general_gamepath_buttons, 0, wxEXPAND, 5); general_panel_sizer->Add(general_gamepath_sizer, 1, wxEXPAND | wxALL, 5); } panel->SetSizerAndFit(general_panel_sizer); return panel; } wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook) { // Graphics page auto graphics_panel = new wxPanel(notebook); auto graphics_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto box = new wxStaticBox(graphics_panel, wxID_ANY, _("General")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto row = new wxFlexGridSizer(0, 2, 0, 0); row->SetFlexibleDirection(wxBOTH); row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); row->Add(new wxStaticText(box, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); sint32 api_size = 1; wxString choices[3] = { "OpenGL" }; if (g_vulkan_available) { choices[api_size++] = "Vulkan"; } #if ENABLE_METAL choices[api_size++] = "Metal"; #endif m_graphic_api = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, api_size, choices); m_graphic_api->SetSelection(0); if (api_size > 1) m_graphic_api->SetToolTip(_("Select one of the available graphic back ends")); row->Add(m_graphic_api, 0, wxALL, 5); m_graphic_api->Bind(wxEVT_CHOICE, &GeneralSettings2::OnGraphicAPISelected, this); row->Add(new wxStaticText(box, wxID_ANY, _("Graphics Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_graphic_device = new wxChoice(box, wxID_ANY, wxDefaultPosition, { 230, -1 }, api_size, choices); m_graphic_device->SetSelection(0); m_graphic_device->SetToolTip(_("Select the used graphic device")); row->Add(m_graphic_device, 0, wxALL, 5); row->Add(new wxStaticText(box, wxID_ANY, _("VSync")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_vsync = new wxChoice(box, wxID_ANY, wxDefaultPosition, { 230, -1 }); m_vsync->SetToolTip(_("Controls the vsync state")); row->Add(m_vsync, 0, wxALL, 5); box_sizer->Add(row, 0, wxEXPAND, 5); auto* graphic_misc_row = new wxFlexGridSizer(0, 2, 0, 0); m_async_compile = new wxCheckBox(box, wxID_ANY, _("Async shader compile")); m_async_compile->SetToolTip(_("Enables async shader and pipeline compilation. Reduces stutter at the cost of objects not rendering for a short time.\nVulkan only")); graphic_misc_row->Add(m_async_compile, 0, wxALL, 5); m_gx2drawdone_sync = new wxCheckBox(box, wxID_ANY, _("Full sync at GX2DrawDone()")); m_gx2drawdone_sync->SetToolTip(_("If synchronization is requested by the game, the emulated CPU will wait for the GPU to finish all operations.\nThis is more accurate behavior, but may cause lower performance")); graphic_misc_row->Add(m_gx2drawdone_sync, 0, wxALL, 5); m_force_mesh_shaders = new wxCheckBox(box, wxID_ANY, _("Force mesh shaders")); m_force_mesh_shaders->SetToolTip(_("Force mesh shaders on all GPUs that support them. Mesh shaders are disabled by default on Intel GPUs due to potential stability issues")); graphic_misc_row->Add(m_force_mesh_shaders, 0, wxALL, 5); box_sizer->Add(graphic_misc_row, 1, wxEXPAND, 5); graphics_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { wxString choices[] = { _("Bilinear"), _("Bicubic"), _("Hermite"), _("Nearest Neighbor") }; m_upscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Upscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); m_upscale_filter->SetToolTip(_("Upscaling filters are used when the game resolution is smaller than the window size")); graphics_panel_sizer->Add(m_upscale_filter, 0, wxALL | wxEXPAND, 5); m_downscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Downscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); m_downscale_filter->SetToolTip(_("Downscaling filters are used when the game resolution is bigger than the window size")); graphics_panel_sizer->Add(m_downscale_filter, 0, wxALL | wxEXPAND, 5); } { wxString choices[] = { _("Keep aspect ratio"), _("Stretch") }; m_fullscreen_scaling = new wxRadioBox(graphics_panel, wxID_ANY, _("Fullscreen scaling"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); m_fullscreen_scaling->SetToolTip(_("Controls the output aspect ratio when it doesn't match the ratio of the game")); graphics_panel_sizer->Add(m_fullscreen_scaling, 0, wxALL | wxEXPAND, 5); } graphics_panel->SetSizerAndFit(graphics_panel_sizer); return graphics_panel; } wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook) { auto audio_panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto audio_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto box = new wxStaticBox(audio_panel, wxID_ANY, _("General")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto audio_general_row = new wxFlexGridSizer(0, 3, 0, 0); audio_general_row->SetFlexibleDirection(wxBOTH); audio_general_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); audio_general_row->Add(new wxStaticText(box, wxID_ANY, _("API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_audio_api = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr); if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::DirectSound)) m_audio_api->Append(kDirectSound); if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::XAudio27)) m_audio_api->Append(kXAudio27); if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::XAudio2)) m_audio_api->Append(kXAudio2); if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::Cubeb)) m_audio_api->Append(kCubeb); m_audio_api->SetSelection(0); m_audio_api->SetToolTip(_("Select one of the available audio back ends")); audio_general_row->Add(m_audio_api, 0, wxALL, 5); m_audio_api->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioAPISelected, this); audio_general_row->AddSpacer(0); audio_general_row->Add(new wxStaticText(box, wxID_ANY, _("Latency")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_audio_latency = new wxSlider(box, wxID_ANY, 2, 0, IAudioAPI::kBlockCount - 1, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); m_audio_latency->SetToolTip(_("Controls the amount of buffered audio data\nHigher values will create a delay in audio playback, but may avoid audio problems when emulation is too slow")); audio_general_row->Add(m_audio_latency, 0, wxEXPAND | wxALL, 5); auto latency_text = new wxStaticText(box, wxID_ANY, "24ms"); audio_general_row->Add(latency_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnLatencySliderChanged, this, wxID_ANY, wxID_ANY, new wxControlObject(latency_text)); m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnAudioLatencyChanged, this); box_sizer->Add(audio_general_row, 1, wxEXPAND, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } const wxString audio_channel_choices[] = { _("Mono"), _("Stereo") , _("Surround") }; { auto box = new wxStaticBox(audio_panel, wxID_ANY, _("TV")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto audio_tv_row = new wxFlexGridSizer(0, 3, 0, 0); audio_tv_row->SetFlexibleDirection(wxBOTH); audio_tv_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_tv_device = new wxChoice(box, wxID_ANY); m_tv_device->SetMinSize(wxSize(300, -1)); m_tv_device->SetToolTip(_("Select the active audio output device for Wii U TV")); audio_tv_row->Add(m_tv_device, 0, wxEXPAND | wxALL, 5); audio_tv_row->AddSpacer(0); m_tv_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_tv_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_choices), audio_channel_choices); m_tv_channels->SetSelection(1); // set default to stereo m_tv_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this); audio_tv_row->Add(m_tv_channels, 0, wxEXPAND | wxALL, 5); audio_tv_row->AddSpacer(0); audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_tv_volume = new wxSlider(box, wxID_ANY, 100, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); audio_tv_row->Add(m_tv_volume, 0, wxEXPAND | wxALL, 5); auto audio_tv_volume_text = new wxStaticText(box, wxID_ANY, "100%"); audio_tv_row->Add(audio_tv_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_tv_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_tv_volume_text)); m_tv_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); box_sizer->Add(audio_tv_row, 1, wxEXPAND, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Gamepad")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto audio_pad_row = new wxFlexGridSizer(0, 3, 0, 0); audio_pad_row->SetFlexibleDirection(wxBOTH); audio_pad_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_pad_device = new wxChoice(box, wxID_ANY, wxDefaultPosition); m_pad_device->SetMinSize(wxSize(300, -1)); m_pad_device->SetToolTip(_("Select the active audio output device for Wii U GamePad")); audio_pad_row->Add(m_pad_device, 0, wxEXPAND | wxALL, 5); audio_pad_row->AddSpacer(0); m_pad_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); const wxString audio_channel_drc_choices[] = { _("Stereo") }; // stereo for now only audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_pad_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices); m_pad_channels->SetSelection(0); // set default to stereo m_pad_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this); audio_pad_row->Add(m_pad_channels, 0, wxEXPAND | wxALL, 5); audio_pad_row->AddSpacer(0); audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_pad_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); audio_pad_row->Add(m_pad_volume, 0, wxEXPAND | wxALL, 5); auto audio_pad_volume_text = new wxStaticText(box, wxID_ANY, "100%"); audio_pad_row->Add(audio_pad_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_pad_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_pad_volume_text)); m_pad_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); box_sizer->Add(audio_pad_row, 1, wxEXPAND, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Microphone (Experimental)")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto audio_input_row = new wxFlexGridSizer(0, 3, 0, 0); audio_input_row->SetFlexibleDirection(wxBOTH); audio_input_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_input_device = new wxChoice(box, wxID_ANY, wxDefaultPosition); m_input_device->SetMinSize(wxSize(300, -1)); m_input_device->SetToolTip(_("Select the active audio input device for Wii U GamePad")); audio_input_row->Add(m_input_device, 0, wxEXPAND | wxALL, 5); audio_input_row->AddSpacer(0); m_input_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); const wxString audio_channel_drc_choices[] = { _("Mono") }; // mono for now only audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_input_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices); m_input_channels->SetSelection(0); // set default to stereo m_input_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this); audio_input_row->Add(m_input_channels, 0, wxEXPAND | wxALL, 5); audio_input_row->AddSpacer(0); audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_input_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); audio_input_row->Add(m_input_volume, 0, wxEXPAND | wxALL, 5); auto audio_input_volume_text = new wxStaticText(box, wxID_ANY, "100%"); audio_input_row->Add(audio_input_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_input_volume_text)); m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); box_sizer->Add(audio_input_row, 1, wxEXPAND, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } audio_panel->SetSizerAndFit(audio_panel_sizer); return audio_panel; } wxPanel* GeneralSettings2::AddOverlayPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* panel_sizer = new wxBoxSizer(wxVERTICAL); const wxString positions[]{ _("Disabled"), _("Top left"), _("Top center"), _("Top right"), _("Bottom left"), _("Bottom center"), _("Bottom right") }; const wxString text_scale[]{ "50%", "75%", "100%", "125%", "150%", "175%", "200%", "225%", "250%", "275%", "300%" }; { auto box = new wxStaticBox(panel, wxID_ANY, _("Overlay")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto position_row = new wxFlexGridSizer(1, 0, 0, 0); position_row->SetFlexibleDirection(wxBOTH); position_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); { position_row->Add(new wxStaticText(box, wxID_ANY, _("Position")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_overlay_position = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(positions), positions); m_overlay_position->SetSelection(0); m_overlay_position->SetToolTip(_("Controls the overlay which displays technical information while playing")); position_row->Add(m_overlay_position, 0, wxALL, 5); position_row->AddSpacer(25); position_row->Add(new wxStaticText(box, wxID_ANY, _("Text Color")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); m_overlay_font_color = new wxColourPickerCtrl(box, wxID_ANY, *wxWHITE, wxDefaultPosition, wxDefaultSize, wxCLRP_SHOW_ALPHA); m_overlay_font_color->SetToolTip(_("Sets the text color of the overlay")); position_row->Add(m_overlay_font_color, 0, wxEXPAND | wxALL, 5); position_row->AddSpacer(25); position_row->Add(new wxStaticText(box, wxID_ANY, _("Scale")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); m_overlay_scale = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(text_scale), text_scale); m_overlay_scale->SetToolTip(_("Sets the scale of the overlay text")); position_row->Add(m_overlay_scale, 0, wxEXPAND | wxALL, 5); } box_sizer->Add(position_row, 0, wxEXPAND, 5); auto settings2_row = new wxFlexGridSizer(0, 4, 2, 0); { m_overlay_fps = new wxCheckBox(box, wxID_ANY, _("FPS")); m_overlay_fps->SetToolTip(_("The number of frames per second. Average over last 5 seconds")); settings2_row->Add(m_overlay_fps, 0, wxALL, 5); m_overlay_drawcalls = new wxCheckBox(box, wxID_ANY, _("Draw calls per frame")); m_overlay_drawcalls->SetToolTip(_("The number of draw calls per frame. Average over last 5 seconds")); settings2_row->Add(m_overlay_drawcalls, 0, wxALL, 5); m_overlay_cpu = new wxCheckBox(box, wxID_ANY, _("CPU usage")); m_overlay_cpu->SetToolTip(_("CPU usage of Cemu in percent")); settings2_row->Add(m_overlay_cpu, 0, wxALL, 5); m_overlay_cpu_per_core = new wxCheckBox(box, wxID_ANY, _("CPU per core usage")); m_overlay_cpu_per_core->SetToolTip(_("Total cpu usage in percent for each core")); settings2_row->Add(m_overlay_cpu_per_core, 0, wxALL, 5); m_overlay_ram = new wxCheckBox(box, wxID_ANY, _("RAM usage")); m_overlay_ram->SetToolTip(_("Cemu RAM usage in MB")); settings2_row->Add(m_overlay_ram, 0, wxALL, 5); m_overlay_vram = new wxCheckBox(box, wxID_ANY, _("VRAM usage")); #if BOOST_OS_WINDOWS using RtlGetVersion_t = LONG(WINAPI*)(PRTL_OSVERSIONINFOW lpVersionInformation); const auto pRtlGetVersion = (RtlGetVersion_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); //if(IsWindows8Point1OrGreater()) requires manifest RTL_OSVERSIONINFOW info{}; // Windows 8.1 6.3* if (pRtlGetVersion && pRtlGetVersion(&info) == 0 && ((info.dwMajorVersion == 6 && info.dwMinorVersion >= 3) || info.dwMajorVersion > 6)) m_overlay_vram->SetToolTip(_("The VRAM usage of Cemu in MB")); else { m_overlay_vram->SetToolTip(_("This option requires Win8.1+")); m_overlay_vram->Disable(); } #else m_overlay_vram->SetToolTip(_("The VRAM usage of Cemu in MB")); #endif settings2_row->Add(m_overlay_vram, 0, wxALL, 5); m_overlay_debug = new wxCheckBox(box, wxID_ANY, _("Debug")); m_overlay_debug->SetToolTip(_("Displays internal debug information (Vulkan only)")); settings2_row->Add(m_overlay_debug, 0, wxALL, 5); } box_sizer->Add(settings2_row, 0, wxEXPAND, 5); panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto box = new wxStaticBox(panel, wxID_ANY, _("Notifications")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto position_row = new wxFlexGridSizer(1, 0, 0, 0); position_row->SetFlexibleDirection(wxBOTH); position_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); { position_row->Add(new wxStaticText(box, wxID_ANY, _("Position")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_notification_position = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(positions), positions); m_notification_position->SetSelection(0); m_notification_position->SetToolTip(_("Controls the notification position while playing")); position_row->Add(m_notification_position, 0, wxALL, 5); position_row->AddSpacer(25); position_row->Add(new wxStaticText(box, wxID_ANY, _("Text Color")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); m_notification_font_color = new wxColourPickerCtrl(box, wxID_ANY, *wxWHITE, wxDefaultPosition, wxDefaultSize, wxCLRP_SHOW_ALPHA); m_notification_font_color->SetToolTip(_("Sets the text color of notifications")); position_row->Add(m_notification_font_color, 0, wxEXPAND | wxALL, 5); position_row->Add(new wxStaticText(box, wxID_ANY, _("Scale")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); m_notification_scale = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(text_scale), text_scale); m_notification_scale->SetToolTip(_("Sets the scale of the notification text")); position_row->Add(m_notification_scale, 0, wxEXPAND | wxALL, 5); } box_sizer->Add(position_row, 0, wxEXPAND, 5); auto settings1_row = new wxFlexGridSizer(1, 0, 2, 0); { m_controller_profile_name = new wxCheckBox(box, wxID_ANY, _("Controller profiles")); m_controller_profile_name->SetToolTip(_("Displays the active controller profile when starting a game")); settings1_row->Add(m_controller_profile_name, 0, wxALL, 5); m_controller_low_battery = new wxCheckBox(box, wxID_ANY, _("Low battery")); m_controller_low_battery->SetToolTip(_("Shows a notification when a low controller battery has been detected")); settings1_row->Add(m_controller_low_battery, 0, wxALL, 5); m_shader_compiling = new wxCheckBox(box, wxID_ANY, _("Shader compiler")); m_shader_compiling->SetToolTip(_("Shows a notification after shaders have been compiled")); settings1_row->Add(m_shader_compiling, 0, wxALL, 5); m_friends_data = new wxCheckBox(box, wxID_ANY, _("Friend list")); m_friends_data->SetToolTip(_("Shows friend list related data if online")); settings1_row->Add(m_friends_data, 0, wxALL, 5); } box_sizer->Add(settings1_row, 0, wxEXPAND, 5); panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } panel->SetSizerAndFit(panel_sizer); return panel; } wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) { auto* online_panel = new wxPanel(notebook); auto* online_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Account settings")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto* content = new wxFlexGridSizer(0, 4, 0, 0); content->SetFlexibleDirection(wxBOTH); content->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); content->AddGrowableCol(1, 1); content->AddGrowableCol(2, 0); content->AddGrowableCol(3, 0); content->Add(new wxStaticText(box, wxID_ANY, _("Active account")), 1, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_active_account = new wxChoice(box, wxID_ANY); m_active_account->SetMinSize({ 250, -1 }); content->Add(m_active_account, 0, wxEXPAND | wxALL, 5); m_active_account->Bind(wxEVT_CHOICE, &GeneralSettings2::OnActiveAccountChanged, this); m_create_account = new wxButton(box, wxID_ANY, _("Create")); content->Add(m_create_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5); m_create_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountCreate, this); m_delete_account = new wxButton(box, wxID_ANY, _("Delete")); content->Add(m_delete_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5); m_delete_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountDelete, this); box_sizer->Add(content, 1, wxEXPAND, 5); online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); if (CafeSystem::IsTitleRunning()) { m_active_account->Enable(false); m_create_account->Enable(false); m_delete_account->Enable(false); } } { wxString choices[] = { _("Offline"), _("Nintendo"), _("Pretendo"), _("Custom") }; m_active_service = new wxRadioBox(online_panel, wxID_ANY, _("Network Service"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 4, wxRA_SPECIFY_COLS); if (!NetworkConfig::XMLExists()) m_active_service->Enable(3, false); m_active_service->SetItemToolTip(0, _("Online functionality disabled for this account")); m_active_service->SetItemToolTip(1, _("Connect to the official Nintendo Network Service")); m_active_service->SetItemToolTip(2, _("Connect to the Pretendo Network Service")); m_active_service->SetItemToolTip(3, _("Connect to a custom Network Service (configured via network_services.xml)")); m_active_service->Bind(wxEVT_RADIOBOX, &GeneralSettings2::OnAccountServiceChanged,this); online_panel_sizer->Add(m_active_service, 0, wxEXPAND | wxALL, 5); if (CafeSystem::IsTitleRunning()) { m_active_service->Enable(false); } } { auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Online play requirements")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto* row = new wxFlexGridSizer(0, 2, 0, 0); row->SetFlexibleDirection(wxBOTH); row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); const wxImage tmp = wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage(); m_validate_online = new wxBitmapButton(box, wxID_ANY, tmp.Scale(16, 16)); m_validate_online->Bind(wxEVT_BUTTON, &GeneralSettings2::OnShowOnlineValidator, this); row->Add(m_validate_online, 0, wxEXPAND | wxALL, 5); m_online_status = new wxStaticText(box, wxID_ANY, _("No account selected")); row->Add(m_online_status, 1, wxALL | wxALIGN_CENTRE_VERTICAL, 5); box_sizer->Add(row, 1, wxEXPAND, 5); auto* tutorial_link = new wxHyperlinkCtrl(box, wxID_ANY, _("Online play tutorial"), "https://cemu.info/online-guide"); box_sizer->Add(tutorial_link, 0, wxALL, 5); online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { m_account_information = new wxCollapsiblePane(online_panel, wxID_ANY, _("Account information")); #if BOOST_OS_WINDOWS m_account_information->GetControlWidget()->SetBackgroundColour(*wxWHITE); #endif auto win = m_account_information->GetPane(); auto content = new wxBoxSizer(wxVERTICAL); m_account_grid = new wxPropertyGrid(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_HIDE_MARGIN | wxPG_STATIC_SPLITTER); m_account_grid->SetExtraStyle(wxPG_EX_HELP_AS_TOOLTIPS); m_account_grid->SetMinSize({ 300, -1 }); //m_account_grid->Append(new wxPropertyCategory("Main")); auto* persistent_id_gprop = m_account_grid->Append(new wxStringProperty("PersistentId", kPropertyPersistentId)); persistent_id_gprop->SetHelpString(_("The persistent id is the internal folder name used for your saves")); m_account_grid->SetPropertyReadOnly(persistent_id_gprop); m_account_grid->Append(new wxStringProperty(_("Mii name"), kPropertyMiiName))->SetHelpString(_("The mii name is the profile name")); m_account_grid->Append(new wxStringProperty(_("Birthday"), kPropertyBirthday)); wxPGChoices gender; gender.Add(_("Female"), 0); gender.Add(_("Male"), 1); m_account_grid->Append(new wxEnumProperty(_("Gender"), kPropertyGender, gender)); m_account_grid->Append(new wxStringProperty(_("Email"), kPropertyEmail)); wxPGChoices countries; for (int i = 0; i < NCrypto::GetCountryCount(); ++i) { const auto country = NCrypto::GetCountryAsString(i); if (country && (i == 0 || !boost::equals(country, "NN"))) { countries.Add(country, i); } } m_account_grid->Append(new wxEnumProperty(_("Country"), kPropertyCountry, countries)); m_account_grid->Bind(wxEVT_PG_CHANGED, &GeneralSettings2::OnAccountSettingsChanged, this); content->Add(m_account_grid, 1, wxEXPAND | wxALL, 5); win->SetSizer(content); content->SetSizeHints(win); online_panel_sizer->Add(m_account_information, 0, wxEXPAND | wxALL, 5); } online_panel->SetSizerAndFit(online_panel_sizer); return online_panel; } wxPanel* GeneralSettings2::AddDebugPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* debug_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); debug_row->SetFlexibleDirection(wxBOTH); debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); debug_row->Add(new wxStaticText(panel, wxID_ANY, _("Crash dump"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); #if BOOST_OS_WINDOWS wxString dump_choices[] = {_("Disabled"), _("Lite"), _("Full")}; #elif BOOST_OS_UNIX wxString dump_choices[] = {_("Disabled"), _("Enabled")}; #endif m_crash_dump = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(dump_choices), dump_choices); m_crash_dump->SetSelection(0); #if BOOST_OS_WINDOWS m_crash_dump->SetToolTip(_("Creates a dump when Cemu crashes\nOnly enable when requested by a developer!\nThe Full option will create a very large dump file (includes a full RAM dump of the Cemu process)")); #elif BOOST_OS_UNIX m_crash_dump->SetToolTip(_("Creates a core dump when Cemu crashes\nOnly enable when requested by a developer!")); #endif debug_row->Add(m_crash_dump, 0, wxALL | wxEXPAND, 5); debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); } { auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); debug_row->SetFlexibleDirection(wxBOTH); debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); debug_row->Add(new wxStaticText(panel, wxID_ANY, _("GDB Stub port"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_gdb_port = new wxSpinCtrl(panel, wxID_ANY, "1337", wxDefaultPosition, wxDefaultSize, 0, 1000, 65535); m_gdb_port->SetToolTip(_("Changes the port that the GDB stub will use, which you can use by either starting Cemu with the --enable-gdbstub option or by enabling it the Debug tab.")); debug_row->Add(m_gdb_port, 0, wxALL | wxEXPAND, 5); debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); } { auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); debug_row->SetFlexibleDirection(wxBOTH); debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); debug_row->Add(new wxStaticText(panel, wxID_ANY, _("GPU capture save directory"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_gpu_capture_dir = new wxTextCtrl(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_DONTWRAP); m_gpu_capture_dir->SetMinSize(wxSize(150, -1)); m_gpu_capture_dir->SetToolTip(_("Cemu will save the GPU captures done by selecting Debug -> GPU capture in the menu bar in this directory. If a debugger with support for GPU captures (like Xcode) is attached, the capture will be opened in that debugger instead. If such debugger is not attached, METAL_CAPTURE_ENABLED must be set to 1 as an environment variable.")); debug_row->Add(m_gpu_capture_dir, 0, wxALL | wxEXPAND, 5); debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); } { auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); debug_row->SetFlexibleDirection(wxBOTH); debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); m_framebuffer_fetch = new wxCheckBox(panel, wxID_ANY, _("Framebuffer fetch")); m_framebuffer_fetch->SetToolTip(_("Enable framebuffer fetch for eligible textures on supported devices.")); debug_row->Add(m_framebuffer_fetch, 0, wxALL | wxEXPAND, 5); debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); } panel->SetSizerAndFit(debug_panel_sizer); return panel; } GeneralSettings2::GeneralSettings2(wxWindow* parent, bool game_launched) : wxDialog(parent, wxID_ANY, _("General settings"), wxDefaultPosition, wxDefaultSize, wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER), m_game_launched(game_launched) { SetIcon(wxICON(X_SETTINGS)); auto* sizer = new wxBoxSizer(wxVERTICAL); auto* notebook = new wxNotebook(this, wxID_ANY); notebook->AddPage(AddGeneralPage(notebook), _("General")); notebook->AddPage(AddGraphicsPage(notebook), _("Graphics")); notebook->AddPage(AddAudioPage(notebook), _("Audio")); notebook->AddPage(AddOverlayPage(notebook), _("Overlay")); notebook->AddPage(AddAccountPage(notebook), _("Account")); notebook->AddPage(AddDebugPage(notebook), _("Debug")); Bind(wxEVT_CLOSE_WINDOW, &GeneralSettings2::OnClose, this); // sizer->Add(notebook, 1, wxEXPAND | wxALL, 5); SetSizerAndFit(sizer); Layout(); Centre(wxBOTH); // UpdateOnlineAccounts(); UpdateAudioDeviceList(); ApplyConfig(); HandleGraphicsApiSelection(); DisableSettings(game_launched); } uint32 GeneralSettings2::GetSelectedAccountPersistentId() { const auto active_account = m_active_account->GetSelection(); if (active_account == wxNOT_FOUND) return GetConfig().account.m_persistent_id.GetInitValue(); return dynamic_cast(m_active_account->GetClientObject(active_account))->GetAccount().GetPersistentId(); } void GeneralSettings2::StoreConfig() { auto* app = (CemuApp*)wxTheApp; auto& config = GetConfig(); config.use_discord_presence = m_discord_presence->IsChecked(); config.fullscreen_menubar = m_fullscreen_menubar->IsChecked(); config.check_update = m_auto_update->IsChecked(); config.save_screenshot = m_save_screenshot->IsChecked(); config.receive_untested_updates = m_receive_untested_releases->IsChecked(); #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) config.feral_gamemode = m_feral_gamemode->IsChecked(); #endif config.play_boot_sound = m_play_boot_sound->IsChecked(); config.disable_screensaver = m_disable_screensaver->IsChecked(); // Toggle while a game is running if (CafeSystem::IsTitleRunning()) { ScreenSaver::SetInhibit(config.disable_screensaver); } // -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it config.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; config.window_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; config.pad_position = m_save_padwindow_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; config.pad_size = m_save_padwindow_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; config.game_paths.clear(); for (auto& path : m_game_paths->GetStrings()) config.game_paths.emplace_back(path.utf8_string()); auto selection = m_language->GetSelection(); if (selection == 0) GetConfig().language = wxLANGUAGE_DEFAULT; else { const auto language = m_language->GetStringSelection(); for (const auto& lang : app->GetLanguages()) { if (lang->DescriptionNative == language) { GetConfig().language = lang->Language; break; } } } // audio if (m_audio_api->GetStringSelection() == kDirectSound) config.audio_api = IAudioAPI::DirectSound; else if (m_audio_api->GetStringSelection() == kXAudio27) config.audio_api = IAudioAPI::XAudio27; else if (m_audio_api->GetStringSelection() == kXAudio2) config.audio_api = IAudioAPI::XAudio2; else if (m_audio_api->GetStringSelection() == kCubeb) config.audio_api = IAudioAPI::Cubeb; config.audio_delay = m_audio_latency->GetValue(); config.tv_channels = (AudioChannels)m_tv_channels->GetSelection(); //config.pad_channels = (AudioChannels)m_pad_channels->GetSelection(); config.pad_channels = kStereo; // (AudioChannels)m_pad_channels->GetSelection(); //config.input_channels = (AudioChannels)m_input_channels->GetSelection(); config.input_channels = kMono; // (AudioChannels)m_input_channels->GetSelection(); config.tv_volume = m_tv_volume->GetValue(); config.pad_volume = m_pad_volume->GetValue(); config.input_volume = m_input_volume->GetValue(); config.tv_device.clear(); const auto tv_device = m_tv_device->GetSelection(); if (tv_device != wxNOT_FOUND && tv_device != 0 && m_tv_device->HasClientObjectData()) { const auto* device_description = (wxDeviceDescription*)m_tv_device->GetClientObject(tv_device); if(device_description) config.tv_device = device_description->GetDescription()->GetIdentifier(); } config.pad_device.clear(); const auto pad_device = m_pad_device->GetSelection(); if (pad_device != wxNOT_FOUND && pad_device != 0 && m_pad_device->HasClientObjectData()) { const auto* device_description = (wxDeviceDescription*)m_pad_device->GetClientObject(pad_device); if (device_description) config.pad_device = device_description->GetDescription()->GetIdentifier(); } config.input_device = L""; const auto input_device = m_input_device->GetSelection(); if (input_device != wxNOT_FOUND && input_device != 0 && m_input_device->HasClientObjectData()) { const auto* device_description = (wxDeviceDescription*)m_input_device->GetClientObject(input_device); if (device_description) config.input_device = device_description->GetDescription()->GetIdentifier(); } // graphics config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection(); selection = m_graphic_device->GetSelection(); if (config.graphic_api == GraphicAPI::kVulkan) { if (selection != wxNOT_FOUND) { const auto* info = (wxVulkanUUID*)m_graphic_device->GetClientObject(selection); if (info) config.vk_graphic_device_uuid = info->GetDeviceInfo().uuid; else config.vk_graphic_device_uuid = {}; } else config.vk_graphic_device_uuid = {}; } else if (config.graphic_api == GraphicAPI::kMetal) { if (selection != wxNOT_FOUND) { #if ENABLE_METAL const auto* info = (wxMetalUUID*)m_graphic_device->GetClientObject(selection); if (info) config.mtl_graphic_device_uuid = info->GetDeviceInfo().uuid; else config.mtl_graphic_device_uuid = {}; #endif } else config.mtl_graphic_device_uuid = {}; } config.vsync = m_vsync->GetSelection(); config.gx2drawdone_sync = m_gx2drawdone_sync->IsChecked(); config.force_mesh_shaders = m_force_mesh_shaders->IsChecked(); config.async_compile = m_async_compile->IsChecked(); config.upscale_filter = m_upscale_filter->GetSelection(); config.downscale_filter = m_downscale_filter->GetSelection(); config.fullscreen_scaling = m_fullscreen_scaling->GetSelection(); config.overlay.position = (ScreenPosition)m_overlay_position->GetSelection(); wxASSERT((int)config.overlay.position <= (int)ScreenPosition::kBottomRight); config.overlay.text_color = m_overlay_font_color->GetColour().GetRGBA(); config.overlay.text_scale = m_overlay_scale->GetSelection() * 25 + 50; config.overlay.fps = m_overlay_fps->GetValue(); config.overlay.drawcalls = m_overlay_drawcalls->GetValue(); config.overlay.cpu_usage = m_overlay_cpu->GetValue(); config.overlay.cpu_per_core_usage = m_overlay_cpu_per_core->GetValue(); config.overlay.ram_usage = m_overlay_ram->GetValue(); config.overlay.vram_usage = m_overlay_vram->GetValue(); config.overlay.debug = m_overlay_debug->GetValue(); config.notification.position = (ScreenPosition)m_notification_position->GetSelection(); wxASSERT((int)config.notification.position <= (int)ScreenPosition::kBottomRight); config.notification.text_color = m_notification_font_color->GetColour().GetRGBA(); config.notification.text_scale = m_notification_scale->GetSelection() * 25 + 50; config.notification.controller_profiles = m_controller_profile_name->GetValue(); config.notification.controller_battery = m_controller_low_battery->GetValue(); config.notification.shader_compiling = m_shader_compiling->GetValue(); config.notification.friends = m_friends_data->GetValue(); // account config.account.m_persistent_id = GetSelectedAccountPersistentId(); // debug config.crash_dump = (CrashDump)m_crash_dump->GetSelection(); config.gdb_port = m_gdb_port->GetValue(); config.gpu_capture_dir = m_gpu_capture_dir->GetValue().utf8_string(); config.framebuffer_fetch = m_framebuffer_fetch->IsChecked(); g_config.Save(); } GeneralSettings2::~GeneralSettings2() { Unbind(wxEVT_CLOSE_WINDOW, &GeneralSettings2::OnClose, this); } void GeneralSettings2::OnClose(wxCloseEvent& event) { StoreConfig(); if (m_has_account_change) { wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); GetParent()->ProcessWindowEvent(refresh_event); } event.Skip(); } void GeneralSettings2::ValidateConfig() { g_config.Load(); auto& data = g_config.data(); // todo //data.fullscreen_scaling = min(max(data.fullscreen_scaling,)) } void GeneralSettings2::DisableSettings(bool game_launched) { } void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event) { IAudioAPI::s_audioDelay = event.GetInt(); event.Skip(); } void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event) { if(event.GetEventObject() == m_input_volume) { std::shared_lock lock(g_audioInputMutex); if (g_inputAudio) g_inputAudio->SetVolume(event.GetInt()); } else { std::shared_lock lock(g_audioMutex); if(event.GetEventObject() == m_pad_volume) { if (g_padAudio) { g_padAudio->SetVolume(event.GetInt()); g_padVolume = event.GetInt(); } } else { if (g_tvAudio) g_tvAudio->SetVolume(event.GetInt()); } } event.Skip(); } void GeneralSettings2::OnInputVolumeChanged(wxCommandEvent& event) { std::shared_lock lock(g_audioMutex); if (g_padAudio) { g_padAudio->SetInputVolume(event.GetInt()); g_padVolume = event.GetInt(); } event.Skip(); } void GeneralSettings2::OnSliderChangedPercent(wxCommandEvent& event) { const auto slider = dynamic_cast(event.GetEventObject()); wxASSERT(slider); const auto control = dynamic_cast(event.GetEventUserData()); wxASSERT(control); auto slider_text = control->GetControl(); wxASSERT(slider_text); const auto value = event.GetInt(); slider->SetValue(value); slider_text->SetLabel(wxString::Format("%d%%", value)); event.Skip(); } void GeneralSettings2::OnLatencySliderChanged(wxCommandEvent& event) { const auto slider = dynamic_cast(event.GetEventObject()); wxASSERT(slider); const auto control = dynamic_cast(event.GetEventUserData()); wxASSERT(control); auto slider_text = control->GetControl(); wxASSERT(slider_text); const auto value = event.GetInt(); slider->SetValue(value); slider_text->SetLabel(wxString::Format("%dms", value * 12)); event.Skip(); } void GeneralSettings2::UpdateAudioDeviceList() { m_tv_device->Clear(); m_pad_device->Clear(); m_input_device->Clear(); m_tv_device->Append(_("Disabled")); m_pad_device->Append(_("Disabled")); m_input_device->Append(_("Disabled")); const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api; const auto devices = IAudioAPI::GetDevices(audio_api); for (auto& device : devices) { m_tv_device->Append(device->GetName(), new wxDeviceDescription(device)); m_pad_device->Append(device->GetName(), new wxDeviceDescription(device)); } const auto input_audio_api = IAudioInputAPI::Cubeb; //(IAudioAPI::AudioAPI)GetConfig().input_audio_api; const auto input_devices = IAudioInputAPI::GetDevices(input_audio_api); for (auto& device : input_devices) { m_input_device->Append(device->GetName(), new wxInputDeviceDescription(device)); } if(m_tv_device->GetCount() > 1) m_tv_device->SetSelection(1); else m_tv_device->SetSelection(0); m_pad_device->SetSelection(0); m_input_device->SetSelection(0); // todo reset global instance of audio device } void GeneralSettings2::ResetAccountInformation() { m_account_grid->SetSplitterPosition(100); m_active_account->SetSelection(0); for(auto it = m_account_grid->GetIterator(); !it.AtEnd(); ++it) { (*it)->SetValueToUnspecified(); } // refresh pane size m_account_information->InvalidateBestSize(); #if BOOST_OS_WINDOWS m_account_information->OnStateChange(GetBestSize()); #endif } void GeneralSettings2::OnAccountCreate(wxCommandEvent& event) { wxASSERT(Account::HasFreeAccountSlots()); wxCreateAccountDialog dialog(this); if (dialog.ShowModal() == wxID_CANCEL) return; Account account(dialog.GetPersistentId(), dialog.GetMiiName().ToStdWstring()); account.Save(); Account::RefreshAccounts(); const int index = m_active_account->Append(account.ToString(), new wxAccountData(account)); // update ui m_active_account->SetSelection(index); UpdateAccountInformation(); m_create_account->Enable(m_active_account->GetCount() < 0xC); m_delete_account->Enable(m_active_account->GetCount() > 1); // send main window event wxASSERT(GetParent()); wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); GetParent()->ProcessWindowEvent(refresh_event); } void GeneralSettings2::OnAccountDelete(wxCommandEvent& event) { if(m_active_account->GetCount() == 1) { wxMessageBox(_("Can't delete the only account!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } const auto selection = m_active_account->GetSelection(); wxASSERT(selection != wxNOT_FOUND); auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); wxASSERT(obj); auto& account = obj->GetAccount(); const std::wstring format_str = _("Are you sure you want to delete the account {} with id {:x}?").ToStdWstring(); const std::wstring msg = fmt::format(fmt::runtime(format_str), std::wstring{ account.GetMiiName() }, account.GetPersistentId()); const int answer = wxMessageBox(msg, _("Confirmation"), wxYES_NO | wxCENTRE | wxICON_QUESTION, this); if (answer == wxNO) return; // todo: ask if saves should be deleted too? const fs::path path = account.GetFileName(); try { fs::remove_all(path.parent_path()); m_active_account->Delete(selection); m_active_account->SetSelection(0); Account::RefreshAccounts(); UpdateAccountInformation(); m_create_account->Enable(m_active_account->GetCount() < 0xC); m_delete_account->Enable(m_active_account->GetCount() > 1); } catch(const std::exception& ex) { SystemException sys(ex); cemuLog_log(LogType::Force, sys.what()); } } void GeneralSettings2::OnAccountSettingsChanged(wxPropertyGridEvent& event) { wxPGProperty* property = event.GetProperty(); if (!property) return; const wxAny value = property->GetValue(); if (value.IsNull()) return; const auto selection = m_active_account->GetSelection(); wxASSERT(selection != wxNOT_FOUND); auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); wxASSERT(obj); auto& account = obj->GetAccount(); // TODO make id changeable to free ids + current it? bool refresh_accounts = false; if (property->GetName() == kPropertyMiiName) { std::wstring new_name = value.As().ToStdWstring(); if (new_name.empty()) new_name = L"default"; account.SetMiiName(new_name); refresh_accounts = true; } else if (property->GetName() == kPropertyBirthday) { const std::string birthday = value.As().ToStdString(); const boost::char_separator sep{ "-" }; std::vector tokens; for (const auto& token : boost::tokenizer(birthday, sep)) { tokens.emplace_back(token); } if (tokens.size() == 3) { account.SetBirthYear(ConvertString(tokens[0])); account.SetBirthMonth(ConvertString(tokens[1])); account.SetBirthDay(ConvertString(tokens[2])); } } else if (property->GetName() == kPropertyGender) { account.SetGender(value.As()); } else if (property->GetName() == kPropertyEmail) { account.SetEmail(value.As().ToStdString()); } else if (property->GetName() == kPropertyCountry) { account.SetCountry(value.As()); } else cemu_assert_debug(false); account.Save(); Account::RefreshAccounts(); // refresh internal account list UpdateAccountInformation(); // refresh on invalid values if(refresh_accounts) { wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); GetParent()->ProcessWindowEvent(refresh_event); } } void GeneralSettings2::UpdateAccountInformation() { m_account_grid->SetSplitterPosition(100); const auto selection = m_active_account->GetSelection(); if(selection == wxNOT_FOUND) { m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); ResetAccountInformation(); m_online_status->SetLabel(_("No account selected")); return; } const auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); wxASSERT(obj); const auto& account = obj->GetAccount(); m_active_account->SetString(selection, account.ToString()); m_account_grid->GetProperty(kPropertyPersistentId)->SetValueFromString(fmt::format("{:x}", account.GetPersistentId())); m_account_grid->GetProperty(kPropertyMiiName)->SetValueFromString(std::wstring{ account.GetMiiName() }); m_account_grid->GetProperty(kPropertyBirthday)->SetValueFromString(fmt::format("{:04d}-{:02d}-{:02d}", account.GetBirthYear(), account.GetBirthMonth(), account.GetBirthDay())); const auto gender_property = m_account_grid->GetProperty(kPropertyGender); // gender 2 can be also female? gender_property->SetChoiceSelection(std::min(gender_property->GetChoices().GetCount() - 1, (uint32)account.GetGender())); m_account_grid->GetProperty(kPropertyEmail)->SetValueFromString(std::string{ account.GetEmail() }); auto* country_property = dynamic_cast(m_account_grid->GetProperty(kPropertyCountry)); wxASSERT(country_property); int index = (country_property)->GetIndexForValue(account.GetCountry()); if (index == wxNOT_FOUND) index = 0; country_property->SetChoiceSelection(index); const bool online_fully_valid = account.IsValidOnlineAccount() && ActiveSettings::HasRequiredOnlineFiles(); if (ActiveSettings::HasRequiredOnlineFiles()) { if(account.IsValidOnlineAccount()) m_online_status->SetLabel(_("Selected account is a valid online account")); else m_online_status->SetLabel(_("Selected account is not linked to a NNID or PNID")); } else { if(NCrypto::OTP_IsPresent() != NCrypto::SEEPROM_IsPresent()) m_online_status->SetLabel(_("OTP.bin or SEEPROM.bin is missing")); else if(NCrypto::OTP_IsPresent() && NCrypto::SEEPROM_IsPresent()) m_online_status->SetLabel(_("OTP and SEEPROM present but no certificate files were found")); else m_online_status->SetLabel(_("Online play is not set up. Follow the guide below to get started")); } if(online_fully_valid) { m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_CHECK_YES).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() | wxBORDER_NONE); } else { m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); } // enable/disable network service field depending on online requirements m_active_service->Enable(online_fully_valid && !CafeSystem::IsTitleRunning()); if(online_fully_valid) { NetworkService service = GetConfig().GetAccountNetworkService(account.GetPersistentId()); m_active_service->SetSelection(static_cast(service)); // set the config option here for the selected service // this will guarantee that it's actually written to settings.xml // allowing us to eventually get rid of the legacy option in the (far) future GetConfig().SetAccountSelectedService(account.GetPersistentId(), service); } else { m_active_service->SetSelection(0); // force offline } wxString tmp = _("Network service"); tmp.append(" ("); tmp.append(wxString::FromUTF8(boost::nowide::narrow(account.GetMiiName()))); tmp.append(")"); m_active_service->SetLabel(tmp); // refresh pane size m_account_grid->InvalidateBestSize(); //m_account_grid->GetParent()->FitInside(); //m_account_information->OnStateChange(GetBestSize()); idk.. } void GeneralSettings2::UpdateOnlineAccounts() { m_active_account->Clear(); for(const auto& account : Account::GetAccounts()) { m_active_account->Append(fmt::format(L"{} ({:x})", std::wstring{ account.GetMiiName() }, account.GetPersistentId()), new wxAccountData(account)); } m_active_account->SetSelection(0); m_create_account->Enable(m_active_account->GetCount() < 0xC); m_delete_account->Enable(m_active_account->GetCount() > 1); UpdateAccountInformation(); } void GeneralSettings2::HandleGraphicsApiSelection() { int selection = m_vsync->GetSelection(); if(selection == wxNOT_FOUND) selection = GetConfig().vsync; m_vsync->Clear(); if (m_graphic_api->GetSelection() == 0) { // OpenGL m_vsync->AppendString(_("Off")); m_vsync->AppendString(_("On")); if (selection == 0) m_vsync->Select(0); else m_vsync->Select(1); m_graphic_device->Clear(); m_graphic_device->Disable(); m_gx2drawdone_sync->Enable(); m_async_compile->Disable(); m_force_mesh_shaders->Disable(); } else if (m_graphic_api->GetSelection() == 1) { // Vulkan m_gx2drawdone_sync->Disable(); m_async_compile->Enable(); m_force_mesh_shaders->Disable(); m_vsync->AppendString(_("Off")); m_vsync->AppendString(_("Double buffering")); m_vsync->AppendString(_("Triple buffering")); #if BOOST_OS_WINDOWS m_vsync->AppendString(_("Match emulated display (Experimental)")); #endif m_vsync->Select(selection); m_graphic_device->Enable(); auto devices = VulkanRenderer::GetDevices(); m_graphic_device->Clear(); if(!devices.empty()) { for(const auto& device : devices) { m_graphic_device->Append(device.name, new wxVulkanUUID(device)); } m_graphic_device->SetSelection(0); const auto& config = GetConfig(); for(size_t i = 0; i < devices.size(); ++i) { if(config.vk_graphic_device_uuid == devices[i].uuid) { m_graphic_device->SetSelection(i); break; } } } } else { // Metal m_gx2drawdone_sync->Disable(); m_async_compile->Enable(); m_force_mesh_shaders->Enable(); m_vsync->AppendString(_("Off")); m_vsync->AppendString(_("On")); m_vsync->Select(selection); m_graphic_device->Enable(); m_graphic_device->Clear(); #if ENABLE_METAL auto devices = MetalRenderer::GetDevices(); if(!devices.empty()) { for (const auto& device : devices) { m_graphic_device->Append(device.name, new wxMetalUUID(device)); } m_graphic_device->SetSelection(0); const auto& config = GetConfig(); for (size_t i = 0; i < devices.size(); ++i) { if (config.mtl_graphic_device_uuid == devices[i].uuid) { m_graphic_device->SetSelection(i); break; } } } #endif } } void GeneralSettings2::ApplyConfig() { ValidateConfig(); auto& config = GetConfig(); if (LaunchSettings::GetMLCPath().has_value()) m_mlc_path->SetValue(wxHelper::FromPath(LaunchSettings::GetMLCPath().value())); else m_mlc_path->SetValue(wxHelper::FromUtf8(config.mlc_path.GetValue())); m_save_window_position_size->SetValue(config.window_position != Vector2i{-1,-1}); m_save_padwindow_position_size->SetValue(config.pad_position != Vector2i{-1,-1}); m_discord_presence->SetValue(config.use_discord_presence); m_fullscreen_menubar->SetValue(config.fullscreen_menubar); m_auto_update->SetValue(config.check_update); m_receive_untested_releases->SetValue(config.receive_untested_updates); m_save_screenshot->SetValue(config.save_screenshot); m_disable_screensaver->SetValue(config.disable_screensaver); m_play_boot_sound->SetValue(config.play_boot_sound); #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) m_feral_gamemode->SetValue(config.feral_gamemode); #endif // temporary workaround because feature crashes on macOS #if BOOST_OS_MACOS m_disable_screensaver->SetValue(false); #endif m_game_paths->Clear(); for (auto& path : config.game_paths) { m_game_paths->Append(to_wxString(path)); } const auto app = (CemuApp*)wxTheApp; for (const auto& language : app->GetLanguages()) { if (config.language == language->Language) { m_language->SetStringSelection(language->DescriptionNative); break; } } // graphics m_graphic_api->SetSelection(config.graphic_api); m_vsync->SetSelection(config.vsync); m_async_compile->SetValue(config.async_compile); m_gx2drawdone_sync->SetValue(config.gx2drawdone_sync); m_force_mesh_shaders->SetValue(config.force_mesh_shaders); m_upscale_filter->SetSelection(config.upscale_filter); m_downscale_filter->SetSelection(config.downscale_filter); m_fullscreen_scaling->SetSelection(config.fullscreen_scaling); wxASSERT((uint32)config.overlay.position < m_overlay_position->GetCount()); m_overlay_position->SetSelection((int)config.overlay.position); m_overlay_font_color->SetColour(wxColour((unsigned long)config.overlay.text_color)); uint32 selection = (config.overlay.text_scale - 50) / 25; wxASSERT(selection < m_overlay_scale->GetCount()); m_overlay_scale->SetSelection(selection); m_overlay_fps->SetValue(config.overlay.fps); m_overlay_drawcalls->SetValue(config.overlay.drawcalls); m_overlay_cpu->SetValue(config.overlay.cpu_usage); m_overlay_cpu_per_core->SetValue(config.overlay.cpu_per_core_usage); m_overlay_ram->SetValue(config.overlay.ram_usage); m_overlay_vram->SetValue(config.overlay.vram_usage); m_overlay_debug->SetValue(config.overlay.debug); wxASSERT((uint32)config.notification.position < m_notification_position->GetCount()); m_notification_position->SetSelection((int)config.notification.position); m_notification_font_color->SetColour(wxColour((unsigned long)config.notification.text_color)); selection = (config.notification.text_scale - 50) / 25; wxASSERT(selection < m_notification_scale->GetCount()); m_notification_scale->SetSelection(selection); m_controller_profile_name->SetValue(config.notification.controller_profiles); m_controller_low_battery->SetValue(config.notification.controller_battery); m_shader_compiling->SetValue(config.notification.shader_compiling); m_friends_data->SetValue(config.notification.friends); // audio if(config.audio_api == IAudioAPI::DirectSound) m_audio_api->SetStringSelection(kDirectSound); else if(config.audio_api == IAudioAPI::XAudio27) m_audio_api->SetStringSelection(kXAudio27); else if(config.audio_api == IAudioAPI::XAudio2) m_audio_api->SetStringSelection(kXAudio2); else if(config.audio_api == IAudioAPI::Cubeb) m_audio_api->SetStringSelection(kCubeb); SendSliderEvent(m_audio_latency, config.audio_delay); m_tv_channels->SetSelection(config.tv_channels); //m_pad_channels->SetSelection(config.pad_channels); m_pad_channels->SetSelection(0); //m_input_channels->SetSelection(config.pad_channels); m_input_channels->SetSelection(0); SendSliderEvent(m_tv_volume, config.tv_volume); if (!config.tv_device.empty() && m_tv_device->HasClientObjectData()) { for(uint32 i = 0; i < m_tv_device->GetCount(); ++i) { const auto device_description = (wxDeviceDescription*)m_tv_device->GetClientObject(i); if (device_description && config.tv_device == device_description->GetDescription()->GetIdentifier()) { m_tv_device->SetSelection(i); break; } } } else m_tv_device->SetSelection(0); SendSliderEvent(m_pad_volume, config.pad_volume); if (!config.pad_device.empty() && m_pad_device->HasClientObjectData()) { for (uint32 i = 0; i < m_pad_device->GetCount(); ++i) { const auto device_description = (wxDeviceDescription*)m_pad_device->GetClientObject(i); if (device_description && config.pad_device == device_description->GetDescription()->GetIdentifier()) { m_pad_device->SetSelection(i); break; } } } else m_pad_device->SetSelection(0); SendSliderEvent(m_input_volume, config.input_volume); if (!config.input_device.empty() && m_input_device->HasClientObjectData()) { for (uint32 i = 0; i < m_input_device->GetCount(); ++i) { const auto device_description = (wxInputDeviceDescription*)m_input_device->GetClientObject(i); if (device_description && config.input_device == device_description->GetDescription()->GetIdentifier()) { m_input_device->SetSelection(i); break; } } } else m_input_device->SetSelection(0); // account UpdateOnlineAccounts(); m_active_account->SetSelection(0); for(uint32 i = 0; i < m_active_account->GetCount(); ++i) { const auto* obj = dynamic_cast(m_active_account->GetClientObject(i)); wxASSERT(obj); if(obj->GetAccount().GetPersistentId() == ActiveSettings::GetPersistentId()) { m_active_account->SetSelection(i); break; } } m_active_service->SetSelection((int)config.GetAccountNetworkService(ActiveSettings::GetPersistentId())); UpdateAccountInformation(); // debug m_crash_dump->SetSelection((int)config.crash_dump.GetValue()); m_gdb_port->SetValue(config.gdb_port.GetValue()); m_gpu_capture_dir->SetValue(wxHelper::FromUtf8(config.gpu_capture_dir.GetValue())); m_framebuffer_fetch->SetValue(config.framebuffer_fetch); } void GeneralSettings2::OnAudioAPISelected(wxCommandEvent& event) { IAudioAPI::AudioAPI api; if (m_audio_api->GetStringSelection() == kDirectSound) api = IAudioAPI::DirectSound; else if (m_audio_api->GetStringSelection() == kXAudio27) api = IAudioAPI::XAudio27; else if (m_audio_api->GetStringSelection() == kXAudio2) api = IAudioAPI::XAudio2; else if (m_audio_api->GetStringSelection() == kCubeb) api = IAudioAPI::Cubeb; else { wxFAIL_MSG("invalid audio api selected!"); return; } GetConfig().audio_api = api; UpdateAudioDeviceList(); OnAudioDeviceSelected(event); } #define AX_FRAMES_PER_GROUP 4 void GeneralSettings2::UpdateAudioDevice() { auto& config = GetConfig(); std::unique_lock lock(g_audioMutex); std::unique_lock inputLock(g_audioInputMutex); // tv audio device { const auto selection = m_tv_device->GetSelection(); if (selection == wxNOT_FOUND) { cemu_assert_debug(false); return; } g_tvAudio.reset(); if (m_tv_device->HasClientObjectData()) { const auto description = (wxDeviceDescription*)m_tv_device->GetClientObject(selection); if (description) { sint32 channels; if (m_game_launched && g_tvAudio) channels = g_tvAudio->GetChannels(); else channels = CemuConfig::AudioChannelsToNChannels(config.tv_channels); try { g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); g_tvAudio->SetVolume(m_tv_volume->GetValue()); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what()); } } } } // pad audio device { const auto selection = m_pad_device->GetSelection(); if (selection == wxNOT_FOUND) { cemu_assert_debug(false); return; } g_padAudio.reset(); if (m_pad_device->HasClientObjectData()) { const auto description = (wxDeviceDescription*)m_pad_device->GetClientObject(selection); if (description) { sint32 channels; if (m_game_launched && g_padAudio) channels = g_padAudio->GetChannels(); else channels = CemuConfig::AudioChannelsToNChannels(config.pad_channels); try { g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); g_padAudio->SetVolume(m_pad_volume->GetValue()); g_padVolume = m_pad_volume->GetValue(); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what()); } } } } // input audio device { const auto selection = m_input_device->GetSelection(); if (selection == wxNOT_FOUND) { cemu_assert_debug(false); return; } g_inputAudio.reset(); if (m_input_device->HasClientObjectData()) { const auto description = (wxInputDeviceDescription*)m_input_device->GetClientObject(selection); if (description) { sint32 channels; if (m_game_launched && g_inputAudio) channels = g_inputAudio->GetChannels(); else channels = CemuConfig::AudioChannelsToNChannels(config.input_channels); try { g_inputAudio = IAudioInputAPI::CreateDevice(IAudioInputAPI::AudioInputAPI::Cubeb, description->GetDescription(), 32000, channels, snd_core::AX_SAMPLES_PER_3MS_32KHZ, 16); g_inputAudio->SetVolume(m_input_volume->GetValue()); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what()); } } } } } void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event) { UpdateAudioDevice(); } void GeneralSettings2::OnAudioChannelsSelected(wxCommandEvent& event) { const auto obj = wxDynamicCast(event.GetEventObject(), wxChoice); wxASSERT(obj); if (obj->GetSelection() == wxNOT_FOUND) return; auto& config = GetConfig(); if (obj == m_tv_channels) { if (config.tv_channels == (AudioChannels)obj->GetSelection()) return; config.tv_channels = (AudioChannels)obj->GetSelection(); } else if (obj == m_pad_channels) { if (config.pad_channels == (AudioChannels)obj->GetSelection()) return; config.pad_channels = (AudioChannels)obj->GetSelection(); } else cemu_assert_debug(false); if(m_game_launched) wxMessageBox(_("You have to restart the game in order to apply the new settings."), _("Information"), wxOK | wxCENTRE, this); else UpdateAudioDevice(); } void GeneralSettings2::OnGraphicAPISelected(wxCommandEvent& event) { HandleGraphicsApiSelection(); } void GeneralSettings2::OnAddPathClicked(wxCommandEvent& event) { wxDirDialog path_dialog(this, _("Select a directory containing games."), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty()) return; const auto path = path_dialog.GetPath(); // test if already included for (auto& s : m_game_paths->GetStrings()) { if (s == path) return; } m_game_paths->Append(path); m_reload_gamelist = true; // trigger title list rescan with new path configuration CafeTitleList::ClearScanPaths(); for (auto& it : m_game_paths->GetStrings()) CafeTitleList::AddScanPath(wxHelper::MakeFSPath(it)); CafeTitleList::Refresh(); } void GeneralSettings2::OnRemovePathClicked(wxCommandEvent& event) { const auto selection = m_game_paths->GetSelection(); if (selection == wxNOT_FOUND) return; m_game_paths->Delete(selection); m_reload_gamelist = true; // trigger title list rescan with new path configuration CafeTitleList::ClearScanPaths(); for (auto& it : m_game_paths->GetStrings()) CafeTitleList::AddScanPath(wxHelper::MakeFSPath(it)); CafeTitleList::Refresh(); } void GeneralSettings2::OnActiveAccountChanged(wxCommandEvent& event) { UpdateAccountInformation(); m_has_account_change = true; } void GeneralSettings2::OnAccountServiceChanged(wxCommandEvent& event) { auto& config = GetConfig(); uint32 peristentId = GetSelectedAccountPersistentId(); config.SetAccountSelectedService(peristentId, static_cast(m_active_service->GetSelection())); UpdateAccountInformation(); } void GeneralSettings2::OnMLCPathSelect(wxCommandEvent& event) { if(CafeSystem::IsTitleRunning()) { wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } // show directory dialog wxDirDialog path_dialog(this, _("Select MLC directory"), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty()) return; // check if the choosen MLC path is an already initialized MLC location fs::path newMlc = wxHelper::MakeFSPath(path_dialog.GetPath()); if(CemuApp::CheckMLCPath(newMlc)) { // ask user if they are sure they want to use this folder and let them know that accounts and saves wont transfer wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?"); wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); if(dialog.ShowModal() == wxID_NO) return; if( !CemuApp::CreateDefaultMLCFiles(newMlc) ) // creating also acts as a check for read+write access { wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } } else { // ask user if they want to create a new mlc structure at the choosen location wxString message = _("The selected directory does not contain the expected MLC structure. Do you want to create a new MLC structure in this directory?\nNote that changing the MLC location will not transfer any accounts or save files."); wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); if( !CemuApp::CreateDefaultMLCFiles(newMlc) ) { wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } } // update MLC path and store any other modified settings GetConfig().SetMLCPath(newMlc); StoreConfig(); wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this); // close settings and then cemu wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW); wxPostEvent(this, closeEvent); wxPostEvent(GetParent(), closeEvent); } void GeneralSettings2::OnMLCPathClear(wxCommandEvent& event) { if(CafeSystem::IsTitleRunning()) { wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?"); wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); if(dialog.ShowModal() == wxID_NO) return; GetConfig().SetMLCPath(""); StoreConfig(); g_config.Save(); wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this); // close settings and then cemu wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW); wxPostEvent(this, closeEvent); wxPostEvent(GetParent(), closeEvent); } void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) { const auto selection = m_active_account->GetSelection(); if (selection == wxNOT_FOUND) return; const auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); wxASSERT(obj); const auto& account = obj->GetAccount(); const auto validator = account.ValidateOnlineFiles(); if (validator) // everything valid? shouldn't happen return; wxString err; err << _("The following error(s) have been found:") << '\n'; if (validator.otp == OnlineValidator::FileState::Missing) err << _("otp.bin missing in Cemu directory") << '\n'; else if(validator.otp == OnlineValidator::FileState::Corrupted) err << _("otp.bin is invalid") << '\n'; if (validator.seeprom == OnlineValidator::FileState::Missing) err << _("seeprom.bin missing in Cemu directory") << '\n'; else if(validator.seeprom == OnlineValidator::FileState::Corrupted) err << _("seeprom.bin is invalid") << '\n'; if(!validator.missing_files.empty()) { err << _("Missing certificate and key files:") << '\n'; int counter = 0; for (const auto& f : validator.missing_files) { err << f << '\n'; ++counter; if(counter > 10) { err << "..." << '\n'; break; } } err << '\n'; } if (!validator.valid_account) { err << _("The currently selected account is not a valid or dumped online account:") << '\n'; err << GetOnlineAccountErrorMessage(validator.account_error); } wxMessageBox(err, _("Online Status"), wxOK | wxCENTRE | wxICON_INFORMATION); } wxString GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error) { switch (error) { case OnlineAccountError::kNoAccountId: return _("AccountId missing (The account is not connected to a NNID/PNID)"); case OnlineAccountError::kNoPasswordCached: return _("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)"); case OnlineAccountError::kPasswordCacheEmpty: return _("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)"); case OnlineAccountError::kNoPrincipalId: return _("PrincipalId missing"); default: return "no error"; } }