mirror of
https://github.com/cemu-project/Cemu.git
synced 2026-07-09 17:14:47 -06:00
When coreinit exit() is hit, then Cemu will only close when the game was launched directly via command line parameters (-g or -t). Otherwise it returns to the game list. In the case where Cemu closes it will forward the coreinit exit status code as it's own exit status code. This is useful for cases where Cemu is used as a CLI tool for testing homebrew
666 lines
21 KiB
C++
666 lines
21 KiB
C++
#include "wxgui/CemuApp.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"
|
|
#include "Cafe/CafeSystem.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();
|
|
int retValue = 0;
|
|
if (auto r = CafeSystem::GetForegroundTitleReturnStatus(); (LaunchSettings::GetLoadFile() || LaunchSettings::GetLoadTitleID()) && r)
|
|
retValue = *r;
|
|
#if BOOST_OS_MACOS
|
|
SDLControllerProvider::ShutdownSDL();
|
|
#endif
|
|
#if BOOST_OS_WINDOWS
|
|
ExitProcess(retValue);
|
|
#else
|
|
_Exit(retValue);
|
|
#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();
|
|
}
|