Cemu/src/gui/wxgui/CemuApp.cpp
Cynthia 896c4ab7da allow coreinit.exit to actually "exit"
fixes #1960

this commit implements one particular approach for exiting emulation
when the RPX being emulated calls `coreinit.exit`. I don't believe
this might be the best way long term, but it seemed to be the best
way given the state (and my knowledge of the codebase). This threads
through 'End Emulation' (normally a debug only feature in the Cemu
menu) into one more spot in the codebase, when `coreinit.exit` is
called. Why did we go this way?

Stopping PPC Emulation _while_ the emulator is actively executing
an instruction (the call to `coreinit.exit`) seemed like a path of
el derado everytime i tried it. calling `Shutdown`, or `ShutdownTitle`
is not ideal because it doesn't do things like update status in
discord or any of the other window actions, and they lock up because
the emulator is already running the coreinit instructions (even if
you launch it in a delayed thread, the PPC emulator is still doing
stuff).

so the only way to get this to somewhat work is _to reach out to the
window system_. So I threaded through an `EndEmulation` request, which
started off just calling `EndEmulation` that already existed. There is
a note on that function about how it memory leaks if called repeatedly,
and it's "not finished". However, this seemed better than looping
forever, or just crashing with a debug assert. At least you still have
a semi-functional window (even if memory is leaking), and a log window
you can investigate?

However, just calling endemulation didn't exit when Cemu was launched
from the command line (my goal), it just returns to the main Cemu
window. To make Cemu usable as a command line app that you can fire
and forget, I threaded a new variable through, that when end emulation
is called and the title was launched with `-g`, or `-t`, it just closes
cemu altogether. this allows my CLI commands to "just work"
2026-06-23 11:17:45 -07:00

664 lines
21 KiB
C++

