2022-08-22 22:21:23 +02:00
# 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"
2023-03-29 15:28:17 +01:00
# if BOOST_OS_LINUX && HAS_WAYLAND
# include "gui/helpers/wxWayland.h"
# endif
2022-08-22 22:21:23 +02:00
# include <wx/image.h>
# include <wx/filename.h>
2022-10-11 23:03:26 -07:00
# include <wx/stdpaths.h>
2023-01-29 12:53:31 +00:00
# include "wxHelper.h"
2022-08-22 22:21:23 +02:00
# 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 ;
2022-10-11 23:03:26 -07:00
int mainEmulatorHLE ( ) ;
void HandlePostUpdate ( ) ;
2022-08-22 22:21:23 +02:00
// 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 " ) ) ;
}
bool CemuApp : : OnInit ( )
{
2022-10-11 23:03:26 -07:00
fs : : path user_data_path , config_path , cache_path , data_path ;
auto standardPaths = wxStandardPaths : : Get ( ) ;
fs : : path exePath ( standardPaths . GetExecutablePath ( ) . ToStdString ( ) ) ;
2023-04-16 11:25:44 +02:00
# ifdef PORTABLE
2022-10-23 15:58:28 +01:00
# if MACOS_BUNDLE
exePath = exePath . parent_path ( ) . parent_path ( ) . parent_path ( ) ;
# endif
2022-10-11 23:03:26 -07:00
user_data_path = config_path = cache_path = data_path = exePath . parent_path ( ) ;
# else
SetAppName ( " Cemu " ) ;
wxString appName = GetAppName ( ) ;
2022-10-12 04:10:57 -07:00
# if BOOST_OS_LINUX
2022-10-11 23:03:26 -07:00
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
2023-04-16 11:25:44 +02:00
auto failed_write_access = ActiveSettings : : LoadOnce ( exePath , user_data_path , config_path , cache_path , data_path ) ;
2022-10-11 23:03:26 -07:00
for ( auto & & path : failed_write_access )
2023-09-08 02:09:03 +02:00
wxMessageBox ( formatWxString ( _ ( " Cemu can't write to {}! " ) , path . generic_string ( ) ) ,
_ ( " Warning " ) , wxOK | wxCENTRE | wxICON_EXCLAMATION , nullptr ) ;
2022-10-11 23:03:26 -07:00
NetworkConfig : : LoadOnce ( ) ;
2023-07-21 13:54:07 +02:00
g_config . Load ( ) ;
2022-10-11 23:03:26 -07:00
HandlePostUpdate ( ) ;
mainEmulatorHLE ( ) ;
2022-08-22 22:21:23 +02:00
wxInitAllImageHandlers ( ) ;
2023-10-16 13:41:06 +02:00
LocalizeUI ( ) ;
2022-08-22 22:21:23 +02:00
// 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
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 ( ) ;
2022-12-07 00:48:24 +00:00
2023-03-29 15:28:17 +01:00
# if BOOST_OS_LINUX && HAS_WAYLAND
if ( wxWlIsWaylandWindow ( m_mainFrame ) )
wxWlSetAppId ( m_mainFrame , " info.cemu.Cemu " ) ;
# endif
2022-12-07 00:48:24 +00:00
// 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. " ) ;
2023-09-14 12:47:59 +02:00
wxMessageDialog dialog ( nullptr , message , _ ( " Preview version " ) , wxCENTRE | wxOK | wxICON_WARNING ) ;
2022-12-07 00:48:24 +00:00
dialog . SetOKLabel ( _ ( " I understand " ) ) ;
dialog . ShowModal ( ) ;
GetConfig ( ) . did_show_macos_disclaimer = true ;
g_config . Save ( ) ;
}
# endif
2022-08-22 22:21:23 +02:00
return true ;
}
int CemuApp : : OnExit ( )
{
wxApp : : OnExit ( ) ;
2022-08-26 04:03:26 +02:00
# if BOOST_OS_WINDOWS
2022-08-22 22:21:23 +02:00
ExitProcess ( 0 ) ;
# else
2022-09-08 17:05:31 +02:00
_Exit ( 0 ) ;
2022-08-22 22:21:23 +02:00
# 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! " ) ;
2023-09-28 02:51:40 +02:00
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 ( ) ) ;
2022-08-22 22:21:23 +02:00
# 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 ) ;
2022-09-18 18:07:26 -07:00
g_window_info . set_keystate ( fix_raw_keycode ( key_event . GetRawKeyCode ( ) , key_event . GetRawKeyFlags ( ) ) , true ) ;
2022-08-22 22:21:23 +02:00
}
else if ( event . GetEventType ( ) = = wxEVT_KEY_UP )
{
const auto & key_event = ( wxKeyEvent & ) event ;
2022-09-18 18:07:26 -07:00
g_window_info . set_keystate ( fix_raw_keycode ( key_event . GetRawKeyCode ( ) , key_event . GetRawKeyFlags ( ) ) , false ) ;
}
2022-11-24 12:29:29 +01:00
else if ( event . GetEventType ( ) = = wxEVT_ACTIVATE_APP )
2022-09-18 18:07:26 -07:00
{
2022-11-24 12:29:29 +01:00
const auto & activate_event = ( wxActivateEvent & ) event ;
if ( ! activate_event . GetActive ( ) )
g_window_info . set_keystatesup ( ) ;
2022-08-22 22:21:23 +02:00
}
return wxApp : : FilterEvent ( event ) ;
}
2023-10-16 13:41:06 +02:00
std : : vector < const wxLanguageInfo * > CemuApp : : GetLanguages ( ) const {
std : : vector availableLanguages ( m_availableTranslations ) ;
availableLanguages . insert ( availableLanguages . begin ( ) , wxLocale : : GetLanguageInfo ( wxLANGUAGE_ENGLISH ) ) ;
return availableLanguages ;
}
2022-08-22 22:21:23 +02:00
2023-10-16 13:41:06 +02:00
void CemuApp : : LocalizeUI ( )
{
std : : unique_ptr < wxTranslations > translationsMgr ( new wxTranslations ( ) ) ;
m_availableTranslations = GetAvailableTranslationLanguages ( translationsMgr . get ( ) ) ;
2022-08-22 22:21:23 +02:00
2023-10-16 13:41:06 +02:00
const sint32 configuredLanguage = GetConfig ( ) . language ;
bool isTranslationAvailable = std : : any_of ( m_availableTranslations . begin ( ) , m_availableTranslations . end ( ) ,
[ configuredLanguage ] ( const wxLanguageInfo * info ) { return info - > Language = = configuredLanguage ; } ) ;
if ( configuredLanguage = = wxLANGUAGE_DEFAULT | | isTranslationAvailable )
{
translationsMgr - > SetLanguage ( static_cast < wxLanguage > ( configuredLanguage ) ) ;
translationsMgr - > AddCatalog ( " cemu " ) ;
2022-08-22 22:21:23 +02:00
2023-10-16 13:41:06 +02:00
if ( translationsMgr - > IsLoaded ( " cemu " ) & & wxLocale : : IsAvailable ( configuredLanguage ) )
m_locale . Init ( configuredLanguage ) ;
2022-08-22 22:21:23 +02:00
2023-10-16 13:41:06 +02:00
// 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 ( ) ) ;
2022-08-22 22:21:23 +02:00
}
2023-10-16 13:41:06 +02:00
}
2022-08-22 22:21:23 +02:00
2023-10-16 13:41:06 +02:00
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 ;
2022-08-22 22:21:23 +02:00
}
void CemuApp : : CreateDefaultFiles ( bool first_start )
{
2023-01-29 12:53:31 +00:00
fs : : path mlc = ActiveSettings : : GetMlcPath ( ) ;
2022-08-22 22:21:23 +02:00
// check for mlc01 folder missing if custom path has been set
if ( ! fs : : exists ( mlc ) & & ! first_start )
{
2023-09-08 02:09:03 +02:00
const wxString message = formatWxString ( _ ( " Your mlc01 folder seems to be missing. \n \n This is where Cemu stores save files, game updates and other Wii U files. \n \n The expected path is: \n {} \n \n Do you want to create the folder at the expected path? " ) ,
_pathToUtf8 ( mlc ) ) ;
2022-08-22 22:21:23 +02:00
2023-09-08 02:09:03 +02:00
wxMessageDialog dialog ( nullptr , message , _ ( " Error " ) , wxCENTRE | wxYES_NO | wxCANCEL | wxICON_WARNING ) ;
2022-08-22 22:21:23 +02:00
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 ;
2023-01-29 12:53:31 +00:00
mlc = ActiveSettings : : GetMlcPath ( ) ;
2022-08-22 22:21:23 +02:00
}
else
{
2023-01-29 12:53:31 +00:00
GetConfig ( ) . mlc_path = " " ;
2022-08-22 22:21:23 +02:00
g_config . Save ( ) ;
}
}
// create sys/usr folder in mlc01
try
{
2023-01-29 12:53:31 +00:00
const auto sysFolder = fs : : path ( mlc ) . append ( " sys " ) ;
2022-08-22 22:21:23 +02:00
fs : : create_directories ( sysFolder ) ;
2023-01-29 12:53:31 +00:00
const auto usrFolder = fs : : path ( mlc ) . append ( " usr " ) ;
2022-08-22 22:21:23 +02:00
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},
2023-01-29 12:53:31 +00:00
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 " ) ) ;
2022-08-22 22:21:23 +02:00
// lang files
2023-01-29 12:53:31 +00:00
const auto langDir = fs : : path ( mlc ) . append ( " sys/title/0005001b/1005c000/content " ) ;
2022-08-22 22:21:23 +02:00
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 )
{
2023-09-08 02:09:03 +02:00
wxString errorMsg = formatWxString ( _ ( " Couldn't create a required mlc01 subfolder or file! \n \n Error: {0} \n Target path: \n {1} " ) , ex . what ( ) , _pathToUtf8 ( mlc ) ) ;
2022-08-22 22:21:23 +02:00
2022-08-26 04:03:26 +02:00
# if BOOST_OS_WINDOWS
2022-08-22 22:21:23 +02:00
const DWORD lastError = GetLastError ( ) ;
if ( lastError ! = ERROR_SUCCESS )
errorMsg < < fmt : : format ( " \n \n {} " , GetSystemErrorMessage ( lastError ) ) ;
# endif
2023-09-08 02:09:03 +02:00
wxMessageBox ( errorMsg , _ ( " Error " ) , wxOK | wxCENTRE | wxICON_ERROR ) ;
2022-08-22 22:21:23 +02:00
exit ( 0 ) ;
}
// cemu directories
try
{
2023-01-29 12:53:31 +00:00
const auto controllerProfileFolder = ActiveSettings : : GetConfigPath ( " controllerProfiles " ) ;
2022-08-22 22:21:23 +02:00
if ( ! fs : : exists ( controllerProfileFolder ) )
fs : : create_directories ( controllerProfileFolder ) ;
2023-01-29 12:53:31 +00:00
const auto memorySearcherFolder = ActiveSettings : : GetUserDataPath ( " memorySearcher " ) ;
2022-08-22 22:21:23 +02:00
if ( ! fs : : exists ( memorySearcherFolder ) )
fs : : create_directories ( memorySearcherFolder ) ;
}
catch ( const std : : exception & ex )
{
2023-09-08 02:09:03 +02:00
wxString errorMsg = formatWxString ( _ ( " Couldn't create a required cemu directory or file! \n \n Error: {0} " ) , ex . what ( ) ) ;
2022-08-22 22:21:23 +02:00
2022-08-26 04:03:26 +02:00
# if BOOST_OS_WINDOWS
2022-08-22 22:21:23 +02:00
const DWORD lastError = GetLastError ( ) ;
if ( lastError ! = ERROR_SUCCESS )
errorMsg < < fmt : : format ( " \n \n {} " , GetSystemErrorMessage ( lastError ) ) ;
# endif
2023-09-08 02:09:03 +02:00
wxMessageBox ( errorMsg , _ ( " Error " ) , wxOK | wxCENTRE | wxICON_ERROR ) ;
2022-08-22 22:21:23 +02:00
exit ( 0 ) ;
}
}
2023-01-29 12:53:31 +00:00
bool CemuApp : : TrySelectMLCPath ( fs : : path path )
2022-10-20 13:18:44 +02:00
{
if ( path . empty ( ) )
2023-01-29 12:53:31 +00:00
path = ActiveSettings : : GetDefaultMLCPath ( ) ;
2022-10-20 13:18:44 +02:00
2023-01-29 12:53:31 +00:00
if ( ! TestWriteAccess ( path ) )
2022-10-20 13:18:44 +02:00
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 ;
}
2022-08-22 22:21:23 +02:00
bool CemuApp : : SelectMLCPath ( wxWindow * parent )
{
auto & config = GetConfig ( ) ;
2023-01-29 12:53:31 +00:00
fs : : path default_path ;
if ( fs : : exists ( _utf8ToPath ( config . mlc_path . GetValue ( ) ) ) )
default_path = _utf8ToPath ( config . mlc_path . GetValue ( ) ) ;
2022-08-22 22:21:23 +02:00
// try until users selects a valid path or aborts
while ( true )
{
2023-01-29 12:53:31 +00:00
wxDirDialog path_dialog ( parent , _ ( " Select a mlc directory " ) , wxHelper : : FromPath ( default_path ) , wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST ) ;
2022-08-22 22:21:23 +02:00
if ( path_dialog . ShowModal ( ) ! = wxID_OK | | path_dialog . GetPath ( ) . empty ( ) )
return false ;
const auto path = path_dialog . GetPath ( ) . ToStdWstring ( ) ;
2022-10-20 13:18:44 +02:00
if ( ! TrySelectMLCPath ( path ) )
2022-08-22 22:21:23 +02:00
{
const auto result = wxMessageBox ( _ ( " Cemu can't write to the selected mlc path! \n Do you want to select another path? " ) , _ ( " Error " ) , wxYES_NO | wxCENTRE | wxICON_ERROR ) ;
if ( result = = wxYES )
continue ;
2022-10-20 13:18:44 +02:00
2022-08-22 22:21:23 +02:00
break ;
}
return true ;
}
return false ;
}
void CemuApp : : ActivateApp ( wxActivateEvent & event )
{
g_window_info . app_active = event . GetActive ( ) ;
event . Skip ( ) ;
}