#include "stdafx.h" #include "main_application.h" #include "display_sleep_control.h" #include "util/types.hpp" #include "util/logs.hpp" #include "util/sysinfo.hpp" #include "Utilities/Thread.h" #include "Utilities/File.h" #include "Input/pad_thread.h" #include "Emu/System.h" #include "Emu/system_config.h" #include "Emu/system_utils.hpp" #include "Emu/IdManager.h" #include "Emu/Io/Null/NullKeyboardHandler.h" #include "Emu/Io/Null/NullMouseHandler.h" #include "Emu/Io/KeyboardHandler.h" #include "Emu/Io/MouseHandler.h" #include "Input/basic_keyboard_handler.h" #include "Input/basic_mouse_handler.h" #include "Input/raw_mouse_handler.h" #include "Emu/Audio/AudioBackend.h" #include "Emu/Audio/Null/NullAudioBackend.h" #include "Emu/Audio/Null/null_enumerator.h" #include "Emu/Audio/Cubeb/CubebBackend.h" #include "Emu/Audio/Cubeb/cubeb_enumerator.h" #ifdef _WIN32 #include "Emu/Audio/XAudio2/XAudio2Backend.h" #include "Emu/Audio/XAudio2/xaudio2_enumerator.h" #endif #ifdef HAVE_FAUDIO #include "Emu/Audio/FAudio/FAudioBackend.h" #include "Emu/Audio/FAudio/faudio_enumerator.h" #endif #include // This shouldn't be outside rpcs3qt... #include // This shouldn't be outside rpcs3qt... #include // This shouldn't be outside rpcs3qt... #include LOG_CHANNEL(sys_log, "SYS"); namespace audio { extern void configure_audio(bool force_reset = false); extern void configure_rsxaudio(); } namespace rsx::overlays { extern void reset_performance_overlay(); extern void reset_debug_overlay(); } extern void qt_events_aware_op(int repeat_duration_ms, std::function wrapped_op); /** Emu.Init() wrapper for user management */ void main_application::InitializeEmulator(const std::string& user, bool show_gui) { Emu.SetHasGui(show_gui); Emu.SetUsr(user); Emu.Init(); // 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); sys_log.always()("%s", firmware_string); } void main_application::OnEmuSettingsChange() { if (Emu.IsRunning()) { if (g_cfg.misc.prevent_display_sleep) { disable_display_sleep(); } else { enable_display_sleep(); } } if (!Emu.IsStopped()) { // Change logging (only allowed during gameplay) rpcs3::utils::configure_logs(); // Force audio provider g_cfg.audio.provider.set(Emu.IsVsh() ? audio_provider::rsxaudio : audio_provider::cell_audio); } audio::configure_audio(); audio::configure_rsxaudio(); rsx::overlays::reset_performance_overlay(); rsx::overlays::reset_debug_overlay(); } /** RPCS3 emulator has functions it desires to call from the GUI at times. Initialize them in here. */ EmuCallbacks main_application::CreateCallbacks() { EmuCallbacks callbacks{}; callbacks.update_emu_settings = [this]() { Emu.CallFromMainThread([&]() { OnEmuSettingsChange(); }); }; callbacks.save_emu_settings = [this]() { Emu.BlockingCallFromMainThread([&]() { Emulator::SaveSettings(g_cfg.to_string(), Emu.GetTitleID()); }); }; callbacks.init_kb_handler = [this]() { switch (g_cfg.io.keyboard.get()) { case keyboard_handler::null: { ensure(g_fxo->init(Emu.DeserialManager())); break; } case keyboard_handler::basic: { basic_keyboard_handler* ret = g_fxo->init(Emu.DeserialManager()); ensure(ret); ret->moveToThread(get_thread()); ret->SetTargetWindow(reinterpret_cast(m_game_window)); break; } } }; callbacks.init_mouse_handler = [this]() { mouse_handler handler = g_cfg.io.mouse; if (handler == mouse_handler::null) { switch (g_cfg.io.move) { case move_handler::mouse: handler = mouse_handler::basic; break; case move_handler::raw_mouse: handler = mouse_handler::raw; break; default: break; } } switch (handler) { case mouse_handler::null: { ensure(g_fxo->init(Emu.DeserialManager())); break; } case mouse_handler::basic: { basic_mouse_handler* ret = g_fxo->init(Emu.DeserialManager()); ensure(ret); ret->moveToThread(get_thread()); ret->SetTargetWindow(reinterpret_cast(m_game_window)); break; } case mouse_handler::raw: { ensure(g_fxo->init(Emu.DeserialManager())); break; } } }; callbacks.init_pad_handler = [this](std::string_view title_id) { ensure(g_fxo->init>(get_thread(), m_game_window, title_id)); qt_events_aware_op(0, [](){ return !!pad::g_started; }); }; callbacks.get_audio = []() -> std::shared_ptr { std::shared_ptr result; switch (g_cfg.audio.renderer.get()) { case audio_renderer::null: result = std::make_shared(); break; #ifdef _WIN32 case audio_renderer::xaudio: result = std::make_shared(); break; #endif case audio_renderer::cubeb: result = std::make_shared(); break; #ifdef HAVE_FAUDIO case audio_renderer::faudio: result = std::make_shared(); break; #endif } if (!result->Initialized()) { // Fall back to a null backend if something went wrong 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()); result = std::make_shared(); } return result; }; callbacks.get_audio_enumerator = [](u64 renderer) -> std::shared_ptr { switch (static_cast(renderer)) { case audio_renderer::null: return std::make_shared(); #ifdef _WIN32 case audio_renderer::xaudio: return std::make_shared(); #endif case audio_renderer::cubeb: return std::make_shared(); #ifdef HAVE_FAUDIO case audio_renderer::faudio: return std::make_shared(); #endif default: fmt::throw_exception("Invalid renderer index %u", renderer); } }; 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); } else { sys_log.error("get_image_info failed to read '%s'. Error='%s'", filename, reader.errorString()); } }); return success; }; callbacks.get_scaled_image = [](const std::string& path, s32 target_width, s32 target_height, s32& width, s32& height, u8* dst, bool force_fit) -> bool { width = 0; height = 0; if (target_width <= 0 || target_height <= 0 || !dst || !fs::is_file(path)) { return false; } bool success = false; Emu.BlockingCallFromMainThread([&]() { // We use QImageReader instead of QImage. This way we can load and scale image in one step. QImageReader reader(QString::fromStdString(path)); if (reader.canRead()) { QSize size = reader.size(); width = size.width(); height = size.height(); if (width <= 0 || height <= 0) { return; } if (force_fit || width > target_width || height > target_height) { const f32 target_ratio = target_width / static_cast(target_height); const f32 image_ratio = width / static_cast(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(); } QImage image = reader.read(); if (image.format() != QImage::Format::Format_RGBA8888) { image = image.convertToFormat(QImage::Format::Format_RGBA8888); } std::memcpy(dst, image.constBits(), std::min(target_width * target_height * 4LL, image.height() * image.bytesPerLine())); success = true; sys_log.notice("get_scaled_image scaled image: path='%s', width=%d, height=%d", path, width, height); } else { sys_log.error("get_scaled_image failed to read '%s'. Error='%s'", path, reader.errorString()); } }); return success; }; callbacks.resolve_path = [](std::string_view sv) { // May result in an empty string if path does not exist return QFileInfo(QString::fromUtf8(sv.data(), static_cast(sv.size()))).canonicalFilePath().toStdString(); }; callbacks.get_font_dirs = []() { const QStringList locations = QStandardPaths::standardLocations(QStandardPaths::FontsLocation); std::vector font_dirs; for (const QString& location : locations) { std::string font_dir = location.toStdString(); if (!font_dir.ends_with('/')) { font_dir += '/'; } font_dirs.push_back(font_dir); } return font_dirs; }; callbacks.on_install_pkgs = [](const std::vector& 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; }; return callbacks; }