#include "wxgui/CemuApp.h"
#include "interface/WindowSystem.h"
#include "wxCemuConfig.h"
#include "wxgui/MainWindow.h"
#include "wxgui/wxgui.h"
#include "config/CemuConfig.h"
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#endif
#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "config/ActiveSettings.h"
#include "config/LaunchSettings.h"
#include "wxgui/GettingStartedDialog.h"
#include "input/InputManager.h"
#include "input/api/SDL/SDLControllerProvider.h"
#include "wxgui/helpers/wxHelpers.h"
#include "Cemu/ncrypto/ncrypto.h"
#include "wxgui/input/HotkeySettings.h"
#include "wxgui/debugger/DebuggerWindow2.h"
#include <wx/language.h>
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
#include "wxgui/helpers/wxWayland.h"
#endif
#if __WXGTK__
#include <glib.h>
#endif
#include <wx/image.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <wx/clipbrd.h>
#include <wx/timer.h>
#include "wxHelper.h"
#include "Cafe/TitleList/TitleList.h"
#include "Cafe/TitleList/SaveList.h"
wxIMPLEMENT_APP_NO_MAIN(CemuApp);
// defined in wxWindowSystem.cpp
extern WindowSystem::WindowInfo g_window_info;
extern std::shared_mutex g_mutex;
// forward declarations from main.cpp
void UnitTests();
void CemuCommonInit();
void HandlePostUpdate();
// Translation strings to extract for gettext:
void unused_translation_dummy()
{
void(_("Browse"));
void(_("Select a file"));
void(_("Select a directory"));
void(_("Japanese"));
void(_("English"));
void(_("French"));
void(_("German"));
void(_("Italian"));
void(_("Spanish"));
void(_("Chinese"));
void(_("Korean"));
void(_("Dutch"));
void(_("Portugese"));
void(_("Russian"));
void(_("Taiwanese"));
void(_("unknown"));
}
#if BOOST_OS_WINDOWS
#include <shlobj.h>
fs::path GetAppDataRoamingPath()
{
PWSTR path = nullptr;
HRESULT result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &path);
if (FAILED(result))
{
CoTaskMemFree(path);
return {};
}
std::string appDataPath = boost::nowide::narrow(path);
CoTaskMemFree(path);
return _utf8ToPath(appDataPath);
}
#endif
#if BOOST_OS_WINDOWS
void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Windows
{
std::error_code ec;
bool isPortable = false;
fs::path user_data_path, config_path, cache_path, data_path;
auto standardPaths = wxStandardPaths::Get();
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
fs::path portablePath = exePath.parent_path() / "portable";
data_path = exePath.parent_path(); // the data path is always the same as the exe path
#ifdef CEMU_ALLOW_PORTABLE
if (fs::is_directory(portablePath, ec))
{
isPortable = true;
user_data_path = config_path = cache_path = portablePath;
}
else
#endif
{
fs::path roamingPath = GetAppDataRoamingPath() / "Cemu";
user_data_path = config_path = cache_path = roamingPath;
}
// on Windows Cemu used to be portable by default prior to 2.0-89
// to remain backwards compatible with old installations we check for settings.xml in the Cemu directory
// if it exists, we use the exe path as the portable directory
if(!isPortable) // lower priority than portable directory
{
if (fs::exists(exePath.parent_path() / "settings.xml", ec))
{
isPortable = true;
user_data_path = config_path = cache_path = exePath.parent_path();
}
}
ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess);
}
#endif
#if BOOST_OS_LINUX || BOOST_OS_BSD
void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux
{
std::error_code ec;
bool isPortable = false;
fs::path user_data_path, config_path, cache_path, data_path;
auto standardPaths = wxStandardPaths::Get();
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
fs::path portablePath = exePath.parent_path() / "portable";
// GetExecutablePath returns the AppImage's temporary mount location
wxString appImagePath;
if (wxGetEnv("APPIMAGE", &appImagePath))
{
exePath = wxHelper::MakeFSPath(appImagePath);
portablePath = exePath.parent_path() / "portable";
}
#ifdef CEMU_ALLOW_PORTABLE
if (fs::is_directory(portablePath, ec))
{
isPortable = true;
user_data_path = config_path = cache_path = portablePath;
// in portable mode assume the data directories (resources, gameProfiles/default/) are next to the executable
data_path = exePath.parent_path();
}
else
#endif
{
SetAppName("Cemu");
wxString appName = GetAppName();
standardPaths.SetFileLayout(wxStandardPaths::FileLayout::FileLayout_XDG);
auto getEnvDir = [&](const wxString& varName, const wxString& defaultValue)
{
wxString dir;
if (!wxGetEnv(varName, &dir) || dir.empty())
return defaultValue;
return dir;
};
wxString homeDir = wxFileName::GetHomeDir();
user_data_path = (getEnvDir(wxS("XDG_DATA_HOME"), homeDir + wxS("/.local/share")) + "/" + appName).ToStdString();
config_path = (getEnvDir(wxS("XDG_CONFIG_HOME"), homeDir + wxS("/.config")) + "/" + appName).ToStdString();
data_path = standardPaths.GetDataDir().ToStdString();
cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString();
cache_path /= appName.ToStdString();
}
ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess);
}
#endif
#if BOOST_OS_MACOS
void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for MacOS
{
std::error_code ec;
bool isPortable = false;
fs::path user_data_path, config_path, cache_path, data_path;
auto standardPaths = wxStandardPaths::Get();
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
// If run from an app bundle, use its parent directory
fs::path appPath = exePath.parent_path().parent_path().parent_path();
fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable";
#ifdef CEMU_ALLOW_PORTABLE
if (fs::is_directory(portablePath, ec))
{
isPortable = true;
user_data_path = config_path = cache_path = portablePath;
data_path = exePath.parent_path();
}
else
#endif
{
SetAppName("Cemu");
wxString appName = GetAppName();
user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString();
data_path = standardPaths.GetDataDir().ToStdString();
cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString();
cache_path /= appName.ToStdString();
}
ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess);
}
#endif
// create default MLC files or quit if it fails
void CemuApp::InitializeNewMLCOrFail(fs::path mlc)
{
if( CemuApp::CreateDefaultMLCFiles(mlc) )
return; // all good
cemu_assert_debug(!ActiveSettings::IsCustomMlcPath()); // should not be possible?
if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath())
{
// tell user that the custom path is not writable
wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
wxMessageBox(formatWxString(_("Cemu failed to write to the mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
void CemuApp::InitializeExistingMLCOrFail(fs::path mlc)
{
if(CreateDefaultMLCFiles(mlc))
return; // all good
// failed to write mlc files
if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath())
{
// tell user that the custom path is not writable
// if it's a command line path then just quit. Otherwise ask if user wants to reset the path
if(ActiveSettings::IsCommandLineMlcPath())
{
wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
// ask user if they want to reset the path
const wxString message = formatWxString(_("Cemu failed to write to the custom mlc directory.\n\nThe path is:\n{}\n\nCemu cannot start without a valid mlc path. Do you want to reset the path? You can later change it again in the General Settings."),
_pathToUtf8(mlc));
wxMessageDialog dialog(nullptr, message, _("Error"), wxCENTRE | wxYES_NO | wxICON_WARNING);
dialog.SetYesNoLabels(_("Reset path"), _("Exit"));
const auto dialogResult = dialog.ShowModal();
if (dialogResult == wxID_NO)
exit(0);
else // reset path
{
GetConfig().mlc_path = "";
GetConfigHandle().Save();
}
}
else
{
// default path is not writeable. Just let the user know and quit. Unsure if it would be a good idea to ask the user to choose an alternative path instead
wxMessageBox(formatWxString(_("Cemu failed to write to the default mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
}
std::string TranslationCallback(std::string_view msgId)
{
return wxGetTranslation(wxString::FromUTF8(msgId)).utf8_string();
}
bool CemuApp::OnInit()
{
#if __WXGTK__
GTKSuppressDiagnostics(G_LOG_LEVEL_MASK & ~G_LOG_FLAG_FATAL);
#endif
std::set<fs::path> failedWriteAccess;
DeterminePaths(failedWriteAccess);
// make sure default cemu directories exist
CreateDefaultCemuFiles();
GetConfigHandle().SetFilename(ActiveSettings::GetConfigPath("settings.xml").generic_wstring());
auto& config = GetWxGUIConfig();
std::error_code ec;
bool isFirstStart = !fs::exists(ActiveSettings::GetConfigPath("settings.xml"), ec);
NetworkConfig::LoadOnce();
if (!isFirstStart)
{
GetConfigHandle().Load();
sint32 language = config.language.GetValue();
LocalizeUI(static_cast<wxLanguage>(language == wxLANGUAGE_DEFAULT ? wxLocale::GetSystemLanguage() : language));
}
else
{
LocalizeUI(static_cast<wxLanguage>(wxLocale::GetSystemLanguage()));
}
SetTranslationCallback(TranslationCallback);
#if __WXMSW__
auto& wxGuiConfig = GetWxGUIConfig();
if (wxGuiConfig.msw_theme.GetValue() == static_cast<int>(MSWThemeOption::kAuto))
{
MSWEnableDarkMode(DarkMode_Auto);
}
else if (wxGuiConfig.msw_theme.GetValue() == static_cast<int>(MSWThemeOption::kDark))
{
MSWEnableDarkMode(DarkMode_Always);
}
// extend tooltip duration to the maximum possible value
wxToolTip::SetDelay(-1);
wxToolTip::SetAutoPop(MAKELPARAM(std::numeric_limits<short>::max(),0));
#endif
for (auto&& path : failedWriteAccess)
{
wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxHelper::FromPath(path)),
_("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr);
}
if (isFirstStart)
{
// show the getting started dialog
GettingStartedDialog dia(nullptr);
dia.ShowModal();
// make sure config is created. Gfx pack UI and input UI may create it earlier already, but we still want to update it
GetConfigHandle().Save();
// create mlc, on failure the user can quit here. So do this after the Getting Started dialog
InitializeNewMLCOrFail(ActiveSettings::GetMlcPath());
}
else
{
// check if mlc is valid and recreate default files
InitializeExistingMLCOrFail(ActiveSettings::GetMlcPath());
}
ActiveSettings::Init(); // this is a bit of a misnomer, right now this call only loads certs for online play. In the future we should move the logic to a more appropriate place
HandlePostUpdate();
LatteOverlay_init();
// run a couple of tests if in non-release mode
#ifdef CEMU_DEBUG_ASSERT
UnitTests();
#endif
#if BOOST_OS_MACOS
SDLControllerProvider::InitSDL();
#endif
CemuCommonInit();
#if BOOST_OS_MACOS
m_sdlEventPumpTimer = new wxTimer(this);
Bind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this);
m_sdlEventPumpTimer->Start(5, wxTIMER_CONTINUOUS);
#endif
wxInitAllImageHandlers();
// fill colour db
wxTheColourDatabase->AddColour("ERROR", wxColour(0xCC, 0, 0));
wxTheColourDatabase->AddColour("SUCCESS", wxColour(0, 0xbb, 0));
#if BOOST_OS_WINDOWS
const auto parent_path = GetParentProcess();
if(parent_path.has_filename())
{
const auto filename = parent_path.filename().generic_string();
if (boost::icontains(filename, "WiiU_USB_Helper"))
__fastfail(0);
}
#endif
#ifdef ENABLE_VULKAN
InitializeGlobalVulkan();
#endif
Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this);
m_mainFrame = new MainWindow();
std::unique_lock lock(g_mutex);
g_window_info.app_active = true;
HotkeySettings::Init(m_mainFrame);
SetTopWindow(m_mainFrame);
m_mainFrame->Show();
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
if (wxWlIsWaylandWindow(m_mainFrame))
wxWlSetAppId(m_mainFrame, "info.cemu.Cemu");
#endif
// show warning on macOS about state of builds
#if BOOST_OS_MACOS
if (!config.did_show_macos_disclaimer)
{
const auto message = _(
"Thank you for testing the in-development build of Cemu for macOS.\n \n"
"The macOS port is currently purely experimental and should not be considered stable or ready for issue-free gameplay. "
"There are also known issues with degraded performance due to the use of MoltenVk and Rosetta for ARM Macs. We appreciate your patience while we improve Cemu for macOS.");
wxMessageDialog dialog(nullptr, message, _("Preview version"), wxCENTRE | wxOK | wxICON_WARNING);
dialog.SetOKLabel(_("I understand"));
dialog.ShowModal();
config.did_show_macos_disclaimer = true;
GetConfigHandle().Save();
}
#endif
return true;
}
int CemuApp::OnExit()
{
#if BOOST_OS_MACOS
if (m_sdlEventPumpTimer)
{
m_sdlEventPumpTimer->Stop();
Unbind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this);
delete m_sdlEventPumpTimer;
m_sdlEventPumpTimer = nullptr;
}
#endif
wxApp::OnExit();
wxTheClipboard->Flush();
InputManager::instance().Shutdown();
#if BOOST_OS_MACOS
SDLControllerProvider::ShutdownSDL();
#endif
#if BOOST_OS_WINDOWS
ExitProcess(WindowSystem::GetExitCode());
#else
_Exit(WindowSystem::GetExitCode());
#endif
}
#if BOOST_OS_MACOS
void CemuApp::OnSDLEventPumpTimer(wxTimerEvent& event)
{
// this callback is only used on macOS where SDL event functions need to be called on the main thread
// on other platforms SDLControllerProvider creates a separate thread for SDL event polling
SDLControllerProvider::PumpSDLEvents();
}
#endif
#if BOOST_OS_WINDOWS
void DumpThreadStackTrace();
#endif
void CemuApp::OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg)
{
cemuLog_createLogFile(false);
cemuLog_log(LogType::Force, "Encountered wxWidgets assert!");
cemuLog_log(LogType::Force, "File: {0} Line: {1}", wxString(file).utf8_string(), line);
cemuLog_log(LogType::Force, "Func: {0} Cond: {1}", wxString(func).utf8_string(), wxString(cond).utf8_string());
cemuLog_log(LogType::Force, "Message: {}", wxString(msg).utf8_string());
#if BOOST_OS_WINDOWS
DumpThreadStackTrace();
#endif
cemu_assert_debug(false);
}
int CemuApp::FilterEvent(wxEvent& event)
{
if(event.GetEventType() == wxEVT_KEY_DOWN)
{
const auto& key_event = (wxKeyEvent&)event;
g_window_info.set_keystate(fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags()), true);
}
else if(event.GetEventType() == wxEVT_KEY_UP)
{
const auto& key_event = (wxKeyEvent&)event;
g_window_info.set_keystate(fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags()), false);
}
else if(event.GetEventType() == wxEVT_ACTIVATE_APP)
{
const auto& activate_event = (wxActivateEvent&)event;
if(!activate_event.GetActive())
g_window_info.set_keystatesup();
}
// track if debugger window or its child windows are focused
if (s_debuggerWindow && (event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_ACTIVATE))
{
wxWindow* target_window = wxDynamicCast(event.GetEventObject(), wxWindow);
if (target_window && event.GetEventType() == wxEVT_ACTIVATE && !((wxActivateEvent&)event).GetActive())
target_window = nullptr;
if (target_window)
{
g_window_info.debugger_focused = false;
wxWindow* window_it = target_window;
while (window_it)
{
if (window_it == s_debuggerWindow) g_window_info.debugger_focused = true;
window_it = window_it->GetParent();
}
}
}
else if (!s_debuggerWindow)
{
g_window_info.debugger_focused = false;
}
return wxApp::FilterEvent(event);
}
std::vector<const wxLanguageInfo *> CemuApp::GetLanguages() const {
std::vector availableLanguages(m_availableTranslations);
availableLanguages.insert(availableLanguages.begin(), wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH));
return availableLanguages;
}
void CemuApp::LocalizeUI(wxLanguage languageToUse)
{
std::unique_ptr<wxTranslations> translationsMgr(new wxTranslations());
m_availableTranslations = GetAvailableTranslationLanguages(translationsMgr.get());
bool isTranslationAvailable = std::any_of(m_availableTranslations.begin(), m_availableTranslations.end(),
[languageToUse](const wxLanguageInfo* info) { return info->Language == languageToUse; });
if (languageToUse == wxLANGUAGE_DEFAULT || isTranslationAvailable)
{
translationsMgr->SetLanguage(static_cast<wxLanguage>(languageToUse));
translationsMgr->AddCatalog("cemu");
if (translationsMgr->IsLoaded("cemu") && wxLocale::IsAvailable(languageToUse))
{
m_locale.Init(languageToUse);
}
// This must be run after wxLocale::Init, as the latter sets up its own wxTranslations instance which we want to override
wxTranslations::Set(translationsMgr.release());
}
}
std::vector<const wxLanguageInfo*> CemuApp::GetAvailableTranslationLanguages(wxTranslations* translationsMgr)
{
wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxHelper::FromPath(ActiveSettings::GetDataPath("resources")));
std::vector<const wxLanguageInfo*> languages;
for (const auto& langName : translationsMgr->GetAvailableTranslations("cemu"))
{
const auto* langInfo = wxLocale::FindLanguageInfo(langName);
if (langInfo)
languages.emplace_back(langInfo);
}
return languages;
}
bool CemuApp::CheckMLCPath(const fs::path& mlc)
{
std::error_code ec;
if (!fs::exists(mlc, ec))
return false;
if (!fs::exists(mlc / "usr", ec) || !fs::exists(mlc / "sys", ec))
return false;
return true;
}
bool CemuApp::CreateDefaultMLCFiles(const fs::path& mlc)
{
auto CreateDirectoriesIfNotExist = [](const fs::path& path)
{
std::error_code ec;
if (!fs::exists(path, ec))
return fs::create_directories(path, ec);
return true;
};
// list of directories to create
const fs::path directories[] = {
mlc,
mlc / "sys",
mlc / "usr",
mlc / "usr/title/00050000", // base
mlc / "usr/title/0005000c", // dlc
mlc / "usr/title/0005000e", // update
mlc / "usr/save/00050010/1004a000/user/common/db", // Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200}
mlc / "usr/save/00050010/1004a100/user/common/db",
mlc / "usr/save/00050010/1004a200/user/common/db",
mlc / "sys/title/0005001b/1005c000/content" // lang files
};
for(auto& path : directories)
{
if(!CreateDirectoriesIfNotExist(path))
return false;
}
// create sys/usr folder in mlc01
try
{
const auto langDir = fs::path(mlc).append("sys/title/0005001b/1005c000/content");
auto langFile = fs::path(langDir).append("language.txt");
if (!fs::exists(langFile))
{
std::ofstream file(langFile);
if (file.is_open())
{
const char* langStrings[] = { "ja","en","fr","de","it","es","zh","ko","nl","pt","ru","zh" };
for (const char* lang : langStrings)
file << fmt::format(R"("{}",)", lang) << std::endl;
file.flush();
file.close();
}
}
auto countryFile = fs::path(langDir).append("country.txt");
if (!fs::exists(countryFile))
{
std::ofstream file(countryFile);
for (sint32 i = 0; i < NCrypto::GetCountryCount(); i++)
{
const char* countryCode = NCrypto::GetCountryAsString(i);
if (boost::iequals(countryCode, "NN"))
file << "NULL," << std::endl;
else
file << fmt::format(R"("{}",)", countryCode) << std::endl;
}
file.flush();
file.close();
}
// create a dummy file in the mlc folder to check if it's writable
const auto dummyFile = fs::path(mlc).append("writetestdummy");
std::ofstream file(dummyFile);
if (!file.is_open())
return false;
file.close();
fs::remove(dummyFile);
}
catch (const std::exception& ex)
{
return false;
}
return true;
}
void CemuApp::CreateDefaultCemuFiles()
{
// cemu directories
try
{
const auto controllerProfileFolder = ActiveSettings::GetConfigPath("controllerProfiles");
if (!fs::exists(controllerProfileFolder))
fs::create_directories(controllerProfileFolder);
const auto memorySearcherFolder = ActiveSettings::GetUserDataPath("memorySearcher");
if (!fs::exists(memorySearcherFolder))
fs::create_directories(memorySearcherFolder);
}
catch (const std::exception& ex)
{
wxString errorMsg = formatWxString(_("Couldn't create a required cemu directory or file!\n\nError: {0}"), ex.what());
#if BOOST_OS_WINDOWS
const DWORD lastError = GetLastError();
if (lastError != ERROR_SUCCESS)
errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError));
#endif
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
}
void CemuApp::ActivateApp(wxActivateEvent& event)
{
g_window_info.app_active = event.GetActive();
event.Skip();
}