mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-29 20:44:18 +01:00
468 lines
14 KiB
C++
468 lines
14 KiB
C++
#include "gui/CemuApp.h"
|
|
#include "gui/MainWindow.h"
|
|
#include "gui/wxgui.h"
|
|
#include "config/CemuConfig.h"
|
|
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
|
#include "gui/guiWrapper.h"
|
|
#include "config/ActiveSettings.h"
|
|
#include "gui/GettingStartedDialog.h"
|
|
#include "config/PermanentConfig.h"
|
|
#include "config/PermanentStorage.h"
|
|
#include "input/InputManager.h"
|
|
#include "gui/helpers/wxHelpers.h"
|
|
#include "Cemu/ncrypto/ncrypto.h"
|
|
|
|
#if BOOST_OS_LINUX && HAS_WAYLAND
|
|
#include "gui/helpers/wxWayland.h"
|
|
#endif
|
|
|
|
#include <wx/image.h>
|
|
#include <wx/filename.h>
|
|
#include <wx/stdpaths.h>
|
|
#include "wxHelper.h"
|
|
|
|
#include "Cafe/TitleList/TitleList.h"
|
|
#include "Cafe/TitleList/SaveList.h"
|
|
|
|
wxIMPLEMENT_APP_NO_MAIN(CemuApp);
|
|
|
|
// defined in guiWrapper.cpp
|
|
extern WindowInfo g_window_info;
|
|
extern std::shared_mutex g_mutex;
|
|
|
|
int mainEmulatorHLE();
|
|
void HandlePostUpdate();
|
|
// Translation strings to extract for gettext:
|
|
void unused_translation_dummy()
|
|
{
|
|
void(_("Browse"));
|
|
void(_("Select a file"));
|
|
void(_("Select a directory"));
|
|
|
|
void(_("base"));
|
|
void(_("update"));
|
|
void(_("dlc"));
|
|
void(_("save"));
|
|
|
|
void(_("Japan"));
|
|
void(_("USA"));
|
|
void(_("Europe"));
|
|
void(_("Australia"));
|
|
void(_("China"));
|
|
void(_("Korea"));
|
|
void(_("Taiwan"));
|
|
void(_("Auto"));
|
|
void(_("many"));
|
|
|
|
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"));
|
|
|
|
|
|
// account.h
|
|
void(_("AccountId missing (The account is not connected to a NNID)"));
|
|
void(_("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)"));
|
|
void(_("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)"));
|
|
void(_("PrincipalId missing"));
|
|
}
|
|
|
|
bool CemuApp::OnInit()
|
|
{
|
|
fs::path user_data_path, config_path, cache_path, data_path;
|
|
auto standardPaths = wxStandardPaths::Get();
|
|
fs::path exePath(standardPaths.GetExecutablePath().ToStdString());
|
|
#ifdef PORTABLE
|
|
#if MACOS_BUNDLE
|
|
exePath = exePath.parent_path().parent_path().parent_path();
|
|
#endif
|
|
user_data_path = config_path = cache_path = data_path = exePath.parent_path();
|
|
#else
|
|
SetAppName("Cemu");
|
|
wxString appName=GetAppName();
|
|
#if BOOST_OS_LINUX
|
|
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();
|
|
#else
|
|
user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString();
|
|
#endif
|
|
data_path = standardPaths.GetDataDir().ToStdString();
|
|
cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString();
|
|
cache_path /= appName.ToStdString();
|
|
#endif
|
|
auto failed_write_access = ActiveSettings::LoadOnce(exePath, user_data_path, config_path, cache_path, data_path);
|
|
for (auto&& path : failed_write_access)
|
|
wxMessageBox(fmt::format("Cemu can't write to {} !", path.generic_string()), _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr);
|
|
|
|
NetworkConfig::LoadOnce();
|
|
|
|
HandlePostUpdate();
|
|
mainEmulatorHLE();
|
|
|
|
wxInitAllImageHandlers();
|
|
|
|
g_config.Load();
|
|
m_languages = GetAvailableLanguages();
|
|
|
|
const sint32 language = GetConfig().language;
|
|
const auto it = std::find_if(m_languages.begin(), m_languages.end(), [language](const wxLanguageInfo* info) { return info->Language == language; });
|
|
if (it != m_languages.end() && wxLocale::IsAvailable(language))
|
|
{
|
|
if (m_locale.Init(language))
|
|
{
|
|
m_locale.AddCatalogLookupPathPrefix(ActiveSettings::GetDataPath("resources").generic_string());
|
|
m_locale.AddCatalog("cemu");
|
|
}
|
|
}
|
|
|
|
if (!m_locale.IsOk())
|
|
{
|
|
if (!wxLocale::IsAvailable(wxLANGUAGE_DEFAULT) || !m_locale.Init(wxLANGUAGE_DEFAULT))
|
|
{
|
|
m_locale.Init(wxLANGUAGE_ENGLISH);
|
|
m_locale.AddCatalogLookupPathPrefix(ActiveSettings::GetDataPath("resources").generic_string());
|
|
m_locale.AddCatalog("cemu");
|
|
}
|
|
}
|
|
|
|
// 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
|
|
|
|
// init input
|
|
InputManager::instance().load();
|
|
|
|
InitializeGlobalVulkan();
|
|
|
|
Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this);
|
|
|
|
auto& config = GetConfig();
|
|
const bool first_start = !config.did_show_graphic_pack_download;
|
|
|
|
CreateDefaultFiles(first_start);
|
|
|
|
m_mainFrame = new MainWindow();
|
|
|
|
if (first_start)
|
|
m_mainFrame->ShowGettingStartedDialog();
|
|
|
|
std::unique_lock lock(g_mutex);
|
|
g_window_info.app_active = true;
|
|
|
|
SetTopWindow(m_mainFrame);
|
|
m_mainFrame->Show();
|
|
|
|
#if BOOST_OS_LINUX && 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 (!GetConfig().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();
|
|
GetConfig().did_show_macos_disclaimer = true;
|
|
g_config.Save();
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
int CemuApp::OnExit()
|
|
{
|
|
wxApp::OnExit();
|
|
#if BOOST_OS_WINDOWS
|
|
ExitProcess(0);
|
|
#else
|
|
_Exit(0);
|
|
#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, fmt::format(L"File: {0} Line: {1}", std::wstring_view(file), line));
|
|
cemuLog_log(LogType::Force, fmt::format(L"Func: {0} Cond: {1}", func, std::wstring_view(cond)));
|
|
cemuLog_log(LogType::Force, fmt::format(L"Message: {}", std::wstring_view(msg)));
|
|
|
|
#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;
|
|
wxGetKeyState(wxKeyCode::WXK_F17);
|
|
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();
|
|
}
|
|
|
|
return wxApp::FilterEvent(event);
|
|
}
|
|
|
|
std::vector<const wxLanguageInfo*> CemuApp::GetAvailableLanguages()
|
|
{
|
|
const auto path = ActiveSettings::GetDataPath("resources");
|
|
if (!exists(path))
|
|
return {};
|
|
|
|
std::vector<const wxLanguageInfo*> result;
|
|
for (const auto& p : fs::directory_iterator(path))
|
|
{
|
|
if (!fs::is_directory(p))
|
|
continue;
|
|
|
|
const auto& path = p.path();
|
|
auto filename = path.filename();
|
|
|
|
const auto* lang_info = wxLocale::FindLanguageInfo(filename.c_str());
|
|
if (!lang_info)
|
|
continue;
|
|
|
|
const auto language_file = path / "cemu.mo";
|
|
if (!fs::exists(language_file))
|
|
continue;
|
|
|
|
result.emplace_back(lang_info);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void CemuApp::CreateDefaultFiles(bool first_start)
|
|
{
|
|
fs::path mlc = ActiveSettings::GetMlcPath();
|
|
|
|
// check for mlc01 folder missing if custom path has been set
|
|
if (!fs::exists(mlc) && !first_start)
|
|
{
|
|
const std::wstring message = fmt::format(fmt::runtime(_(L"Your mlc01 folder seems to be missing.\n\nThis is where Cemu stores save files, game updates and other Wii U files.\n\nThe expected path is:\n{}\n\nDo you want to create the folder at the expected path?").ToStdWstring()), mlc.wstring());
|
|
|
|
wxMessageDialog dialog(nullptr, message, "Error", wxCENTRE | wxYES_NO | wxCANCEL| wxICON_WARNING);
|
|
dialog.SetYesNoCancelLabels(_("Yes"), _("No"), _("Select a custom path"));
|
|
const auto dialogResult = dialog.ShowModal();
|
|
if (dialogResult == wxID_NO)
|
|
exit(0);
|
|
else if(dialogResult == wxID_CANCEL)
|
|
{
|
|
if (!SelectMLCPath())
|
|
return;
|
|
mlc = ActiveSettings::GetMlcPath();
|
|
}
|
|
else
|
|
{
|
|
GetConfig().mlc_path = "";
|
|
g_config.Save();
|
|
}
|
|
}
|
|
|
|
// create sys/usr folder in mlc01
|
|
try
|
|
{
|
|
const auto sysFolder = fs::path(mlc).append("sys");
|
|
fs::create_directories(sysFolder);
|
|
|
|
const auto usrFolder = fs::path(mlc).append("usr");
|
|
fs::create_directories(usrFolder);
|
|
fs::create_directories(fs::path(usrFolder).append("title/00050000")); // base
|
|
fs::create_directories(fs::path(usrFolder).append("title/0005000c")); // dlc
|
|
fs::create_directories(fs::path(usrFolder).append("title/0005000e")); // update
|
|
|
|
// Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200},
|
|
fs::create_directories(fs::path(mlc).append("usr/save/00050010/1004a000/user/common/db"));
|
|
fs::create_directories(fs::path(mlc).append("usr/save/00050010/1004a100/user/common/db"));
|
|
fs::create_directories(fs::path(mlc).append("usr/save/00050010/1004a200/user/common/db"));
|
|
|
|
// lang files
|
|
const auto langDir = fs::path(mlc).append("sys/title/0005001b/1005c000/content");
|
|
fs::create_directories(langDir);
|
|
|
|
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 < 201; 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();
|
|
}
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
std::stringstream errorMsg;
|
|
errorMsg << fmt::format(fmt::runtime(_("Couldn't create a required mlc01 subfolder or file!\n\nError: {0}\nTarget path:\n{1}").ToStdString()), ex.what(), _pathToUtf8(mlc));
|
|
|
|
#if BOOST_OS_WINDOWS
|
|
const DWORD lastError = GetLastError();
|
|
if (lastError != ERROR_SUCCESS)
|
|
errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError));
|
|
|
|
wxMessageBox(errorMsg.str(), "Error", wxOK | wxCENTRE | wxICON_ERROR);
|
|
#endif
|
|
exit(0);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
std::stringstream errorMsg;
|
|
errorMsg << fmt::format(fmt::runtime(_("Couldn't create a required cemu directory or file!\n\nError: {0}").ToStdString()), ex.what());
|
|
|
|
#if BOOST_OS_WINDOWS
|
|
const DWORD lastError = GetLastError();
|
|
if (lastError != ERROR_SUCCESS)
|
|
errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError));
|
|
|
|
|
|
wxMessageBox(errorMsg.str(), "Error", wxOK | wxCENTRE | wxICON_ERROR);
|
|
#endif
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
|
|
bool CemuApp::TrySelectMLCPath(fs::path path)
|
|
{
|
|
if (path.empty())
|
|
path = ActiveSettings::GetDefaultMLCPath();
|
|
|
|
if (!TestWriteAccess(path))
|
|
return false;
|
|
|
|
GetConfig().SetMLCPath(path);
|
|
CemuApp::CreateDefaultFiles();
|
|
|
|
// update TitleList and SaveList scanner with new MLC path
|
|
CafeTitleList::SetMLCPath(path);
|
|
CafeTitleList::Refresh();
|
|
CafeSaveList::SetMLCPath(path);
|
|
CafeSaveList::Refresh();
|
|
return true;
|
|
}
|
|
|
|
bool CemuApp::SelectMLCPath(wxWindow* parent)
|
|
{
|
|
auto& config = GetConfig();
|
|
|
|
fs::path default_path;
|
|
if (fs::exists(_utf8ToPath(config.mlc_path.GetValue())))
|
|
default_path = _utf8ToPath(config.mlc_path.GetValue());
|
|
|
|
// try until users selects a valid path or aborts
|
|
while(true)
|
|
{
|
|
wxDirDialog path_dialog(parent, _("Select a mlc directory"), wxHelper::FromPath(default_path), wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
|
|
if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty())
|
|
return false;
|
|
|
|
const auto path = path_dialog.GetPath().ToStdWstring();
|
|
|
|
if (!TrySelectMLCPath(path))
|
|
{
|
|
const auto result = wxMessageBox(_("Cemu can't write to the selected mlc path!\nDo you want to select another path?"), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR);
|
|
if (result == wxYES)
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CemuApp::ActivateApp(wxActivateEvent& event)
|
|
{
|
|
g_window_info.app_active = event.GetActive();
|
|
event.Skip();
|
|
}
|
|
|
|
|