2023-01-21 00:53:49 +01:00
#include "stdafx.h"
2020-12-05 13:08:24 +01:00
#include "main_application.h"
2023-01-21 00:53:49 +01:00
#include "display_sleep_control.h"
2025-07-06 05:50:03 -04:00
#include "gamemode_control.h"
2026-04-18 16:09:01 +02:00
#include "rpcs3qt/config_database.h"
2019-06-18 04:02:06 +02:00
2020-12-22 11:42:57 +03:00
#include "util/types.hpp"
#include "util/logs.hpp"
2021-04-10 11:54:52 +02:00
#include "util/sysinfo.hpp"
2020-12-22 11:42:57 +03:00
2021-09-07 21:56:49 +02:00
#include "Utilities/Thread.h"
2022-07-27 22:09:16 +02:00
#include "Utilities/File.h"
2019-12-22 17:39:42 +01:00
#include "Input/pad_thread.h"
2020-02-15 23:36:20 +01:00
#include "Emu/System.h"
2020-07-30 13:23:55 +02:00
#include "Emu/system_config.h"
2023-01-21 00:53:49 +01:00
#include "Emu/system_utils.hpp"
2020-07-30 13:23:55 +02:00
#include "Emu/IdManager.h"
2019-06-18 04:02:06 +02:00
#include "Emu/Io/Null/NullKeyboardHandler.h"
#include "Emu/Io/Null/NullMouseHandler.h"
#include "Emu/Io/KeyboardHandler.h"
#include "Emu/Io/MouseHandler.h"
2025-12-27 17:41:29 +01:00
#include "Emu/VFS.h"
2019-12-22 17:39:42 +01:00
#include "Input/basic_keyboard_handler.h"
#include "Input/basic_mouse_handler.h"
2024-02-05 23:49:00 +01:00
#include "Input/raw_mouse_handler.h"
2019-06-18 04:02:06 +02:00
#include "Emu/Audio/AudioBackend.h"
#include "Emu/Audio/Null/NullAudioBackend.h"
2022-07-09 00:13:38 +09:00
#include "Emu/Audio/Null/null_enumerator.h"
2021-11-25 03:41:05 +09:00
#include "Emu/Audio/Cubeb/CubebBackend.h"
2022-07-09 00:13:38 +09:00
#include "Emu/Audio/Cubeb/cubeb_enumerator.h"
2019-06-18 04:02:06 +02:00
#ifdef _WIN32
#include "Emu/Audio/XAudio2/XAudio2Backend.h"
2022-07-09 00:13:38 +09:00
#include "Emu/Audio/XAudio2/xaudio2_enumerator.h"
2019-06-18 04:02:06 +02:00
#endif
2019-10-24 21:26:29 +02:00
#ifdef HAVE_FAUDIO
#include "Emu/Audio/FAudio/FAudioBackend.h"
2022-07-09 00:13:38 +09:00
#include "Emu/Audio/FAudio/faudio_enumerator.h"
2019-10-24 21:26:29 +02:00
#endif
2019-06-18 04:02:06 +02:00
2025-12-27 17:41:29 +01:00
#include <QDateTime>
2026-08-08 21:26:49 +02:00
#include <QDir>
2022-01-13 06:24:04 +02:00
#include <QFileInfo> // This shouldn't be outside rpcs3qt...
2022-07-23 21:19:43 +02:00
#include <QImageReader> // This shouldn't be outside rpcs3qt...
2023-05-13 20:58:59 +02:00
#include <QStandardPaths> // This shouldn't be outside rpcs3qt...
2022-07-04 16:02:17 +03:00
#include <thread>
2022-01-13 06:24:04 +02:00
2020-02-18 20:42:55 +01:00
LOG_CHANNEL ( sys_log , "SYS" );
2026-06-02 07:55:40 +02:00
LOG_CHANNEL ( cfg_log , "CFG" );
2020-02-18 20:42:55 +01:00
2023-01-21 00:53:49 +01:00
namespace audio
{
extern void configure_audio ( bool force_reset = false );
extern void configure_rsxaudio ();
}
namespace rsx :: overlays
{
extern void reset_performance_overlay ();
2023-12-14 01:59:28 +01:00
extern void reset_debug_overlay ();
2023-01-21 00:53:49 +01:00
}
2024-11-26 11:01:31 +02:00
extern void qt_events_aware_op ( int repeat_duration_ms , std :: function < bool () > wrapped_op );
2026-06-02 07:55:40 +02:00
main_application :: main_application ()
: m_render_creator ( std :: make_shared < render_creator > ())
{
std :: set < video_renderer > supported_renderers ;
supported_renderers . insert ( video_renderer :: null );
if ( m_render_creator -> OpenGL . supported )
{
supported_renderers . insert ( video_renderer :: opengl );
}
// Make Vulkan default setting if it is supported
if ( m_render_creator -> Vulkan . supported && ! m_render_creator -> Vulkan . adapters . empty ())
{
const std :: string adapter = :: at32 ( m_render_creator -> Vulkan . adapters , 0 ). toStdString ();
cfg_log . notice ( "Setting the default renderer to Vulkan. Default GPU: '%s'" , adapter );
Emu . SetDefaultRenderer ( video_renderer :: vulkan );
Emu . SetDefaultGraphicsAdapter ( adapter );
supported_renderers . insert ( video_renderer :: vulkan );
}
else if ( m_render_creator -> OpenGL . supported )
{
cfg_log . notice ( "Setting the default renderer to OpenGl" );
Emu . SetDefaultRenderer ( video_renderer :: opengl );
}
Emu . SetSupportedRenderers ( supported_renderers );
}
2021-04-02 00:54:32 +02:00
/** Emu.Init() wrapper for user management */
2026-06-02 00:42:33 +02:00
void main_application :: InitializeEmulator ( const std :: string & user , bool show_gui , bool headless )
2019-06-18 04:02:06 +02:00
{
2019-12-03 08:32:28 +01:00
Emu . SetHasGui ( show_gui );
2026-06-02 00:42:33 +02:00
Emu . SetHeadless ( headless );
2021-04-02 00:54:32 +02:00
Emu . SetUsr ( user );
Emu . Init ();
2021-04-10 11:54:52 +02:00
// Log Firmware Version after Emu was initialized
const std :: string firmware_version = utils :: get_firmware_version ();
const std :: string firmware_string = firmware_version . empty () ? "Missing Firmware" : ( "Firmware version: " + firmware_version );
2021-05-19 14:30:39 +03:00
sys_log . always ()( "%s" , firmware_string );
2026-03-23 16:40:33 +01:00
rpcs3 :: utils :: configure_logs ( Emu . IsStopped ());
2019-06-18 04:02:06 +02:00
}
2023-01-21 00:53:49 +01:00
void main_application :: OnEmuSettingsChange ()
{
2026-03-23 16:40:33 +01:00
// Change logging
rpcs3 :: utils :: configure_logs ( Emu . IsStopped ());
2023-01-21 00:53:49 +01:00
if ( Emu . IsRunning ())
{
2025-02-24 21:41:14 +01:00
enable_display_sleep ( ! g_cfg . misc . prevent_display_sleep );
2023-01-21 00:53:49 +01:00
}
if ( ! Emu . IsStopped ())
{
// Force audio provider
2023-02-07 23:35:52 +01:00
g_cfg . audio . provider . set ( Emu . IsVsh () ? audio_provider :: rsxaudio : audio_provider :: cell_audio );
2023-01-21 00:53:49 +01:00
}
audio :: configure_audio ();
audio :: configure_rsxaudio ();
rsx :: overlays :: reset_performance_overlay ();
2023-12-14 01:59:28 +01:00
rsx :: overlays :: reset_debug_overlay ();
2023-01-21 00:53:49 +01:00
}
2019-06-18 04:02:06 +02:00
/** RPCS3 emulator has functions it desires to call from the GUI at times. Initialize them in here. */
EmuCallbacks main_application :: CreateCallbacks ()
{
2023-01-21 00:53:49 +01:00
EmuCallbacks callbacks {};
callbacks . update_emu_settings = [ this ]()
{
Emu . CallFromMainThread ([ & ]()
{
OnEmuSettingsChange ();
});
};
2019-06-18 04:02:06 +02:00
2023-01-21 00:53:49 +01:00
callbacks . save_emu_settings = [ this ]()
{
Emu . BlockingCallFromMainThread ([ & ]()
{
Emulator :: SaveSettings ( g_cfg . to_string (), Emu . GetTitleID ());
});
};
2020-06-24 17:01:48 +02:00
callbacks . init_kb_handler = [ this ]()
2019-06-18 04:02:06 +02:00
{
2021-04-07 23:05:18 +02:00
switch ( g_cfg . io . keyboard . get ())
2019-06-18 04:02:06 +02:00
{
2019-09-19 01:50:08 +03:00
case keyboard_handler :: null :
{
2024-11-27 13:09:56 +02:00
ensure ( g_fxo -> init < KeyboardHandlerBase , NullKeyboardHandler > ( Emu . DeserialManager ()));
2019-09-19 01:50:08 +03:00
break ;
}
2019-06-18 04:02:06 +02:00
case keyboard_handler :: basic :
{
2022-07-04 16:02:17 +03:00
basic_keyboard_handler * ret = g_fxo -> init < KeyboardHandlerBase , basic_keyboard_handler > ( Emu . DeserialManager ());
2024-10-01 01:44:39 +02:00
ensure ( ret );
2019-06-18 04:02:06 +02:00
ret -> moveToThread ( get_thread ());
2025-01-03 20:22:36 +01:00
ret -> SetTargetWindow ( reinterpret_cast < QWindow *> ( m_game_window ));
2019-09-19 01:50:08 +03:00
break ;
2019-06-18 04:02:06 +02:00
}
}
};
2020-06-24 17:01:48 +02:00
callbacks . init_mouse_handler = [ this ]()
2019-06-18 04:02:06 +02:00
{
2024-06-27 20:56:50 +02:00
mouse_handler handler = g_cfg . io . mouse ;
if ( handler == mouse_handler :: null )
2019-09-19 01:50:08 +03:00
{
2024-02-05 23:49:00 +01:00
switch ( g_cfg . io . move )
{
case move_handler :: mouse :
2024-06-27 20:56:50 +02:00
handler = mouse_handler :: basic ;
2024-02-05 23:49:00 +01:00
break ;
case move_handler :: raw_mouse :
2024-06-27 20:56:50 +02:00
handler = mouse_handler :: raw ;
2024-02-05 23:49:00 +01:00
break ;
default :
break ;
}
2024-06-27 20:56:50 +02:00
}
2018-07-23 19:57:40 +02:00
2024-06-27 20:56:50 +02:00
switch ( handler )
{
case mouse_handler :: null :
{
2024-11-27 13:09:56 +02:00
ensure ( g_fxo -> init < MouseHandlerBase , NullMouseHandler > ( Emu . DeserialManager ()));
2019-09-19 01:50:08 +03:00
break ;
}
2019-06-18 04:02:06 +02:00
case mouse_handler :: basic :
{
2022-07-04 16:02:17 +03:00
basic_mouse_handler * ret = g_fxo -> init < MouseHandlerBase , basic_mouse_handler > ( Emu . DeserialManager ());
2024-10-01 01:44:39 +02:00
ensure ( ret );
2019-06-18 04:02:06 +02:00
ret -> moveToThread ( get_thread ());
2025-01-03 20:22:36 +01:00
ret -> SetTargetWindow ( reinterpret_cast < QWindow *> ( m_game_window ));
2019-09-19 01:50:08 +03:00
break ;
2019-06-18 04:02:06 +02:00
}
2024-02-05 23:49:00 +01:00
case mouse_handler :: raw :
{
2024-11-27 13:09:56 +02:00
ensure ( g_fxo -> init < MouseHandlerBase , raw_mouse_handler > ( Emu . DeserialManager ()));
2024-02-05 23:49:00 +01:00
break ;
}
2019-06-18 04:02:06 +02:00
}
};
2019-09-18 02:44:06 +03:00
callbacks . init_pad_handler = [ this ]( std :: string_view title_id )
2019-06-18 04:02:06 +02:00
{
2022-07-04 16:02:17 +03:00
ensure ( g_fxo -> init < named_thread < pad_thread >> ( get_thread (), m_game_window , title_id ));
2024-11-26 11:01:31 +02:00
qt_events_aware_op ( 0 , [](){ return !! pad :: g_started ; });
2019-06-18 04:02:06 +02:00
};
callbacks . get_audio = []() -> std :: shared_ptr < AudioBackend >
{
2020-02-18 20:42:55 +01:00
std :: shared_ptr < AudioBackend > result ;
2021-04-07 23:05:18 +02:00
switch ( g_cfg . audio . renderer . get ())
2019-06-18 04:02:06 +02:00
{
2020-02-18 20:42:55 +01:00
case audio_renderer :: null : result = std :: make_shared < NullAudioBackend > (); break ;
2019-06-18 04:02:06 +02:00
#ifdef _WIN32
2020-02-18 20:42:55 +01:00
case audio_renderer :: xaudio : result = std :: make_shared < XAudio2Backend > (); break ;
2019-06-18 04:02:06 +02:00
#endif
2021-11-25 03:41:05 +09:00
case audio_renderer :: cubeb : result = std :: make_shared < CubebBackend > (); break ;
2019-10-24 21:26:29 +02:00
#ifdef HAVE_FAUDIO
2020-02-18 20:42:55 +01:00
case audio_renderer :: faudio : result = std :: make_shared < FAudioBackend > (); break ;
2019-10-24 21:26:29 +02:00
#endif
2019-06-18 04:02:06 +02:00
}
2020-02-18 20:42:55 +01:00
if ( ! result -> Initialized ())
{
// Fall back to a null backend if something went wrong
2021-12-02 00:58:28 +01:00
sys_log . error ( "Audio renderer %s could not be initialized, using a Null renderer instead. Make sure that no other application is running that might block audio access (e.g. Netflix)." , result -> GetName ());
2020-02-18 20:42:55 +01:00
result = std :: make_shared < NullAudioBackend > ();
}
return result ;
2019-06-18 04:02:06 +02:00
};
2022-07-09 00:13:38 +09:00
callbacks . get_audio_enumerator = []( u64 renderer ) -> std :: shared_ptr < audio_device_enumerator >
{
switch ( static_cast < audio_renderer > ( renderer ))
{
case audio_renderer :: null : return std :: make_shared < null_enumerator > ();
#ifdef _WIN32
case audio_renderer :: xaudio : return std :: make_shared < xaudio2_enumerator > ();
#endif
case audio_renderer :: cubeb : return std :: make_shared < cubeb_enumerator > ();
#ifdef HAVE_FAUDIO
case audio_renderer :: faudio : return std :: make_shared < faudio_enumerator > ();
#endif
default : fmt :: throw_exception ( "Invalid renderer index %u" , renderer );
}
};
2022-07-23 21:19:43 +02:00
callbacks . get_image_info = []( const std :: string & filename , std :: string & sub_type , s32 & width , s32 & height , s32 & orientation ) -> bool
{
sub_type . clear ();
width = 0 ;
height = 0 ;
orientation = 0 ; // CELL_SEARCH_ORIENTATION_UNKNOWN
bool success = false ;
Emu . BlockingCallFromMainThread ([ & ]()
{
const QImageReader reader ( QString :: fromStdString ( filename ));
if ( reader . canRead ())
{
const QSize size = reader . size ();
width = size . width ();
height = size . height ();
sub_type = reader . subType (). toStdString ();
switch ( reader . transformation ())
{
case QImageIOHandler :: Transformation :: TransformationNone :
orientation = 1 ; // CELL_SEARCH_ORIENTATION_TOP_LEFT = 0°
break ;
case QImageIOHandler :: Transformation :: TransformationRotate90 :
orientation = 2 ; // CELL_SEARCH_ORIENTATION_TOP_RIGHT = 90°
break ;
case QImageIOHandler :: Transformation :: TransformationRotate180 :
orientation = 3 ; // CELL_SEARCH_ORIENTATION_BOTTOM_RIGHT = 180°
break ;
case QImageIOHandler :: Transformation :: TransformationRotate270 :
orientation = 4 ; // CELL_SEARCH_ORIENTATION_BOTTOM_LEFT = 270°
break ;
default :
// Ignore other transformations for now
break ;
}
success = true ;
sys_log . notice ( "get_image_info found image: filename='%s', sub_type='%s', width=%d, height=%d, orientation=%d" , filename , sub_type , width , height , orientation );
}
2022-07-28 20:57:13 +02:00
else
{
2023-06-12 03:45:37 +02:00
sys_log . error ( "get_image_info failed to read '%s'. Error='%s'" , filename , reader . errorString ());
2022-07-28 20:57:13 +02:00
}
2022-07-23 21:19:43 +02:00
});
return success ;
};
2022-07-28 20:57:13 +02:00
callbacks . get_scaled_image = []( const std :: string & path , s32 target_width , s32 target_height , s32 & width , s32 & height , u8 * dst , bool force_fit ) -> bool
2022-07-27 22:09:16 +02:00
{
width = 0 ;
height = 0 ;
if ( target_width <= 0 || target_height <= 0 || ! dst || ! fs :: is_file ( path ))
{
return false ;
}
bool success = false ;
Emu . BlockingCallFromMainThread ([ & ]()
{
2022-07-28 20:57:13 +02:00
// We use QImageReader instead of QImage. This way we can load and scale image in one step.
QImageReader reader ( QString :: fromStdString ( path ));
2022-07-27 22:09:16 +02:00
2022-07-28 20:57:13 +02:00
if ( reader . canRead ())
2022-07-27 22:09:16 +02:00
{
2022-07-28 20:57:13 +02:00
QSize size = reader . size ();
width = size . width ();
height = size . height ();
2022-07-27 22:09:16 +02:00
if ( width <= 0 || height <= 0 )
{
return ;
}
2022-07-28 20:57:13 +02:00
if ( force_fit || width > target_width || height > target_height )
2022-07-27 22:09:16 +02:00
{
2022-07-28 20:57:13 +02:00
const f32 target_ratio = target_width / static_cast < f32 > ( target_height );
const f32 image_ratio = width / static_cast < f32 > ( height );
const f32 convert_ratio = image_ratio / target_ratio ;
if ( convert_ratio > 1.0f )
{
size = QSize ( target_width , target_height / convert_ratio );
}
else if ( convert_ratio < 1.0f )
{
size = QSize ( target_width * convert_ratio , target_height );
}
else
{
size = QSize ( target_width , target_height );
}
reader . setScaledSize ( size );
width = size . width ();
height = size . height ();
2022-07-27 22:09:16 +02:00
}
2022-07-28 20:57:13 +02:00
QImage image = reader . read ();
2022-07-27 22:09:16 +02:00
if ( image . format () != QImage :: Format :: Format_RGBA8888 )
{
image = image . convertToFormat ( QImage :: Format :: Format_RGBA8888 );
}
2021-05-22 10:42:05 +02:00
std :: memcpy ( dst , image . constBits (), std :: min ( target_width * target_height * 4LL , image . height () * image . bytesPerLine ()));
2022-07-28 20:57:13 +02:00
success = true ;
sys_log . notice ( "get_scaled_image scaled image: path='%s', width=%d, height=%d" , path , width , height );
}
else
{
2023-06-12 03:45:37 +02:00
sys_log . error ( "get_scaled_image failed to read '%s'. Error='%s'" , path , reader . errorString ());
2022-07-27 22:09:16 +02:00
}
});
return success ;
};
2022-01-13 06:24:04 +02:00
callbacks . resolve_path = []( std :: string_view sv )
{
2023-03-04 18:12:45 +02:00
// May result in an empty string if path does not exist
2025-12-14 13:08:34 +00:00
return QFileInfo ( QString :: fromUtf8 ( sv . data (), static_cast < int > ( sv . size ()))). canonicalFilePath (). toStdString ();
2022-01-13 06:24:04 +02:00
};
2026-08-08 21:26:49 +02:00
callbacks . resolve_path_may_not_exist = []( std :: string_view sv )
{
const QString path = QString :: fromUtf8 ( sv . data (), static_cast < int > ( sv . size ()));
QFileInfo fi ( path );
QString tail ;
while ( ! fi . exists ())
{
tail = fi . fileName () + "/" + tail ;
fi . setFile ( fi . path ());
}
const QString result = QDir ( fi . canonicalFilePath ()). filePath ( tail );
return QDir :: cleanPath ( result ). toStdString ();
};
2023-05-13 20:58:59 +02:00
callbacks . get_font_dirs = []()
{
const QStringList locations = QStandardPaths :: standardLocations ( QStandardPaths :: FontsLocation );
std :: vector < std :: string > font_dirs ;
for ( const QString & location : locations )
{
std :: string font_dir = location . toStdString ();
if ( ! font_dir . ends_with ( '/' ))
{
font_dir += '/' ;
}
2026-06-10 02:15:16 +02:00
font_dirs . push_back ( std :: move ( font_dir ));
2023-05-13 20:58:59 +02:00
}
return font_dirs ;
};
2023-06-01 02:08:09 +02:00
callbacks . on_install_pkgs = []( const std :: vector < std :: string >& pkgs )
{
for ( const std :: string & pkg : pkgs )
{
if ( ! rpcs3 :: utils :: install_pkg ( pkg ))
{
sys_log . error ( "Failed to install %s" , pkg );
return false ;
}
}
return true ;
};
2025-07-06 05:50:03 -04:00
callbacks . enable_gamemode = []( bool enabled ){ enable_gamemode ( enabled ); };
2025-12-27 17:41:29 +01:00
callbacks . get_photo_path = []( std :: string_view title )
{
const QDateTime date_time = QDateTime :: currentDateTime ();
const QDate date = date_time . date ();
const QTime time = date_time . time ();
std :: string_view extension = ".png" ;
if ( const auto extension_start = title . find_last_of ( '.' );
extension_start != umax )
{
extension = title . substr ( extension_start );
title = title . substr ( 0 , extension_start );
}
std :: string suffix = std :: string ( extension );
const std :: string path = vfs :: get ( fmt :: format ( "/dev_hdd0/photo/%04d/%02d/%02d/%s %02d-%02d-%04d %02d-%02d-%02d" ,
date . year (), date . month (), date . day (), vfs :: escape ( title , true ),
date . day (), date . month (), date . year (), time . hour (), time . minute (), time . second ()));
u32 counter = 0 ;
while ( ! Emu . IsStopped () && fs :: is_file ( path + suffix ))
{
suffix = fmt :: format ( " %d%s" , ++ counter , extension );
}
return path + suffix ;
};
2026-04-18 16:09:01 +02:00
callbacks . get_database_config = []( const std :: string & title_id )
{
sys_log . notice ( "Trying to retrieve database config for: '%s'" , title_id );
2026-04-29 22:24:37 +02:00
if ( title_id . empty ())
{
sys_log . warning ( "Cannot retrieve database config for empty title_id" );
return std :: string ();
}
2026-04-27 21:14:32 +02:00
config_database config_db ( nullptr );
2026-04-18 16:09:01 +02:00
config_db . request_config_database ( false );
if ( ! config_db . has_config ( title_id ))
2026-04-29 22:24:37 +02:00
{
sys_log . notice ( "Cannot find database config for: '%s'" , title_id );
2026-04-18 16:09:01 +02:00
return std :: string ();
2026-04-29 22:24:37 +02:00
}
2026-04-18 16:09:01 +02:00
if ( const auto config = config_db . get_config ( title_id ))
{
sys_log . notice ( "Found database config for: '%s'" , title_id );
return config . value ();
}
2026-04-29 22:24:37 +02:00
sys_log . error ( "Failed to retrieve database config for: '%s'" , title_id );
2026-04-18 16:09:01 +02:00
return std :: string ();
};
2019-06-18 04:02:06 +02:00
return callbacks ;
}