diff --git a/CMakeLists.txt b/CMakeLists.txt index 4822658c6..8ecbbf0d6 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,9 +115,20 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) -# Default to origin if there's no upstream set or if the command failed +# If there's no upstream set or the command failed, check remote.pushDefault if (GIT_BRANCH_RESULT OR GIT_REMOTE_NAME STREQUAL "") - set(GIT_REMOTE_NAME "origin") + execute_process( + COMMAND git config --get remote.pushDefault + OUTPUT_VARIABLE GIT_REMOTE_NAME + RESULT_VARIABLE GIT_PUSH_DEFAULT_RESULT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # If remote.pushDefault is not set or fails, default to origin + if (GIT_PUSH_DEFAULT_RESULT OR GIT_REMOTE_NAME STREQUAL "") + set(GIT_REMOTE_NAME "origin") + endif() else() # Extract remote name if the output contains a remote/branch format string(FIND "${GIT_REMOTE_NAME}" "/" INDEX) @@ -886,6 +897,9 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/cheats_patches.h src/qt_gui/compatibility_info.cpp src/qt_gui/compatibility_info.h + src/qt_gui/control_settings.cpp + src/qt_gui/control_settings.h + src/qt_gui/control_settings.ui src/qt_gui/main_window_ui.h src/qt_gui/main_window.cpp src/qt_gui/main_window.h diff --git a/REUSE.toml b/REUSE.toml index a62974bcd..63242edb2 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -38,6 +38,7 @@ path = [ "src/images/list_mode_icon.png", "src/images/pause_icon.png", "src/images/play_icon.png", + "src/images/ps4_controller.png", "src/images/refresh_icon.png", "src/images/settings_icon.png", "src/images/stop_icon.png", diff --git a/dist/net.shadps4.shadPS4.desktop b/dist/net.shadps4.shadPS4.desktop index fbefa0566..a87829e7b 100644 --- a/dist/net.shadps4.shadPS4.desktop +++ b/dist/net.shadps4.shadPS4.desktop @@ -5,5 +5,5 @@ Terminal=false Type=Application Icon=net.shadps4.shadPS4 Comment=PlayStation 4 emulator -Categories=Game; +Categories=Game;Emulator; StartupWMClass=shadps4; diff --git a/src/common/config.cpp b/src/common/config.cpp index 2059da0b3..d9dfb861f 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -95,6 +95,8 @@ std::vector m_pkg_viewer; std::vector m_elf_viewer; std::vector m_recent_files; std::string emulator_language = "en"; +static int backgroundImageOpacity = 50; +static bool showBackgroundImage = true; // Language u32 m_language = 1; // english @@ -611,6 +613,22 @@ u32 GetLanguage() { return m_language; } +int getBackgroundImageOpacity() { + return backgroundImageOpacity; +} + +void setBackgroundImageOpacity(int opacity) { + backgroundImageOpacity = std::clamp(opacity, 0, 100); +} + +bool getShowBackgroundImage() { + return showBackgroundImage; +} + +void setShowBackgroundImage(bool show) { + showBackgroundImage = show; +} + void load(const std::filesystem::path& path) { // If the configuration file does not exist, create it and return std::error_code error; @@ -731,6 +749,8 @@ void load(const std::filesystem::path& path) { m_recent_files = toml::find_or>(gui, "recentFiles", {}); m_table_mode = toml::find_or(gui, "gameTableMode", 0); emulator_language = toml::find_or(gui, "emulatorLanguage", "en"); + backgroundImageOpacity = toml::find_or(gui, "backgroundImageOpacity", 50); + showBackgroundImage = toml::find_or(gui, "showBackgroundImage", true); } if (data.contains("Settings")) { @@ -821,6 +841,8 @@ void save(const std::filesystem::path& path) { data["GUI"]["addonInstallDir"] = std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; data["GUI"]["emulatorLanguage"] = emulator_language; + data["GUI"]["backgroundImageOpacity"] = backgroundImageOpacity; + data["GUI"]["showBackgroundImage"] = showBackgroundImage; data["Settings"]["consoleLanguage"] = m_language; std::ofstream file(path, std::ios::binary); @@ -914,6 +936,8 @@ void setDefaultValues() { separateupdatefolder = false; compatibilityData = false; checkCompatibilityOnStartup = false; + backgroundImageOpacity = 50; + showBackgroundImage = true; } constexpr std::string_view GetDefaultKeyboardConfig() { diff --git a/src/common/config.h b/src/common/config.h index 77ed69ece..69e497527 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -30,6 +30,8 @@ bool getEnableDiscordRPC(); bool getSeparateUpdateEnabled(); bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); +int getBackgroundImageOpacity(); +bool getShowBackgroundImage(); std::string getLogFilter(); std::string getLogType(); @@ -88,6 +90,8 @@ void setGameInstallDirs(const std::vector& settings_insta void setSaveDataPath(const std::filesystem::path& path); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); +void setBackgroundImageOpacity(int opacity); +void setShowBackgroundImage(bool show); void setCursorState(s16 cursorState); void setCursorHideTimeout(int newcursorHideTimeout); diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 5f6c2172d..b4fa204bc 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -23,8 +23,8 @@ std::string FormatLogMessage(const Entry& entry) { const char* class_name = GetLogClassName(entry.log_class); const char* level_name = GetLevelName(entry.log_level); - return fmt::format("[{}] <{}> {}:{}:{}: {}", class_name, level_name, entry.filename, - entry.function, entry.line_num, entry.message); + return fmt::format("[{}] <{}> {}:{} {}: {}", class_name, level_name, entry.filename, + entry.line_num, entry.function, entry.message); } void PrintMessage(const Entry& entry) { diff --git a/src/common/memory_patcher.cpp b/src/common/memory_patcher.cpp index f81a0ed83..899bb6bf3 100644 --- a/src/common/memory_patcher.cpp +++ b/src/common/memory_patcher.cpp @@ -174,7 +174,7 @@ void OnGameLoaded() { patchMask = MemoryPatcher::PatchMask::Mask_Jump32; MemoryPatcher::PatchMemory(currentPatchName, address, patchValue, false, - littleEndian, patchMask); + littleEndian, patchMask, maskOffsetValue); } } } @@ -278,6 +278,7 @@ void OnGameLoaded() { lineObject["Type"] = attributes.value("Type").toString(); lineObject["Address"] = attributes.value("Address").toString(); lineObject["Value"] = attributes.value("Value").toString(); + lineObject["Offset"] = attributes.value("Offset").toString(); linesArray.append(lineObject); } } @@ -321,7 +322,7 @@ void OnGameLoaded() { MemoryPatcher::PatchMemory(currentPatchName, address.toStdString(), patchValue.toStdString(), false, - littleEndian, patchMask); + littleEndian, patchMask, maskOffsetValue); } } } @@ -447,4 +448,4 @@ uintptr_t PatternScan(const std::string& signature) { return 0; } -} // namespace MemoryPatcher \ No newline at end of file +} // namespace MemoryPatcher diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index 64d4966c0..a4916208a 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -84,7 +84,11 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) { std::scoped_lock lock{m_mutex}; for (auto& event : m_events) { if (event.event.ident == ident && event.event.filter == filter) { - event.Trigger(trigger_data); + if (filter == SceKernelEvent::Filter::VideoOut) { + event.TriggerDisplay(trigger_data); + } else { + event.Trigger(trigger_data); + } has_found = true; } } diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index 2db5e6ca7..11c09bb37 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -9,6 +9,7 @@ #include #include +#include "common/rdtsc.h" #include "common/types.h" namespace Core::Loader { @@ -81,6 +82,25 @@ struct EqueueEvent { event.data = reinterpret_cast(data); } + void TriggerDisplay(void* data) { + is_triggered = true; + auto hint = reinterpret_cast(data); + if (hint != 0) { + auto hint_h = static_cast(hint >> 8) & 0xFFFFFF; + auto ident_h = static_cast(event.ident >> 40); + if ((static_cast(hint) & 0xFF) == event.ident && event.ident != 0xFE && + ((hint_h ^ ident_h) & 0xFF) == 0) { + auto time = Common::FencedRDTSC(); + auto mask = 0xF000; + if ((static_cast(event.data) & 0xF000) != 0xF000) { + mask = (static_cast(event.data) + 0x1000) & 0xF000; + } + event.data = (mask | static_cast(static_cast(time) & 0xFFF) | + (hint & 0xFFFFFFFFFFFF0000)); + } + } + } + bool IsTriggered() const { return is_triggered; } diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index c21257c50..3a747bf16 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -13,7 +13,6 @@ namespace Libraries::Kernel { int PS4_SYSV_ABI sceKernelIsNeoMode() { - LOG_DEBUG(Kernel_Sce, "called"); return Config::isNeoModeConsole() && Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode; } diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index de5421fd7..0f832910c 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -185,9 +185,11 @@ void VideoOutDriver::Flip(const Request& req) { // Trigger flip events for the port. for (auto& event : port->flip_events) { if (event != nullptr) { - event->TriggerEvent(u64(OrbisVideoOutEventId::Flip), - Kernel::SceKernelEvent::Filter::VideoOut, - reinterpret_cast(req.flip_arg)); + event->TriggerEvent( + static_cast(OrbisVideoOutInternalEventId::Flip), + Kernel::SceKernelEvent::Filter::VideoOut, + reinterpret_cast(static_cast(OrbisVideoOutInternalEventId::Flip) | + (req.flip_arg << 16))); } } @@ -323,7 +325,7 @@ void VideoOutDriver::PresentThread(std::stop_token token) { // Trigger flip events for the port. for (auto& event : main_port.vblank_events) { if (event != nullptr) { - event->TriggerEvent(u64(OrbisVideoOutEventId::Vblank), + event->TriggerEvent(static_cast(OrbisVideoOutInternalEventId::Vblank), Kernel::SceKernelEvent::Filter::VideoOut, nullptr); } } diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index 78a2b11a4..65713019c 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -50,7 +50,7 @@ s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, } Kernel::EqueueEvent event{}; - event.event.ident = u64(OrbisVideoOutEventId::Flip); + event.event.ident = static_cast(OrbisVideoOutInternalEventId::Flip); event.event.filter = Kernel::SceKernelEvent::Filter::VideoOut; event.event.flags = Kernel::SceKernelEvent::Flags::Add; event.event.udata = udata; @@ -63,6 +63,20 @@ s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, return ORBIS_OK; } +s32 PS4_SYSV_ABI sceVideoOutDeleteFlipEvent(Kernel::SceKernelEqueue eq, s32 handle) { + auto* port = driver->GetPort(handle); + if (port == nullptr) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; + } + + if (eq == nullptr) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; + } + eq->RemoveEvent(handle, Kernel::SceKernelEvent::Filter::VideoOut); + port->flip_events.erase(find(port->flip_events.begin(), port->flip_events.end(), eq)); + return ORBIS_OK; +} + s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata) { LOG_INFO(Lib_VideoOut, "handle = {}", handle); @@ -76,7 +90,7 @@ s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handl } Kernel::EqueueEvent event{}; - event.event.ident = u64(OrbisVideoOutEventId::Vblank); + event.event.ident = static_cast(OrbisVideoOutInternalEventId::Vblank); event.event.filter = Kernel::SceKernelEvent::Filter::VideoOut; event.event.flags = Kernel::SceKernelEvent::Flags::Add; event.event.udata = udata; @@ -156,9 +170,27 @@ int PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev) { return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; } if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) { - return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; + return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; + } + + OrbisVideoOutInternalEventId internal_event_id = + static_cast(ev->ident); + switch (internal_event_id) { + case OrbisVideoOutInternalEventId::Flip: + return static_cast(OrbisVideoOutEventId::Flip); + case OrbisVideoOutInternalEventId::Vblank: + case OrbisVideoOutInternalEventId::SysVblank: + return static_cast(OrbisVideoOutEventId::Vblank); + case OrbisVideoOutInternalEventId::PreVblankStart: + return static_cast(OrbisVideoOutEventId::PreVblankStart); + case OrbisVideoOutInternalEventId::SetMode: + return static_cast(OrbisVideoOutEventId::SetMode); + case OrbisVideoOutInternalEventId::Position: + return static_cast(OrbisVideoOutEventId::Position); + default: { + return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; + } } - return ev->ident; } int PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, int64_t* data) { @@ -356,6 +388,8 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceVideoOutColorSettingsSetGamma); LIB_FUNCTION("pv9CI5VC+R0", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutAdjustColor); + LIB_FUNCTION("-Ozn0F1AFRg", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutDeleteFlipEvent); // openOrbis appears to have libSceVideoOut_v1 module libSceVideoOut_v1.1 LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutOpen); diff --git a/src/core/libraries/videoout/video_out.h b/src/core/libraries/videoout/video_out.h index 5af9d550d..2918fac30 100644 --- a/src/core/libraries/videoout/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -40,7 +40,22 @@ constexpr int SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0; constexpr int SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_VR = 7; constexpr int SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8; -enum class OrbisVideoOutEventId : s16 { Flip = 0, Vblank = 1, PreVblankStart = 2 }; +enum class OrbisVideoOutEventId : s16 { + Flip = 0, + Vblank = 1, + PreVblankStart = 2, + SetMode = 8, + Position = 12, +}; + +enum class OrbisVideoOutInternalEventId : s16 { + Flip = 0x6, + Vblank = 0x7, + SetMode = 0x51, + Position = 0x58, + PreVblankStart = 0x59, + SysVblank = 0x63, +}; enum class AspectRatioMode : s32 { Ratio16_9 = 0, diff --git a/src/emulator.cpp b/src/emulator.cpp index ba8d8917c..cd981add2 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -201,12 +201,16 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector +#include +#include "common/path_util.h" +#include "control_settings.h" +#include "kbm_config_dialog.h" +#include "ui_control_settings.h" + +ControlSettings::ControlSettings(std::shared_ptr game_info_get, QWidget* parent) + : QDialog(parent), m_game_info(game_info_get), ui(new Ui::ControlSettings) { + + ui->setupUi(this); + ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); + + AddBoxItems(); + SetUIValuestoMappings(); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) { + if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { + SaveControllerConfig(true); + } else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { + SetDefault(); + } else if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) { + SaveControllerConfig(false); + } + }); + + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); + connect(ui->KBMButton, &QPushButton::clicked, this, [this] { + auto KBMWindow = new EditorDialog(this); + KBMWindow->exec(); + }); + connect(ui->ProfileComboBox, &QComboBox::currentTextChanged, this, [this] { + GetGameTitle(); + SetUIValuestoMappings(); + }); + + connect(ui->LeftDeadzoneSlider, &QSlider::valueChanged, this, + [this](int value) { ui->LeftDeadzoneValue->setText(QString::number(value)); }); + connect(ui->RightDeadzoneSlider, &QSlider::valueChanged, this, + [this](int value) { ui->RightDeadzoneValue->setText(QString::number(value)); }); + + connect(ui->LStickUpBox, &QComboBox::currentIndexChanged, this, + [this](int value) { ui->LStickDownBox->setCurrentIndex(value); }); + connect(ui->LStickDownBox, &QComboBox::currentIndexChanged, this, + [this](int value) { ui->LStickUpBox->setCurrentIndex(value); }); + connect(ui->LStickRightBox, &QComboBox::currentIndexChanged, this, + [this](int value) { ui->LStickLeftBox->setCurrentIndex(value); }); + connect(ui->LStickLeftBox, &QComboBox::currentIndexChanged, this, + [this](int value) { ui->LStickRightBox->setCurrentIndex(value); }); + + connect(ui->RStickUpBox, &QComboBox::currentIndexChanged, this, + [this](int value) { ui->RStickDownBox->setCurrentIndex(value); }); + connect(ui->RStickDownBox, &QComboBox::currentIndexChanged, this, + [this](int value) { ui->RStickUpBox->setCurrentIndex(value); }); + connect(ui->RStickRightBox, &QComboBox::currentIndexChanged, this, + [this](int value) { ui->RStickLeftBox->setCurrentIndex(value); }); + connect(ui->RStickLeftBox, &QComboBox::currentIndexChanged, this, + [this](int value) { ui->RStickRightBox->setCurrentIndex(value); }); +} + +void ControlSettings::SaveControllerConfig(bool CloseOnSave) { + QList list; + list << ui->RStickUpBox << ui->RStickRightBox << ui->LStickUpBox << ui->LStickRightBox; + int count_axis_left_x = 0, count_axis_left_y = 0, count_axis_right_x = 0, + count_axis_right_y = 0; + for (const auto& i : list) { + if (i->currentText() == "axis_left_x") { + count_axis_left_x = count_axis_left_x + 1; + } else if (i->currentText() == "axis_left_y") { + count_axis_left_y = count_axis_left_y + 1; + } else if (i->currentText() == "axis_right_x") { + count_axis_right_x = count_axis_right_x + 1; + } else if (i->currentText() == "axis_right_y") { + count_axis_right_y = count_axis_right_y + 1; + } + } + + if (count_axis_left_x > 1 | count_axis_left_y > 1 | count_axis_right_x > 1 | + count_axis_right_y > 1) { + QMessageBox::StandardButton nosave; + nosave = QMessageBox::information(this, "Unable to Save", + "Cannot bind axis values more than once"); + return; + } + + std::string config_id; + config_id = (ui->ProfileComboBox->currentText() == "Common Config") + ? "default" + : ui->ProfileComboBox->currentText().toStdString(); + const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); + + int lineCount = 0; + std::string line; + std::vector lines; + std::string output_string = "", input_string = ""; + std::fstream file(config_file); + + while (std::getline(file, line)) { + lineCount++; + + std::size_t comment_pos = line.find('#'); + if (comment_pos != std::string::npos) { + if (!line.contains("Range of deadzones")) + lines.push_back(line); + continue; + } + + std::size_t equal_pos = line.find('='); + if (equal_pos == std::string::npos) { + lines.push_back(line); + continue; + } + + output_string = line.substr(0, equal_pos - 1); + input_string = line.substr(equal_pos + 2); + + if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) != + ControllerInputs.end() || + output_string == "analog_deadzone") { + line.erase(); + continue; + } + lines.push_back(line); + } + + file.close(); + + input_string = "cross"; + output_string = ui->ABox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "circle"; + output_string = ui->BBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "square"; + output_string = ui->XBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "triangle"; + output_string = ui->YBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + lines.push_back(""); + + input_string = "l1"; + output_string = ui->LBBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "r1"; + output_string = ui->RBBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "l2"; + output_string = ui->LTBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "r2"; + output_string = ui->RTBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "l3"; + output_string = ui->LClickBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "r3"; + output_string = ui->RClickBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + lines.push_back(""); + + input_string = "back"; + output_string = ui->BackBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "options"; + output_string = ui->StartBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + lines.push_back(""); + + input_string = "pad_up"; + output_string = ui->DpadUpBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "pad_down"; + output_string = ui->DpadDownBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "pad_left"; + output_string = ui->DpadLeftBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "pad_right"; + output_string = ui->DpadRightBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + lines.push_back(""); + + input_string = "axis_left_x"; + output_string = ui->LStickRightBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "axis_left_y"; + output_string = ui->LStickUpBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "axis_right_x"; + output_string = ui->RStickRightBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + input_string = "axis_right_y"; + output_string = ui->RStickUpBox->currentText().toStdString(); + lines.push_back(output_string + " = " + input_string); + + lines.push_back(""); + lines.push_back("# Range of deadzones: 1 (almost none) to 127 (max)"); + + std::string deadzonevalue = std::to_string(ui->LeftDeadzoneSlider->value()); + lines.push_back("analog_deadzone = leftjoystick, " + deadzonevalue); + + deadzonevalue = std::to_string(ui->RightDeadzoneSlider->value()); + lines.push_back("analog_deadzone = rightjoystick, " + deadzonevalue); + + std::vector save; + bool CurrentLineEmpty = false, LastLineEmpty = false; + for (auto const& line : lines) { + LastLineEmpty = CurrentLineEmpty ? true : false; + CurrentLineEmpty = line.empty() ? true : false; + if (!CurrentLineEmpty || !LastLineEmpty) + save.push_back(line); + } + + std::ofstream output_file(config_file); + for (auto const& line : save) { + output_file << line << '\n'; + } + output_file.close(); + + Config::SetUseUnifiedInputConfig(!ui->PerGameCheckBox->isChecked()); + Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + + if (CloseOnSave) + QWidget::close(); +} + +void ControlSettings::SetDefault() { + ui->ABox->setCurrentIndex(0); + ui->BBox->setCurrentIndex(1); + ui->XBox->setCurrentIndex(2); + ui->YBox->setCurrentIndex(3); + ui->DpadUpBox->setCurrentIndex(11); + ui->DpadDownBox->setCurrentIndex(12); + ui->DpadLeftBox->setCurrentIndex(13); + ui->DpadRightBox->setCurrentIndex(14); + ui->LClickBox->setCurrentIndex(8); + ui->RClickBox->setCurrentIndex(9); + ui->LBBox->setCurrentIndex(4); + ui->RBBox->setCurrentIndex(5); + ui->LTBox->setCurrentIndex(6); + ui->RTBox->setCurrentIndex(7); + ui->StartBox->setCurrentIndex(10); + ui->BackBox->setCurrentIndex(15); + + ui->LStickUpBox->setCurrentIndex(1); + ui->LStickDownBox->setCurrentIndex(1); + ui->LStickLeftBox->setCurrentIndex(0); + ui->LStickRightBox->setCurrentIndex(0); + ui->RStickUpBox->setCurrentIndex(3); + ui->RStickDownBox->setCurrentIndex(3); + ui->RStickLeftBox->setCurrentIndex(2); + ui->RStickRightBox->setCurrentIndex(2); + + ui->LeftDeadzoneSlider->setValue(2); + ui->RightDeadzoneSlider->setValue(2); +} + +void ControlSettings::AddBoxItems() { + ui->DpadUpBox->addItems(ButtonOutputs); + ui->DpadDownBox->addItems(ButtonOutputs); + ui->DpadLeftBox->addItems(ButtonOutputs); + ui->DpadRightBox->addItems(ButtonOutputs); + ui->LBBox->addItems(ButtonOutputs); + ui->RBBox->addItems(ButtonOutputs); + ui->LTBox->addItems(ButtonOutputs); + ui->RTBox->addItems(ButtonOutputs); + ui->RClickBox->addItems(ButtonOutputs); + ui->LClickBox->addItems(ButtonOutputs); + ui->StartBox->addItems(ButtonOutputs); + ui->ABox->addItems(ButtonOutputs); + ui->BBox->addItems(ButtonOutputs); + ui->XBox->addItems(ButtonOutputs); + ui->YBox->addItems(ButtonOutputs); + ui->BackBox->addItems(ButtonOutputs); + + ui->LStickUpBox->addItems(StickOutputs); + ui->LStickDownBox->addItems(StickOutputs); + ui->LStickLeftBox->addItems(StickOutputs); + ui->LStickRightBox->addItems(StickOutputs); + ui->RStickUpBox->addItems(StickOutputs); + ui->RStickDownBox->addItems(StickOutputs); + ui->RStickLeftBox->addItems(StickOutputs); + ui->RStickRightBox->addItems(StickOutputs); + + ui->ProfileComboBox->addItem("Common Config"); + for (int i = 0; i < m_game_info->m_games.size(); i++) { + ui->ProfileComboBox->addItem(QString::fromStdString(m_game_info->m_games[i].serial)); + } + ui->ProfileComboBox->setCurrentText("Common Config"); + ui->TitleLabel->setText("Common Config"); +} + +void ControlSettings::SetUIValuestoMappings() { + std::string config_id; + config_id = (ui->ProfileComboBox->currentText() == "Common Config") + ? "default" + : ui->ProfileComboBox->currentText().toStdString(); + + const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); + std::ifstream file(config_file); + + bool CrossExists = false, CircleExists = false, SquareExists = false, TriangleExists = false, + L1Exists = false, L2Exists = false, L3Exists = false, R1Exists = false, R2Exists = false, + R3Exists = false, DPadUpExists = false, DPadDownExists = false, DPadLeftExists = false, + DPadRightExists = false, StartExists = false, BackExists = false, LStickXExists = false, + LStickYExists = false, RStickXExists = false, RStickYExists = false; + int lineCount = 0; + std::string line = ""; + while (std::getline(file, line)) { + lineCount++; + + line.erase(std::remove(line.begin(), line.end(), ' '), line.end()); + if (line.empty()) + continue; + + std::size_t comment_pos = line.find('#'); + if (comment_pos != std::string::npos) + line = line.substr(0, comment_pos); + + std::size_t equal_pos = line.find('='); + if (equal_pos == std::string::npos) + continue; + + std::string output_string = line.substr(0, equal_pos); + std::string input_string = line.substr(equal_pos + 1); + + if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) != + ControllerInputs.end() || + output_string == "analog_deadzone") { + if (input_string == "cross") { + ui->ABox->setCurrentText(QString::fromStdString(output_string)); + CrossExists = true; + } else if (input_string == "circle") { + ui->BBox->setCurrentText(QString::fromStdString(output_string)); + CircleExists = true; + } else if (input_string == "square") { + ui->XBox->setCurrentText(QString::fromStdString(output_string)); + SquareExists = true; + } else if (input_string == "triangle") { + ui->YBox->setCurrentText(QString::fromStdString(output_string)); + TriangleExists = true; + } else if (input_string == "l1") { + ui->LBBox->setCurrentText(QString::fromStdString(output_string)); + L1Exists = true; + } else if (input_string == "l2") { + ui->LTBox->setCurrentText(QString::fromStdString(output_string)); + L2Exists = true; + } else if (input_string == "r1") { + ui->RBBox->setCurrentText(QString::fromStdString(output_string)); + R1Exists = true; + } else if (input_string == "r2") { + ui->RTBox->setCurrentText(QString::fromStdString(output_string)); + R2Exists = true; + } else if (input_string == "l3") { + ui->LClickBox->setCurrentText(QString::fromStdString(output_string)); + L3Exists = true; + } else if (input_string == "r3") { + ui->RClickBox->setCurrentText(QString::fromStdString(output_string)); + R3Exists = true; + } else if (input_string == "pad_up") { + ui->DpadUpBox->setCurrentText(QString::fromStdString(output_string)); + DPadUpExists = true; + } else if (input_string == "pad_down") { + ui->DpadDownBox->setCurrentText(QString::fromStdString(output_string)); + DPadDownExists = true; + } else if (input_string == "pad_left") { + ui->DpadLeftBox->setCurrentText(QString::fromStdString(output_string)); + DPadLeftExists = true; + } else if (input_string == "pad_right") { + ui->DpadRightBox->setCurrentText(QString::fromStdString(output_string)); + DPadRightExists = true; + } else if (input_string == "options") { + ui->StartBox->setCurrentText(QString::fromStdString(output_string)); + StartExists = true; + } else if (input_string == "back") { + ui->BackBox->setCurrentText(QString::fromStdString(output_string)); + BackExists = true; + } else if (input_string == "axis_left_x") { + ui->LStickRightBox->setCurrentText(QString::fromStdString(output_string)); + ui->LStickLeftBox->setCurrentText(QString::fromStdString(output_string)); + LStickXExists = true; + } else if (input_string == "axis_left_y") { + ui->LStickUpBox->setCurrentText(QString::fromStdString(output_string)); + ui->LStickDownBox->setCurrentText(QString::fromStdString(output_string)); + LStickYExists = true; + } else if (input_string == "axis_right_x") { + ui->RStickRightBox->setCurrentText(QString::fromStdString(output_string)); + ui->RStickLeftBox->setCurrentText(QString::fromStdString(output_string)); + RStickXExists = true; + } else if (input_string == "axis_right_y") { + ui->RStickUpBox->setCurrentText(QString::fromStdString(output_string)); + ui->RStickDownBox->setCurrentText(QString::fromStdString(output_string)); + RStickYExists = true; + } else if (input_string.contains("leftjoystick")) { + std::size_t comma_pos = line.find(','); + int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); + ui->LeftDeadzoneSlider->setValue(deadzonevalue); + ui->LeftDeadzoneValue->setText(QString::number(deadzonevalue)); + } else if (input_string.contains("rightjoystick")) { + std::size_t comma_pos = line.find(','); + int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); + ui->RightDeadzoneSlider->setValue(deadzonevalue); + ui->RightDeadzoneValue->setText(QString::number(deadzonevalue)); + } + } + } + + // If an entry does not exist in the config file, we assume the user wants it unmapped + if (!CrossExists) + ui->ABox->setCurrentText("unmapped"); + if (!CircleExists) + ui->BBox->setCurrentText("unmapped"); + if (!SquareExists) + ui->XBox->setCurrentText("unmapped"); + if (!TriangleExists) + ui->YBox->setCurrentText("unmapped"); + if (!L1Exists) + ui->LBBox->setCurrentText("unmapped"); + if (!L2Exists) + ui->LTBox->setCurrentText("unmapped"); + if (!L3Exists) + ui->LClickBox->setCurrentText("unmapped"); + if (!R1Exists) + ui->RBBox->setCurrentText("unmapped"); + if (!R2Exists) + ui->RTBox->setCurrentText("unmapped"); + if (!R3Exists) + ui->RClickBox->setCurrentText("unmapped"); + if (!DPadUpExists) + ui->DpadUpBox->setCurrentText("unmapped"); + if (!DPadDownExists) + ui->DpadDownBox->setCurrentText("unmapped"); + if (!DPadLeftExists) + ui->DpadLeftBox->setCurrentText("unmapped"); + if (!DPadRightExists) + ui->DpadRightBox->setCurrentText("unmapped"); + if (!BackExists) + ui->BackBox->setCurrentText("unmapped"); + if (!StartExists) + ui->StartBox->setCurrentText("unmapped"); + + if (!LStickXExists) { + ui->LStickRightBox->setCurrentText("unmapped"); + ui->LStickLeftBox->setCurrentText("unmapped"); + } + if (!LStickYExists) { + ui->LStickUpBox->setCurrentText("unmapped"); + ui->LStickDownBox->setCurrentText("unmapped"); + } + if (!RStickXExists) { + ui->RStickRightBox->setCurrentText("unmapped"); + ui->RStickLeftBox->setCurrentText("unmapped"); + } + if (!RStickYExists) { + ui->RStickUpBox->setCurrentText("unmapped"); + ui->RStickDownBox->setCurrentText("unmapped"); + } + + file.close(); +} + +void ControlSettings::GetGameTitle() { + if (ui->ProfileComboBox->currentText() == "Common Config") { + ui->TitleLabel->setText("Common Config"); + } else { + for (int i = 0; i < m_game_info->m_games.size(); i++) { + if (m_game_info->m_games[i].serial == + ui->ProfileComboBox->currentText().toStdString()) { + ui->TitleLabel->setText(QString::fromStdString(m_game_info->m_games[i].name)); + } + } + } +} + +ControlSettings::~ControlSettings() {} diff --git a/src/qt_gui/control_settings.h b/src/qt_gui/control_settings.h new file mode 100644 index 000000000..04227f3a8 --- /dev/null +++ b/src/qt_gui/control_settings.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "game_info.h" + +namespace Ui { +class ControlSettings; +} + +class ControlSettings : public QDialog { + Q_OBJECT +public: + explicit ControlSettings(std::shared_ptr game_info_get, + QWidget* parent = nullptr); + ~ControlSettings(); + +private Q_SLOTS: + void SaveControllerConfig(bool CloseOnSave); + void SetDefault(); + +private: + std::unique_ptr ui; + std::shared_ptr m_game_info; + + void AddBoxItems(); + void SetUIValuestoMappings(); + void GetGameTitle(); + + const std::vector ControllerInputs = { + "cross", "circle", "square", "triangle", "l1", + "r1", "l2", "r2", "l3", + + "r3", "options", "pad_up", + + "pad_down", + + "pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x", + "axis_right_y", "back"}; + + const QStringList ButtonOutputs = {"cross", "circle", "square", "triangle", "l1", + "r1", "l2", "r2", "l3", + + "r3", "options", "pad_up", + + "pad_down", + + "pad_left", "pad_right", "touchpad", "unmapped"}; + + const QStringList StickOutputs = {"axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y", + "unmapped"}; +}; diff --git a/src/qt_gui/control_settings.ui b/src/qt_gui/control_settings.ui new file mode 100644 index 000000000..b6acb5ca9 --- /dev/null +++ b/src/qt_gui/control_settings.ui @@ -0,0 +1,1379 @@ + + + + ControlSettings + + + Qt::WindowModality::WindowModal + + + + 0 + 0 + 1012 + 721 + + + + Configure Controls + + + + :/rpcs3.ico:/rpcs3.ico + + + + + + QFrame::Shape::NoFrame + + + 0 + + + true + + + + + 0 + 0 + 994 + 673 + + + + + Control Settings + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + 5 + + + + + true + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + D-Pad + + + + 6 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 124 + 0 + + + + + 0 + 16777215 + + + + Up + + + + + + false + + + QComboBox::SizeAdjustPolicy::AdjustToContents + + + + + + + + + + + + + + + Left + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + Right + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + false + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 124 + 16777215 + + + + Down + + + + + + true + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Maximum + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Left Stick Deadzone (def:2 max:127) + + + + + + + 0 + 0 + + + + Left Deadzone + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + + 0 + 0 + + + + 1 + + + 127 + + + Qt::Orientation::Horizontal + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Left Stick + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 16777215 + 2121 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 124 + 16777215 + + + + Up + + + + + + true + + + + + + + + + + + + + + + Left + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + true + + + + + + + + + + + 179 + 16777215 + + + + Right + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + true + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 124 + 0 + + + + + 124 + 21212 + + + + Down + + + + + + true + + + false + + + false + + + + + + + + + + + + + + + + + + 0 + + + + + + 0 + 0 + + + + + 12 + true + + + + Config Selection + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + 9 + false + + + + + + + -1 + + + Common Config + + + + + + + + 10 + true + + + + Common Config + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + + + + + + + 0 + 0 + + + + + 9 + false + + + + Use per-game configs + + + + + + + + + + 0 + + + + + + + L1 / LB + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + L2 / LT + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + + + + + 10 + + + + + + true + + + + KBM Controls + + + + + + + true + + + + KBM Editor + + + + + + + + + + Back + + + + + + + + + + + + + + + + + + R1 / RB + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + R2 / RT + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + + + + + + 0 + 200 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 415 + 256 + + + + :/images/ps4_controller.png + + + true + + + Qt::AlignmentFlag::AlignBottom|Qt::AlignmentFlag::AlignHCenter + + + + + + + + + + 10 + + + QLayout::SizeConstraint::SetDefaultConstraint + + + + + L3 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + Options / Start + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + R3 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + + + + + 5 + + + + + + 0 + 0 + + + + Face Buttons + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 124 + 0 + + + + + 0 + 16777215 + + + + Triangle / Y + + + + + + true + + + + 0 + 0 + + + + + + + + + + + + + + + + Square / X + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + Circle / B + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 124 + 0 + + + + + 124 + 16777215 + + + + Cross / A + + + + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Maximum + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Right Stick Deadzone (def:2, max:127) + + + + + + + 0 + 0 + + + + Right Deadzone + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + + 0 + 0 + + + + 1 + + + 127 + + + Qt::Orientation::Horizontal + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Right Stick + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 124 + 1231321 + + + + Up + + + + + + true + + + + + + + + + + + + + + + Left + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + true + + + + + + + + + + Right + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 124 + 0 + + + + + 124 + 2121 + + + + Down + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::RestoreDefaults|QDialogButtonBox::StandardButton::Save + + + false + + + + + + + + + + diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index d719ac878..2db4b7e4e 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -38,17 +38,34 @@ GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) { - crtRow = currentRow; - crtColumn = currentColumn; - columnCnt = this->columnCount(); - - auto itemID = (crtRow * columnCnt) + currentColumn; - if (itemID > m_game_info->m_games.count() - 1) { + // Early exit for invalid indices + if (currentRow < 0 || currentColumn < 0) { cellClicked = false; validCellSelected = false; BackgroundMusicPlayer::getInstance().stopMusic(); return; } + + crtRow = currentRow; + crtColumn = currentColumn; + columnCnt = this->columnCount(); + + // Prevent integer overflow + if (columnCnt <= 0 || crtRow > (std::numeric_limits::max() / columnCnt)) { + cellClicked = false; + validCellSelected = false; + BackgroundMusicPlayer::getInstance().stopMusic(); + return; + } + + auto itemID = (crtRow * columnCnt) + currentColumn; + if (itemID < 0 || itemID > m_game_info->m_games.count() - 1) { + cellClicked = false; + validCellSelected = false; + BackgroundMusicPlayer::getInstance().stopMusic(); + return; + } + cellClicked = true; validCellSelected = true; SetGridBackgroundImage(crtRow, crtColumn); @@ -65,6 +82,8 @@ void GameGridFrame::PlayBackgroundMusic(QString path) { } void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool fromSearch) { + this->crtRow = -1; + this->crtColumn = -1; QVector m_games_; this->clearContents(); if (fromSearch) @@ -136,43 +155,48 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from } void GameGridFrame::SetGridBackgroundImage(int row, int column) { - int itemID = (row * this->columnCount()) + column; QWidget* item = this->cellWidget(row, column); - if (item) { - QString pic1Path; - Common::FS::PathToQString(pic1Path, (*m_games_shared)[itemID].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - (*m_games_shared)[itemID].serial / "pic1.png"; - QString blurredPic1PathQt; - Common::FS::PathToQString(blurredPic1PathQt, blurredPic1Path); - - backgroundImage = QImage(blurredPic1PathQt); - if (backgroundImage.isNull()) { - QImage image(pic1Path); - backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); - - std::filesystem::path img_path = - Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - (*m_games_shared)[itemID].serial; - std::filesystem::create_directories(img_path); - if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { - // qDebug() << "Error: Unable to save image."; - } - } - RefreshGridBackgroundImage(); + if (!item) { + // handle case where no item was clicked + return; } + + // If background images are hidden, clear the background image + if (!Config::getShowBackgroundImage()) { + backgroundImage = QImage(); + m_last_opacity = -1; // Reset opacity tracking when disabled + m_current_game_path.clear(); // Reset current game path + RefreshGridBackgroundImage(); + return; + } + + const auto& game = (*m_games_shared)[itemID]; + const int opacity = Config::getBackgroundImageOpacity(); + + // Recompute if opacity changed or we switched to a different game + if (opacity != m_last_opacity || game.pic_path != m_current_game_path) { + QImage original_image(QString::fromStdString(game.pic_path.string())); + if (!original_image.isNull()) { + backgroundImage = m_game_list_utils.ChangeImageOpacity( + original_image, original_image.rect(), opacity / 100.0f); + m_last_opacity = opacity; + m_current_game_path = game.pic_path; + } + } + + RefreshGridBackgroundImage(); } void GameGridFrame::RefreshGridBackgroundImage() { - if (!backgroundImage.isNull()) { - QPalette palette; + QPalette palette; + if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) { palette.setBrush(QPalette::Base, QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio))); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - this->setPalette(palette); } + QColor transparentColor = QColor(135, 206, 235, 40); + palette.setColor(QPalette::Highlight, transparentColor); + this->setPalette(palette); } bool GameGridFrame::IsValidCellSelected() { diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index 4825d6daf..370b71dcb 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -33,6 +33,8 @@ private: std::shared_ptr m_compat_info; std::shared_ptr> m_games_shared; bool validCellSelected = false; + int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation + std::filesystem::path m_current_game_path; // Track current game path to detect changes public: explicit GameGridFrame(std::shared_ptr game_info_get, diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index 99805cd52..380c79e70 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -19,7 +19,10 @@ public: QVector m_games; static bool CompareStrings(GameInfo& a, GameInfo& b) { - return a.name < b.name; + std::string name_a = a.name, name_b = b.name; + std::transform(name_a.begin(), name_a.end(), name_a.begin(), ::tolower); + std::transform(name_b.begin(), name_b.end(), name_b.begin(), ::tolower); + return name_a < name_b; } static GameInfo readGameInfo(const std::filesystem::path& filePath) { @@ -72,4 +75,4 @@ public: } return game; } -}; \ No newline at end of file +}; diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index f2d08f578..64c0f17ba 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -89,6 +89,7 @@ void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int if (!item) { return; } + m_current_item = item; // Store current item SetListBackgroundImage(item); PlayBackgroundMusic(item); } @@ -104,6 +105,7 @@ void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) { } void GameListFrame::PopulateGameList(bool isInitialPopulation) { + this->m_current_item = nullptr; // Do not show status column if it is not enabled this->setColumnHidden(2, !Config::getCompatibilityEnabled()); this->setColumnHidden(6, !Config::GetLoadGameSizeEnabled()); @@ -167,38 +169,41 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { return; } - QString pic1Path; - Common::FS::PathToQString(pic1Path, m_game_info->m_games[item->row()].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - m_game_info->m_games[item->row()].serial / "pic1.png"; - QString blurredPic1PathQt; - Common::FS::PathToQString(blurredPic1PathQt, blurredPic1Path); + // If background images are hidden, clear the background image + if (!Config::getShowBackgroundImage()) { + backgroundImage = QImage(); + m_last_opacity = -1; // Reset opacity tracking when disabled + m_current_game_path.clear(); // Reset current game path + RefreshListBackgroundImage(); + return; + } - backgroundImage = QImage(blurredPic1PathQt); - if (backgroundImage.isNull()) { - QImage image(pic1Path); - backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); + const auto& game = m_game_info->m_games[item->row()]; + const int opacity = Config::getBackgroundImageOpacity(); - std::filesystem::path img_path = - Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - m_game_info->m_games[item->row()].serial; - std::filesystem::create_directories(img_path); - if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { - // qDebug() << "Error: Unable to save image."; + // Recompute if opacity changed or we switched to a different game + if (opacity != m_last_opacity || game.pic_path != m_current_game_path) { + QImage original_image(QString::fromStdString(game.pic_path.string())); + if (!original_image.isNull()) { + backgroundImage = m_game_list_utils.ChangeImageOpacity( + original_image, original_image.rect(), opacity / 100.0f); + m_last_opacity = opacity; + m_current_game_path = game.pic_path; } } + RefreshListBackgroundImage(); } void GameListFrame::RefreshListBackgroundImage() { - if (!backgroundImage.isNull()) { - QPalette palette; + QPalette palette; + if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) { palette.setBrush(QPalette::Base, QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio))); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - this->setPalette(palette); } + QColor transparentColor = QColor(135, 206, 235, 40); + palette.setColor(QPalette::Highlight, transparentColor); + this->setPalette(palette); } void GameListFrame::SortNameAscending(int columnIndex) { @@ -392,3 +397,7 @@ QString GameListFrame::GetPlayTime(const std::string& serial) { file.close(); return playTime; } + +QTableWidgetItem* GameListFrame::GetCurrentItem() { + return m_current_item; +} \ No newline at end of file diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index 7e37c4ea7..b2e5f1e2f 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -44,11 +44,14 @@ private: QList m_columnActs; GameInfoClass* game_inf_get = nullptr; bool ListSortedAsc = true; + QTableWidgetItem* m_current_item = nullptr; + int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation + std::filesystem::path m_current_game_path; // Track current game path to detect changes public: void PopulateGameList(bool isInitialPopulation = true); void ResizeIcons(int iconSize); - + QTableWidgetItem* GetCurrentItem(); QImage backgroundImage; GameListUtils m_game_list_utils; GuiContextMenus m_gui_context_menus; diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 581a8a55f..c6b69e70e 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -201,4 +201,30 @@ public: return result; } + + // Opacity is a float between 0 and 1 + static QImage ChangeImageOpacity(const QImage& image, const QRect& rect, float opacity) { + // Convert to ARGB32 format to ensure alpha channel support + QImage result = image.convertToFormat(QImage::Format_ARGB32); + + // Ensure opacity is between 0 and 1 + opacity = std::clamp(opacity, 0.0f, 1.0f); + + // Convert opacity to integer alpha value (0-255) + int alpha = static_cast(opacity * 255); + + // Process only the specified rectangle area + for (int y = rect.top(); y <= rect.bottom(); ++y) { + QRgb* line = reinterpret_cast(result.scanLine(y)); + for (int x = rect.left(); x <= rect.right(); ++x) { + // Get current pixel + QRgb pixel = line[x]; + // Keep RGB values, but modify alpha while preserving relative transparency + int newAlpha = (qAlpha(pixel) * alpha) / 255; + line[x] = qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), newAlpha); + } + } + + return result; + } }; diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index bdc2aec0c..262a1d733 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -77,10 +77,14 @@ public: QMenu* copyMenu = new QMenu(tr("Copy info..."), widget); QAction* copyName = new QAction(tr("Copy Name"), widget); QAction* copySerial = new QAction(tr("Copy Serial"), widget); + QAction* copyVersion = new QAction(tr("Copy Version"), widget); + QAction* copySize = new QAction(tr("Copy Size"), widget); QAction* copyNameAll = new QAction(tr("Copy All"), widget); copyMenu->addAction(copyName); copyMenu->addAction(copySerial); + copyMenu->addAction(copyVersion); + copyMenu->addAction(copySize); copyMenu->addAction(copyNameAll); menu.addMenu(copyMenu); @@ -346,6 +350,16 @@ public: clipboard->setText(QString::fromStdString(m_games[itemID].serial)); } + if (selected == copyVersion) { + QClipboard* clipboard = QGuiApplication::clipboard(); + clipboard->setText(QString::fromStdString(m_games[itemID].version)); + } + + if (selected == copySize) { + QClipboard* clipboard = QGuiApplication::clipboard(); + clipboard->setText(QString::fromStdString(m_games[itemID].size)); + } + if (selected == copyNameAll) { QClipboard* clipboard = QGuiApplication::clipboard(); QString combinedText = QString("Name:%1 | Serial:%2 | Version:%3 | Size:%4") diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index de3557992..67615a1b6 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -16,6 +16,7 @@ #include "common/scm_rev.h" #include "common/string_util.h" #include "common/version.h" +#include "control_settings.h" #include "core/file_format/pkg.h" #include "core/loader.h" #include "game_install_dialog.h" @@ -62,12 +63,16 @@ bool MainWindow::Init() { window_title = fmt::format("shadPS4 v{}", Common::VERSION); } else { std::string remote_url(Common::g_scm_remote_url); - if (remote_url == "https://github.com/shadps4-emu/shadPS4.git" || - remote_url.length() == 0) { + std::string remote_host; + try { + remote_host = remote_url.substr(19, remote_url.rfind('/') - 19); + } catch (...) { + remote_host = "unknown"; + } + if (remote_host == "shadps4-emu" || remote_url.length() == 0) { window_title = fmt::format("shadPS4 v{} {} {}", Common::VERSION, Common::g_scm_branch, Common::g_scm_desc); } else { - std::string remote_host = remote_url.substr(19, remote_url.rfind('/') - 19); window_title = fmt::format("shadPS4 v{} {}/{} {}", Common::VERSION, remote_host, Common::g_scm_branch, Common::g_scm_desc); } @@ -292,13 +297,30 @@ void MainWindow::CreateConnects() { connect(settingsDialog, &SettingsDialog::CompatibilityChanged, this, &MainWindow::RefreshGameTable); + connect(settingsDialog, &SettingsDialog::BackgroundOpacityChanged, this, + [this](int opacity) { + Config::setBackgroundImageOpacity(opacity); + if (m_game_list_frame) { + QTableWidgetItem* current = m_game_list_frame->GetCurrentItem(); + if (current) { + m_game_list_frame->SetListBackgroundImage(current); + } + } + if (m_game_grid_frame) { + if (m_game_grid_frame->IsValidCellSelected()) { + m_game_grid_frame->SetGridBackgroundImage(m_game_grid_frame->crtRow, + m_game_grid_frame->crtColumn); + } + } + }); + settingsDialog->exec(); }); // this is the editor for kbm keybinds connect(ui->controllerButton, &QPushButton::clicked, this, [this]() { - EditorDialog* editorWindow = new EditorDialog(this); - editorWindow->exec(); // Show the editor window modally + auto configWindow = new ControlSettings(m_game_info, this); + configWindow->exec(); }); #ifdef ENABLE_UPDATER diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 802325126..8f4b22c6d 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -72,7 +72,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); // Add list of available GPUs - ui->graphicsAdapterBox->addItem("Auto Select"); // -1, auto selection + ui->graphicsAdapterBox->addItem(tr("Auto Select")); // -1, auto selection for (const auto& device : physical_devices) { ui->graphicsAdapterBox->addItem(device); } @@ -173,6 +173,9 @@ SettingsDialog::SettingsDialog(std::span physical_devices, { connect(ui->chooseHomeTabComboBox, &QComboBox::currentTextChanged, this, [](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); }); + + connect(ui->showBackgroundImageCheckBox, &QCheckBox::stateChanged, this, + [](int state) { Config::setShowBackgroundImage(state == Qt::Checked); }); } // Input TAB { @@ -251,6 +254,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, #ifdef ENABLE_UPDATER ui->updaterGroupBox->installEventFilter(this); #endif + ui->GUIBackgroundImageGroupBox->installEventFilter(this); ui->GUIMusicGroupBox->installEventFilter(this); ui->disableTrophycheckBox->installEventFilter(this); ui->enableCompatibilityCheckBox->installEventFilter(this); @@ -410,6 +414,8 @@ void SettingsDialog::LoadValuesFromConfig() { ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); ResetInstallFolders(); + ui->backgroundImageOpacitySlider->setValue(Config::getBackgroundImageOpacity()); + ui->showBackgroundImageCheckBox->setChecked(Config::getShowBackgroundImage()); } void SettingsDialog::InitializeEmulatorLanguages() { @@ -504,6 +510,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { } else if (elementName == "updaterGroupBox") { text = tr("updaterGroupBox"); #endif + } else if (elementName == "GUIBackgroundImageGroupBox") { + text = tr("GUIBackgroundImageGroupBox"); } else if (elementName == "GUIMusicGroupBox") { text = tr("GUIMusicGroupBox"); } else if (elementName == "disableTrophycheckBox") { @@ -638,6 +646,9 @@ void SettingsDialog::UpdateSettings() { Config::setChooseHomeTab(ui->chooseHomeTabComboBox->currentText().toStdString()); Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked()); Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->isChecked()); + Config::setBackgroundImageOpacity(ui->backgroundImageOpacitySlider->value()); + emit BackgroundOpacityChanged(ui->backgroundImageOpacitySlider->value()); + Config::setShowBackgroundImage(ui->showBackgroundImageCheckBox->isChecked()); #ifdef ENABLE_DISCORD_RPC auto* rpc = Common::Singleton::Instance(); diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index 892e67671..c440351f6 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -33,6 +33,7 @@ public: signals: void LanguageChanged(const std::string& locale); void CompatibilityChanged(); + void BackgroundOpacityChanged(int opacity); private: void LoadValuesFromConfig(); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index d15f49efe..80f7a117e 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -583,6 +583,76 @@ + + + + Background Image + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + Show Background Image + + + + + + + 9 + + + + + + 0 + 0 + + + + Opacity + + + + + + + + 0 + 0 + + + + 0 + + + 100 + + + 50 + + + Qt::Orientation::Horizontal + + + + + + + + diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index 71ee066c1..4985160ff 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -128,6 +128,14 @@ Copy Serial Seriennummer kopieren + + Copy Version + Version kopieren + + + Copy Size + Größe kopieren + Copy All Alles kopieren @@ -1421,4 +1429,31 @@ TB + + CompatibilityInfoClass + + Unknown + Unbekannt + + + Nothing + Nichts + + + Boots + Startet + + + Menus + Menüs + + + Ingame + ImSpiel + + + Playable + Spielbar + + diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 58d6e9aa8..d0540d7cd 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -124,6 +124,14 @@ Copy Serial Copy Serial + + Copy Version + Copy Version + + + Copy Size + Copy Size + Copy All Copy All @@ -749,6 +757,18 @@ Disable Trophy Pop-ups Disable Trophy Pop-ups + + Background Image + Background Image + + + Show Background Image + Show Background Image + + + Opacity + Opacity + Play title music Play title music @@ -845,6 +865,10 @@ updaterGroupBox Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. + + GUIBackgroundImageGroupBox + Background Image:\nControl the opacity of the game background image. + GUIMusicGroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index d732e67ea..772980994 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -748,6 +748,18 @@ Disable Trophy Pop-ups Disable Trophy Pop-ups + + Background Image + Imagen de fondo + + + Show Background Image + Mostrar Imagen de Fondo + + + Opacity + Opacidad + Play title music Reproducir la música de apertura @@ -844,6 +856,10 @@ updaterGroupBox Actualización:\nRelease: Versiones oficiales lanzadas cada mes que pueden estar muy desactualizadas, pero son más confiables y están probadas.\nNightly: Versiones de desarrollo que tienen todas las últimas funciones y correcciones, pero pueden contener errores y son menos estables. + + GUIBackgroundImageGroupBox + Imagen de fondo:\nControle la opacidad de la imagen de fondo del juego. + GUIMusicGroupBox Reproducir Música del Título:\nSi un juego lo admite, habilita la reproducción de música especial al seleccionar el juego en la interfaz gráfica. diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index f2ea4fcc7..efaaa9ad1 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -742,7 +742,7 @@ Title Music - Title Music + Musique du titre Disable Trophy Pop-ups @@ -958,7 +958,7 @@ crashDiagnosticsCheckBox - Diagnostic de crash:\nCrée un fichier .yaml avec des informations sur l'état de Vulkan au moment du crash.\nUtile pour déboguer les erreurs "Device lost". Si cette option est activée, vous devez aussi activer Marqueur de débogage hôte ET invité.\nNe marche pas pour les GPUs Intel.\nVous devez activer le Vulkan Validation Layers ainsi que le Vulkan SDK pour que cela fonctionne. + Diagnostic de crash:\nCrée un fichier .yaml avec des informations sur l'état de Vulkan au moment du crash.\nUtile pour déboguer les erreurs "Device lost". Si cette option est activée, vous devez aussi activer Marqueur de débogage hôte ET invité.\nNe marche pas pour les GPUs Intel.\nVous devez activer la couche de validation Vulkan ainsi que le Vulkan SDK pour que cela fonctionne. copyGPUBuffersCheckBox @@ -1405,4 +1405,31 @@ TB + + CompatibilityInfoClass + + Unknown + Inconnu + + + Nothing + Rien + + + Boots + Démarre + + + Menus + Menu + + + Ingame + En jeu + + + Playable + Jouable + + diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index bce5791af..b2a355c95 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -1,7 +1,7 @@ - AboutDialog @@ -1298,6 +1298,14 @@ Game can be completed with playable performance and no major glitches Spillet kan fullføres med spillbar ytelse og uten store feil + + Click to go to issue + Klikk for å gå til rapporten + + + Last updated + Sist oppdatert + CheckUpdate @@ -1425,4 +1433,43 @@ TB + + CompatibilityInfoClass + + Unknown + Ukjent + + + Nothing + Ingenting + + + Boots + Starter opp + + + Menus + Meny + + + Ingame + I spill + + + Playable + Spillbar + + + Fetching compatibility data, please wait + Henter kompatibilitetsdata, vennligst vent + + + Cancel + Avbryt + + + Loading... + Laster... + + diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 270396a6d..a8b3bacb4 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1,1408 +1,1611 @@ - - - AboutDialog - - About shadPS4 - О shadPS4 - - - shadPS4 - shadPS4 - - - shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 это экспериментальный эмулятор с открытым исходным кодом для PlayStation 4. - - - This software should not be used to play games you have not legally obtained. - Это програмное обеспечение не должно использоваться для запуска игр, которые вы получили нелегально. - - - - ElfViewer - - Open Folder - Открыть папку - - - - GameInfoClass - - Loading game list, please wait :3 - Загрузка списка игр, пожалуйста подождите :3 - - - Cancel - Отмена - - - Loading... - Загрузка... - - - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Выберите папку - - - Select which directory you want to install to. - Выберите папку, в которую вы хотите установить. - - - - GameInstallDialog - - shadPS4 - Choose directory - shadPS4 - Выберите папку - - - Directory to install games - Папка для установки игр - - - Browse - Обзор - - - Error - Ошибка - - - The value for location to install games is not valid. - Недопустимое значение местоположения для установки игр. - - - - GuiContextMenus - - Create Shortcut - Создать ярлык - - - Cheats / Patches - Читы и патчи - - - SFO Viewer - Просмотр SFO - - - Trophy Viewer - Просмотр трофеев - - - Open Folder... - Открыть папку... - - - Open Game Folder - Открыть папку с игрой - - - Open Save Data Folder - Открыть папку сохранений - - - Open Log Folder - Открыть папку логов - - - Copy info... - Копировать информацию... - - - Copy Name - Копировать имя - - - Copy Serial - Копировать серийный номер - - - Copy All - Копировать всё - - - Delete... - Удалить... - - - Delete Game - Удалить игру - - - Delete Update - Удалить обновление - - - Delete DLC - Удалить DLC - - - Compatibility... - Совместимость... - - - Update database - Обновить базу данных - - - View report - Посмотреть отчет - - - Submit a report - Отправить отчёт - - - Shortcut creation - Создание ярлыка - - - Shortcut created successfully! - Ярлык создан успешно! - - - Error - Ошибка - - - Error creating shortcut! - Ошибка создания ярлыка! - - - Install PKG - Установить PKG - - - Game - Игры - - - requiresEnableSeparateUpdateFolder_MSG - Эта функция требует включения настройки 'Отдельная папка обновлений'. Если вы хотите использовать эту функцию, пожалуйста включите её. - - - This game has no update to delete! - У этой игры нет обновлений для удаления! - - - Update - Обновления - - - This game has no DLC to delete! - У этой игры нет DLC для удаления! - - - DLC - DLC - - - Delete %1 - Удалить %1 - - - Are you sure you want to delete %1's %2 directory? - Вы уверены, что хотите удалить папку %2 %1? - - - - MainWindow - - Open/Add Elf Folder - Открыть/Добавить папку Elf - - - Install Packages (PKG) - Установить пакеты (PKG) - - - Boot Game - Запустить игру - - - Check for Updates - Проверить обновления - - - About shadPS4 - О shadPS4 - - - Configure... - Настроить... - - - Install application from a .pkg file - Установить приложение из файла .pkg - - - Recent Games - Недавние игры - - - Open shadPS4 Folder - Open shadPS4 Folder - - - Exit - Выход - - - Exit shadPS4 - Выйти из shadPS4 - - - Exit the application. - Выйти из приложения. - - - Show Game List - Показать список игр - - - Game List Refresh - Обновить список игр - - - Tiny - Крошечный - - - Small - Маленький - - - Medium - Средний - - - Large - Большой - - - List View - Список - - - Grid View - Сетка - - - Elf Viewer - Исполняемый файл - - - Game Install Directory - Каталог установки игры - - - Download Cheats/Patches - Скачать читы или патчи - - - Dump Game List - Дамп списка игр - - - PKG Viewer - Просмотр PKG - - - Search... - Поиск... - - - File - Файл - - - View - Вид - - - Game List Icons - Размер иконок списка игр - - - Game List Mode - Вид списка игр - - - Settings - Настройки - - - Utils - Утилиты - - - Themes - Темы - - - Help - Помощь - - - Dark - Темная - - - Light - Светлая - - - Green - Зеленая - - - Blue - Синяя - - - Violet - Фиолетовая - - - toolBar - Панель инструментов - - - Game List - Список игр - - - * Unsupported Vulkan Version - * Неподдерживаемая версия Vulkan - - - Download Cheats For All Installed Games - Скачать читы для всех установленных игр - - - Download Patches For All Games - Скачать патчи для всех игр - - - Download Complete - Скачивание завершено - - - You have downloaded cheats for all the games you have installed. - Вы скачали читы для всех установленных игр. - - - Patches Downloaded Successfully! - Патчи успешно скачаны! - - - All Patches available for all games have been downloaded. - Все доступные патчи для всех игр были скачаны. - - - Games: - Игры: - - - PKG File (*.PKG) - Файл PKG (*.PKG) - - - ELF files (*.bin *.elf *.oelf) - Файлы ELF (*.bin *.elf *.oelf) - - - Game Boot - Запуск игры - - - Only one file can be selected! - Можно выбрать только один файл! - - - PKG Extraction - Извлечение PKG - - - Patch detected! - Обнаружен патч! - - - PKG and Game versions match: - Версии PKG и игры совпадают: - - - Would you like to overwrite? - Хотите перезаписать? - - - PKG Version %1 is older than installed version: - Версия PKG %1 старее установленной версии: - - - Game is installed: - Игра установлена: - - - Would you like to install Patch: - Хотите установить патч: - - - DLC Installation - Установка DLC - - - Would you like to install DLC: %1? - Вы хотите установить DLC: %1? - - - DLC already installed: - DLC уже установлен: - - - Game already installed - Игра уже установлена - - - PKG is a patch, please install the game first! - PKG - это патч, сначала установите игру! - - - PKG ERROR - ОШИБКА PKG - - - Extracting PKG %1/%2 - Извлечение PKG %1/%2 - - - Extraction Finished - Извлечение завершено - - - Game successfully installed at %1 - Игра успешно установлена в %1 - - - File doesn't appear to be a valid PKG file - Файл не является допустимым файлом PKG - - - - PKGViewer - - Open Folder - Открыть папку - - - - TrophyViewer - - Trophy Viewer - Просмотр трофеев - - - - SettingsDialog - - Settings - Настройки - - - General - Общее - - - System - Система - - - Console Language - Язык консоли - - - Emulator Language - Язык эмулятора - - - Emulator - Эмулятор - - - Enable Fullscreen - Полноэкранный режим - - - Fullscreen Mode - Тип полноэкранного режима - - - Enable Separate Update Folder - Отдельная папка обновлений - - - Default tab when opening settings - Вкладка по умолчанию при открытии настроек - - - Show Game Size In List - Показать размер игры в списке - - - Show Splash - Показывать заставку - - - Is PS4 Pro - Режим PS4 Pro - - - Enable Discord Rich Presence - Включить Discord Rich Presence - - - Username - Имя пользователя - - - Trophy Key - Ключ трофеев - - - Trophy - Трофеи - - - Logger - Логирование - - - Log Type - Тип логов - - - Log Filter - Фильтр логов - - - Open Log Location - Открыть местоположение журнала - - - Input - Ввод - - - Cursor - Курсор мыши - - - Hide Cursor - Скрывать курсор - - - Hide Cursor Idle Timeout - Время скрытия курсора при бездействии - - - s - сек - - - Controller - Контроллер - - - Back Button Behavior - Поведение кнопки назад - - - Graphics - Графика - - - Gui - Интерфейс - - - User - Пользователь - - - Graphics Device - Графическое устройство - - - Width - Ширина - - - Height - Высота - - - Vblank Divider - Делитель Vblank - - - Advanced - Продвинутые - - - Enable Shaders Dumping - Включить дамп шейдеров - - - Enable NULL GPU - Включить NULL GPU - - - Paths - Пути - - - Game Folders - Игровые папки - - - Add... - Добавить... - - - Remove - Удалить - - - Debug - Отладка - - - Enable Debug Dumping - Включить отладочные дампы - - - Enable Vulkan Validation Layers - Включить слои валидации Vulkan - - - Enable Vulkan Synchronization Validation - Включить валидацию синхронизации Vulkan - - - Enable RenderDoc Debugging - Включить отладку RenderDoc - - - Enable Crash Diagnostics - Enable Crash Diagnostics - - - Collect Shaders - Collect Shaders - - - Copy GPU Buffers - Copy GPU Buffers - - - Host Debug Markers - Host Debug Markers - - - Guest Debug Markers - Guest Debug Markers - - - Update - Обновление - - - Check for Updates at Startup - Проверка при запуске - - - Update Channel - Канал обновления - - - Check for Updates - Проверить обновления - - - GUI Settings - Интерфейс - - - Title Music - Title Music - - - Disable Trophy Pop-ups - Отключить уведомления о трофеях - - - Play title music - Играть заглавную музыку - - - Update Compatibility Database On Startup - Обновлять базу совместимости при запуске - - - Game Compatibility - Совместимость игр - - - Display Compatibility Data - Показывать данные совместимости - - - Update Compatibility Database - Обновить базу совместимости - - - Volume - Громкость - - - Audio Backend - Звуковая подсистема - - - Save - Сохранить - - - Apply - Применить - - - Restore Defaults - По умолчанию - - - Close - Закрыть - - - Point your mouse at an option to display its description. - Наведите указатель мыши на опцию, чтобы отобразить ее описание. - - - consoleLanguageGroupBox - Язык консоли:\nУстановите язык, который будет использоваться в играх PS4.\nРекомендуется устанавливать язык который поддерживается игрой, так как он может отличаться в зависимости от региона. - - - emulatorLanguageGroupBox - Язык эмулятора:\nУстановите язык пользовательского интерфейса эмулятора. - - - fullscreenCheckBox - Полноэкранный режим:\nАвтоматически переводит игровое окно в полноэкранный режим.\nВы можете отключить это, нажав клавишу F11. - - - separateUpdatesCheckBox - Отдельная папка обновлений:\nПозволяет устанавливать обновления игры в отдельную папку для удобства. - - - showSplashCheckBox - Показывать заставку:\nОтображает заставку игры (специальное изображение) во время запуска. - - - ps4proCheckBox - Режим PS4 Pro:\nЗаставляет эмулятор работать как PS4 Pro, что может включить специальные функции в играх, поддерживающих это. - - - discordRPCCheckbox - Включить Discord Rich Presence:\nОтображает значок эмулятора и соответствующую информацию в вашем профиле Discord. - - - userName - Имя пользователя:\nУстановите имя пользователя аккаунта PS4. Это может отображаться в некоторых играх. - - - TrophyKey - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - - - logTypeGroupBox - Тип логов:\nУстановите, синхронизировать ли вывод окна логов ради производительности. Это может негативно сказаться на эмуляции. - - - logFilter - Фильтр логов:\nФильтрует логи, чтобы показывать только определенную информацию.\nПримеры: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и показывает все последующие уровни. - - - updaterGroupBox - Обновление:\nRelease: Официальные версии, которые выпускаются каждый месяц и могут быть очень старыми, но они более надежные и проверенные.\nNightly: Версии разработки, которые содержат все последние функции и исправления, но могут содержать ошибки и менее стабильны. - - - GUIMusicGroupBox - Играть заглавную музыку:\nВключает воспроизведение специальной музыки при выборе игры в списке, если она это поддерживает. - - - disableTrophycheckBox - Отключить уведомления о трофеях:\nОтключает внутриигровые уведомления о трофеях. Прогресс трофеев по прежнему можно отслеживать в меню просмотра трофеев (правая кнопка мыши по игре в главном окне). - - - hideCursorGroupBox - Скрывать курсор:\nВыберите, когда курсор будет скрыт:\nНикогда: Вы всегда будете видеть курсор.\nПри бездействии: Установите время, через которое курсор будет скрыт при бездействии.\nВсегда: Курсор всегда будет скрыт. - - - idleTimeoutGroupBox - Установите время, через которое курсор исчезнет при бездействии. - - - backButtonBehaviorGroupBox - Поведение кнопки «Назад»:\nНастраивает кнопку «Назад» контроллера на эмуляцию нажатия на указанную область на сенсорной панели контроллера PS4. - - - enableCompatibilityCheckBox - Показывать данные совместимости:\nПоказывает информацию о совместимости игр в таблице. Включите «Обновлять базу совместимости при запуске» для получения актуальной информации. - - - checkCompatibilityOnStartupCheckBox - Обновлять базу совместимости при запуске:\nАвтоматически обновлять базу данных совместимости при запуске shadPS4. - - - updateCompatibilityButton - Обновить базу совместимости:\nНемедленно обновить базу данных совместимости. - - - Never - Никогда - - - Idle - При бездействии - - - Always - Всегда - - - Touchpad Left - Тачпад слева - - - Touchpad Right - Тачпад справа - - - Touchpad Center - Центр тачпада - - - None - Нет - - - graphicsAdapterGroupBox - Графическое устройство:\nВ системах с несколькими GPU выберите GPU, который будет использовать эмулятор.\nВыберите "Auto Select", чтобы определить его автоматически. - - - resolutionLayout - Ширина/Высота:\nУстановите размер окна эмулятора при запуске, который может быть изменен во время игры.\nЭто отличается от разрешения в игре. - - - heightDivider - Делитель Vblank:\nЧастота кадров, с которой обновляется эмулятор, умножается на это число. Изменение этого параметра может иметь негативные последствия, такие как увеличение скорости игры или нарушение критических функций игры, которые этого не ожидают! - - - dumpShadersCheckBox - Включить дамп шейдеров:\nДля технической отладки сохраняет шейдеры игр в папку во время рендеринга. - - - nullGpuCheckBox - Включить NULL GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет. - - - gameFoldersBox - Игровые папки:\nСписок папок для проверки установленных игр. - - - addFolderButton - Добавить:\nДобавить папку в список. - - - removeFolderButton - Удалить:\nУдалить папку из списка. - - - debugDump - Включить отладочные дампы:\nСохраняет символы импорта, экспорта и информацию о заголовке файла текущей исполняемой программы PS4 в папку. - - - vkValidationCheckBox - Включить слои валидации Vulkan:\nВключает систему, которая проверяет состояние рендерера Vulkan и логирует информацию о его внутреннем состоянии. Это снизит производительность и, вероятно, изменит поведение эмуляции. - - - vkSyncValidationCheckBox - Включить валидацию синхронизации Vulkan:\nВключает систему, которая проверяет тайминг задач рендеринга Vulkan. Это снизит производительность и, вероятно, изменит поведение эмуляции. - - - rdocCheckBox - Включить отладку RenderDoc:\nЕсли включено, эмулятор обеспечит совместимость с Renderdoc, позволяя захватывать и анализировать текущие кадры во время рендеринга. - - - collectShaderCheckBox - Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). - - - crashDiagnosticsCheckBox - Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. - - - copyGPUBuffersCheckBox - Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. - - - hostMarkersCheckBox - Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - - - guestMarkersCheckBox - Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - - - - CheatsPatches - - Cheats / Patches for - Читы и патчи для - - - defaultTextEdit_MSG - Читы и патчи экспериментальны.\nИспользуйте с осторожностью.\n\nСкачивайте читы, выбрав репозиторий и нажав на кнопку загрузки.\nВо вкладке "Патчи" вы можете скачать все патчи сразу, выбирать какие вы хотите использовать, и сохранять выбор.\n\nПоскольку мы не разрабатываем читы/патчи,\nпожалуйста сообщайте о проблемах автору чита/патча.\n\nСоздали новый чит? Посетите:\nhttps://github.com/shadps4-emu/ps4_cheats - - - No Image Available - Изображение недоступно - - - Serial: - Серийный номер: - - - Version: - Версия: - - - Size: - Размер: - - - Select Cheat File: - Выберите файл чита: - - - Repository: - Репозиторий: - - - Download Cheats - Скачать читы - - - Delete File - Удалить файл - - - No files selected. - Файлы не выбраны. - - - You can delete the cheats you don't want after downloading them. - Вы можете удалить ненужные читы после их скачивания. - - - Do you want to delete the selected file?\n%1 - Вы хотите удалить выбранный файл?\n%1 - - - Select Patch File: - Выберите файл патча: - - - Download Patches - Скачать патчи - - - Save - Сохранить - - - Cheats - Читы - - - Patches - Патчи - - - Error - Ошибка - - - No patch selected. - Патч не выбран. - - - Unable to open files.json for reading. - Не удалось открыть файл files.json для чтения. - - - No patch file found for the current serial. - Не найден файл патча для текущего серийного номера. - - - Unable to open the file for reading. - Не удалось открыть файл для чтения. - - - Unable to open the file for writing. - Не удалось открыть файл для записи. - - - Failed to parse XML: - Не удалось разобрать XML: - - - Success - Успех - - - Options saved successfully. - Опции успешно сохранены. - - - Invalid Source - Неверный источник - - - The selected source is invalid. - Выбранный источник недействителен. - - - File Exists - Файл существует - - - File already exists. Do you want to replace it? - Файл уже существует. Хотите заменить его? - - - Failed to save file: - Не удалось сохранить файл: - - - Failed to download file: - Не удалось скачать файл: - - - Cheats Not Found - Читы не найдены - - - CheatsNotFound_MSG - Читы не найдены для этой игры в выбранном репозитории. Попробуйте другой репозиторий или другую версию игры. - - - Cheats Downloaded Successfully - Читы успешно скачаны - - - CheatsDownloadedSuccessfully_MSG - Вы успешно скачали читы для этой версии игры из выбранного репозитория. Вы можете попробовать скачать из другого репозитория. Если он доступен, его также можно будет использовать, выбрав файл из списка. - - - Failed to save: - Не удалось сохранить: - - - Failed to download: - Не удалось скачать: - - - Download Complete - Скачивание завершено - - - DownloadComplete_MSG - Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами. Если патч не появляется, возможно, его не существует для конкретного серийного номера и версии игры. - - - Failed to parse JSON data from HTML. - Не удалось разобрать данные JSON из HTML. - - - Failed to retrieve HTML page. - Не удалось получить HTML-страницу. - - - The game is in version: %1 - Игра в версии: %1 - - - The downloaded patch only works on version: %1 - Скачанный патч работает только на версии: %1 - - - You may need to update your game. - Вам может понадобиться обновить игру. - - - Incompatibility Notice - Уведомление о несовместимости - - - Failed to open file: - Не удалось открыть файл: - - - XML ERROR: - ОШИБКА XML: - - - Failed to open files.json for writing - Не удалось открыть файл files.json для записи - - - Author: - Автор: - - - Directory does not exist: - Каталог не существует: - - - Failed to open files.json for reading. - Не удалось открыть файл files.json для чтения. - - - Name: - Имя: - - - Can't apply cheats before the game is started - Невозможно применить читы до начала игры - - - - GameListFrame - - Icon - Иконка - - - Name - Название - - - Serial - Серийный номер - - - Compatibility - Совместимость - - - Region - Регион - - - Firmware - Прошивка - - - Size - Размер - - - Version - Версия - - - Path - Путь - - - Play Time - Время в игре - - - Never Played - Нет - - - h - ч - - - m - м - - - s - с - - - Compatibility is untested - Совместимость не проверена - - - Game does not initialize properly / crashes the emulator - Игра не иницализируется правильно / крашит эмулятор - - - Game boots, but only displays a blank screen - Игра запускается, но показывает только пустой экран - - - Game displays an image but does not go past the menu - Игра показывает картинку, но не проходит дальше меню - - - Game has game-breaking glitches or unplayable performance - Игра имеет ломающие игру глюки или плохую производительность - - - Game can be completed with playable performance and no major glitches - Игра может быть пройдена с хорошей производительностью и без серьезных сбоев - - - - CheckUpdate - - Auto Updater - Автообновление - - - Error - Ошибка - - - Network error: - Сетевая ошибка: - - - Failed to parse update information. - Не удалось разобрать информацию об обновлении. - - - No pre-releases found. - Предварительных версий не найдено. - - - Invalid release data. - Недопустимые данные релиза. - - - No download URL found for the specified asset. - Не найден URL для загрузки указанного ресурса. - - - Your version is already up to date! - Ваша версия уже обновлена! - - - Update Available - Доступно обновление - - - Update Channel - Канал обновления - - - Current Version - Текущая версия - - - Latest Version - Последняя версия - - - Do you want to update? - Вы хотите обновиться? - - - Show Changelog - Показать журнал изменений - - - Check for Updates at Startup - Проверка при запуске - - - Update - Обновить - - - No - Нет - - - Hide Changelog - Скрыть журнал изменений - - - Changes - Журнал изменений - - - Network error occurred while trying to access the URL - Произошла сетевая ошибка при попытке доступа к URL - - - Download Complete - Скачивание завершено - - - The update has been downloaded, press OK to install. - Обновление загружено, нажмите OK для установки. - - - Failed to save the update file at - Не удалось сохранить файл обновления в - - - Starting Update... - Начало обновления... - - - Failed to create the update script file - Не удалось создать файл скрипта обновления - - - - GameListUtils - - B - Б - - - KB - КБ - - - MB - МБ - - - GB - ГБ - - - TB - ТБ - - + + + AboutDialog + + About shadPS4 + О shadPS4 + + + shadPS4 + shadPS4 + + + shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 это экспериментальный эмулятор с открытым исходным кодом для PlayStation 4. + + + This software should not be used to play games you have not legally obtained. + Это программное обеспечение не должно использоваться для запуска игр, которые вы получили нелегально. + + + + ElfViewer + + Open Folder + Открыть папку + + + + GameInfoClass + + Loading game list, please wait :3 + Загрузка списка игр, пожалуйста подождите :3 + + + Cancel + Отмена + + + Loading... + Загрузка... + + + + InstallDirSelect + + shadPS4 - Choose directory + shadPS4 - Выберите папку + + + Select which directory you want to install to. + Выберите папку, в которую вы хотите установить. + + + Install All Queued to Selected Folder + Установить все из очереди в выбранную папку + + + Delete PKG File on Install + Удалить файл PKG при установке + + + + GameInstallDialog + + shadPS4 - Choose directory + shadPS4 - Выберите папку + + + Directory to install games + Каталог для установки игр + + + Browse + Обзор + + + Error + Ошибка + + + The value for location to install games is not valid. + Недопустимое значение местоположения для установки игр. + + + Directory to install DLC + Каталог для установки DLC + + + + GuiContextMenus + + Create Shortcut + Создать ярлык + + + Cheats / Patches + Читы и патчи + + + SFO Viewer + Просмотр SFO + + + Trophy Viewer + Просмотр трофеев + + + Open Folder... + Открыть папку... + + + Open Game Folder + Открыть папку с игрой + + + Open Save Data Folder + Открыть папку сохранений + + + Open Log Folder + Открыть папку логов + + + Copy info... + Копировать информацию... + + + Copy Name + Копировать имя + + + Copy Serial + Копировать серийный номер + + + Copy All + Копировать всё + + + Delete... + Удалить... + + + Delete Game + Удалить игру + + + Delete Update + Удалить обновление + + + Delete DLC + Удалить DLC + + + Compatibility... + Совместимость... + + + Update database + Обновить базу данных + + + View report + Посмотреть отчет + + + Submit a report + Отправить отчёт + + + Shortcut creation + Создание ярлыка + + + Shortcut created successfully! + Ярлык создан успешно! + + + Error + Ошибка + + + Error creating shortcut! + Ошибка создания ярлыка! + + + Install PKG + Установить PKG + + + Game + Игры + + + requiresEnableSeparateUpdateFolder_MSG + Эта функция требует включения настройки 'Отдельная папка обновлений'. Если вы хотите использовать эту функцию, пожалуйста включите её. + + + This game has no update to delete! + У этой игры нет обновлений для удаления! + + + Update + Обновления + + + This game has no DLC to delete! + У этой игры нет DLC для удаления! + + + DLC + DLC + + + Delete %1 + Удалить %1 + + + Are you sure you want to delete %1's %2 directory? + Вы уверены, что хотите удалить папку %2 %1? + + + Open Update Folder + Открыть папку обновлений + + + Delete Save Data + Удалить сохранения + + + This game has no update folder to open! + У этой игры нет папки обновлений, которую можно открыть! + + + Failed to convert icon. + Не удалось преобразовать иконку. + + + This game has no save data to delete! + У этой игры нет сохранений, которые можно удалить! + + + Save Data + Сохранения + + + + MainWindow + + Open/Add Elf Folder + Открыть/Добавить папку Elf + + + Install Packages (PKG) + Установить пакеты (PKG) + + + Boot Game + Запустить игру + + + Check for Updates + Проверить обновления + + + About shadPS4 + О shadPS4 + + + Configure... + Настроить... + + + Install application from a .pkg file + Установить приложение из файла .pkg + + + Recent Games + Недавние игры + + + Open shadPS4 Folder + Открыть папку shadPS4 + + + Exit + Выход + + + Exit shadPS4 + Выйти из shadPS4 + + + Exit the application. + Выйти из приложения. + + + Show Game List + Показать список игр + + + Game List Refresh + Обновить список игр + + + Tiny + Крошечный + + + Small + Маленький + + + Medium + Средний + + + Large + Большой + + + List View + Список + + + Grid View + Сетка + + + Elf Viewer + Исполняемый файл + + + Game Install Directory + Каталог установки игры + + + Download Cheats/Patches + Скачать читы или патчи + + + Dump Game List + Дамп списка игр + + + PKG Viewer + Просмотр PKG + + + Search... + Поиск... + + + File + Файл + + + View + Вид + + + Game List Icons + Размер иконок списка игр + + + Game List Mode + Вид списка игр + + + Settings + Настройки + + + Utils + Утилиты + + + Themes + Темы + + + Help + Помощь + + + Dark + Темная + + + Light + Светлая + + + Green + Зеленая + + + Blue + Синяя + + + Violet + Фиолетовая + + + toolBar + Панель инструментов + + + Game List + Список игр + + + * Unsupported Vulkan Version + * Неподдерживаемая версия Vulkan + + + Download Cheats For All Installed Games + Скачать читы для всех установленных игр + + + Download Patches For All Games + Скачать патчи для всех игр + + + Download Complete + Скачивание завершено + + + You have downloaded cheats for all the games you have installed. + Вы скачали читы для всех установленных игр. + + + Patches Downloaded Successfully! + Патчи успешно скачаны! + + + All Patches available for all games have been downloaded. + Все доступные патчи для всех игр были скачаны. + + + Games: + Игры: + + + PKG File (*.PKG) + Файл PKG (*.PKG) + + + ELF files (*.bin *.elf *.oelf) + Файлы ELF (*.bin *.elf *.oelf) + + + Game Boot + Запуск игры + + + Only one file can be selected! + Можно выбрать только один файл! + + + PKG Extraction + Извлечение PKG + + + Patch detected! + Обнаружен патч! + + + PKG and Game versions match: + Версии PKG и игры совпадают: + + + Would you like to overwrite? + Хотите перезаписать? + + + PKG Version %1 is older than installed version: + Версия PKG %1 старше установленной версии: + + + Game is installed: + Игра установлена: + + + Would you like to install Patch: + Хотите установить патч: + + + DLC Installation + Установка DLC + + + Would you like to install DLC: %1? + Вы хотите установить DLC: %1? + + + DLC already installed: + DLC уже установлен: + + + Game already installed + Игра уже установлена + + + PKG is a patch, please install the game first! + PKG - это патч, сначала установите игру! + + + PKG ERROR + ОШИБКА PKG + + + Extracting PKG %1/%2 + Извлечение PKG %1/%2 + + + Extraction Finished + Извлечение завершено + + + Game successfully installed at %1 + Игра успешно установлена в %1 + + + File doesn't appear to be a valid PKG file + Файл не является допустимым файлом PKG + + + Run Game + Запустить игру + + + Eboot.bin file not found + Файл eboot.bin не найден + + + PKG File (*.PKG *.pkg) + Файл PKG (*.PKG *.pkg) + + + PKG is a patch or DLC, please install the game first! + Выбранный PKG является патчем или DLC, пожалуйста, сначала установите игру! + + + Game is already running! + Игра уже запущена! + + + shadPS4 + shadPS4 + + + + PKGViewer + + Open Folder + Открыть папку + + + &File + Файл + + + PKG ERROR + ОШИБКА PKG + + + + TrophyViewer + + Trophy Viewer + Просмотр трофеев + + + + SettingsDialog + + Settings + Настройки + + + General + Общее + + + System + Система + + + Console Language + Язык консоли + + + Emulator Language + Язык эмулятора + + + Emulator + Эмулятор + + + Enable Fullscreen + Полноэкранный режим + + + Fullscreen Mode + Тип полноэкранного режима + + + Enable Separate Update Folder + Отдельная папка обновлений + + + Default tab when opening settings + Вкладка по умолчанию при открытии настроек + + + Show Game Size In List + Показать размер игры в списке + + + Show Splash + Показывать заставку + + + Is PS4 Pro + Режим PS4 Pro + + + Enable Discord Rich Presence + Включить Discord Rich Presence + + + Username + Имя пользователя + + + Trophy Key + Ключ трофеев + + + Trophy + Трофеи + + + Logger + Логирование + + + Log Type + Тип логов + + + Log Filter + Фильтр логов + + + Open Log Location + Открыть местоположение журнала + + + Input + Ввод + + + Cursor + Курсор мыши + + + Hide Cursor + Скрывать курсор + + + Hide Cursor Idle Timeout + Время скрытия курсора при бездействии + + + s + сек + + + Controller + Контроллер + + + Back Button Behavior + Поведение кнопки назад + + + Graphics + Графика + + + Gui + Интерфейс + + + User + Пользователь + + + Graphics Device + Графическое устройство + + + Width + Ширина + + + Height + Высота + + + Vblank Divider + Делитель Vblank + + + Advanced + Продвинутые + + + Enable Shaders Dumping + Включить дамп шейдеров + + + Enable NULL GPU + Включить NULL GPU + + + Paths + Пути + + + Game Folders + Игровые папки + + + Add... + Добавить... + + + Remove + Удалить + + + Debug + Отладка + + + Enable Debug Dumping + Включить отладочные дампы + + + Enable Vulkan Validation Layers + Включить слои валидации Vulkan + + + Enable Vulkan Synchronization Validation + Включить валидацию синхронизации Vulkan + + + Enable RenderDoc Debugging + Включить отладку RenderDoc + + + Enable Crash Diagnostics + Включить диагностику сбоев + + + Collect Shaders + Собирать шейдеры + + + Copy GPU Buffers + Копировать буферы GPU + + + Host Debug Markers + Маркеры отладки хоста + + + Guest Debug Markers + Маркеры отладки гостя + + + Update + Обновление + + + Check for Updates at Startup + Проверка при запуске + + + Update Channel + Канал обновления + + + Check for Updates + Проверить обновления + + + GUI Settings + Настройки интерфейса + + + Title Music + Заглавная музыка + + + Disable Trophy Pop-ups + Отключить уведомления о трофеях + + + Play title music + Играть заглавную музыку + + + Update Compatibility Database On Startup + Обновлять базу совместимости при запуске + + + Game Compatibility + Совместимость игр + + + Display Compatibility Data + Показывать данные совместимости + + + Update Compatibility Database + Обновить базу совместимости + + + Volume + Громкость + + + Audio Backend + Звуковая подсистема + + + Save + Сохранить + + + Apply + Применить + + + Restore Defaults + По умолчанию + + + Close + Закрыть + + + Point your mouse at an option to display its description. + Наведите указатель мыши на опцию, чтобы отобразить ее описание. + + + consoleLanguageGroupBox + Язык консоли:\nУстановите язык, который будет использоваться в играх PS4.\nРекомендуется устанавливать язык, который поддерживается игрой, так как он может отличаться в зависимости от региона. + + + emulatorLanguageGroupBox + Язык эмулятора:\nУстановите язык пользовательского интерфейса эмулятора. + + + fullscreenCheckBox + Полноэкранный режим:\nАвтоматически переводит игровое окно в полноэкранный режим.\nЭто можно переключить, нажав клавишу F11. + + + separateUpdatesCheckBox + Отдельная папка обновлений:\nПозволяет устанавливать обновления игры в отдельную папку для удобства.\nМожно создать вручную, добавив извлеченное обновление в папку с игрой с именем "CUSA00000-UPDATE", где идентификатор CUSA совпадает с идентификатором игры. + + + showSplashCheckBox + Показывать заставку:\nОтображает заставку игры (специальное изображение) во время запуска. + + + ps4proCheckBox + Режим PS4 Pro:\nЗаставляет эмулятор работать как PS4 Pro, что может включить специальные функции в играх, поддерживающих это. + + + discordRPCCheckbox + Включить Discord Rich Presence:\nОтображает значок эмулятора и соответствующую информацию в вашем профиле Discord. + + + userName + Имя пользователя:\nУстановите имя пользователя аккаунта PS4. Это может отображаться в некоторых играх. + + + TrophyKey + Ключ трофеев:\nКлюч, используемый для расшифровки трофеев. Должен быть получен из вашей взломанной консоли.\nДолжен содержать только шестнадцатеричные символы. + + + logTypeGroupBox + Тип логов:\nУстановите, синхронизировать ли вывод окна логов ради производительности. Это может негативно сказаться на эмуляции. + + + logFilter + Фильтр логов:\nФильтрует логи, чтобы показывать только определенную информацию.\nПримеры: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и показывает все последующие уровни. + + + updaterGroupBox + Обновление:\nRelease: Официальные версии, которые выпускаются каждый месяц и могут быть очень старыми, но они более надежные и проверенные.\nNightly: Версии разработки, которые содержат все последние функции и исправления, но могут содержать ошибки и менее стабильны. + + + GUIMusicGroupBox + Играть заглавную музыку:\nВключает воспроизведение специальной музыки при выборе игры в списке, если она это поддерживает. + + + disableTrophycheckBox + Отключить уведомления о трофеях:\nОтключает внутриигровые уведомления о трофеях. Прогресс трофеев по-прежнему можно отслеживать в меню просмотра трофеев (правая кнопка мыши по игре в главном окне). + + + hideCursorGroupBox + Скрывать курсор:\nВыберите, когда курсор будет скрыт:\nНикогда: Вы всегда будете видеть курсор.\nПри бездействии: Установите время, через которое курсор будет скрыт при бездействии.\nВсегда: Курсор всегда будет скрыт. + + + idleTimeoutGroupBox + Время скрытия курсора при бездействии:\nУстановите время, через которое курсор исчезнет при бездействии. + + + backButtonBehaviorGroupBox + Поведение кнопки «Назад»:\nНастраивает кнопку «Назад» контроллера на эмуляцию нажатия на указанную область на сенсорной панели контроллера PS4. + + + enableCompatibilityCheckBox + Показывать данные совместимости:\nПоказывает информацию о совместимости игр в таблице. Включите «Обновлять базу совместимости при запуске» для получения актуальной информации. + + + checkCompatibilityOnStartupCheckBox + Обновлять базу совместимости при запуске:\nАвтоматически обновлять базу данных совместимости при запуске shadPS4. + + + updateCompatibilityButton + Обновить базу совместимости:\nНемедленно обновить базу данных совместимости. + + + Never + Никогда + + + Idle + При бездействии + + + Always + Всегда + + + Touchpad Left + Тачпад слева + + + Touchpad Right + Тачпад справа + + + Touchpad Center + Центр тачпада + + + None + Нет + + + graphicsAdapterGroupBox + Графическое устройство:\nВ системах с несколькими GPU выберите тот, который будет использовать эмулятор.\nВыберите "Автовыбор", чтобы определить GPU автоматически. + + + resolutionLayout + Ширина/Высота:\nУстановите размер окна эмулятора при запуске, который может быть изменен во время игры.\nЭто отличается от разрешения в игре. + + + heightDivider + Делитель Vblank:\nЧастота кадров, с которой обновляется эмулятор, умножается на это число. Изменение этого параметра может иметь негативные последствия, такие как увеличение скорости игры или нарушение критических функций игры, которые этого не ожидают! + + + dumpShadersCheckBox + Включить дамп шейдеров:\nДля технической отладки сохраняет шейдеры игр в папку во время рендеринга. + + + nullGpuCheckBox + Включить NULL GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет. + + + gameFoldersBox + Игровые папки:\nСписок папок для проверки установленных игр. + + + addFolderButton + Добавить:\nДобавить папку в список. + + + removeFolderButton + Удалить:\nУдалить папку из списка. + + + debugDump + Включить отладочные дампы:\nСохраняет символы импорта, экспорта и информацию о заголовке файла текущей исполняемой программы PS4 в папку. + + + vkValidationCheckBox + Включить слои валидации Vulkan:\nВключает систему, которая проверяет состояние рендерера Vulkan и логирует информацию о его внутреннем состоянии. Это снизит производительность и, вероятно, изменит поведение эмуляции. + + + vkSyncValidationCheckBox + Включить валидацию синхронизации Vulkan:\nВключает систему, которая проверяет тайминг задач рендеринга Vulkan. Это снизит производительность и, вероятно, изменит поведение эмуляции. + + + rdocCheckBox + Включить отладку RenderDoc:\nЕсли включено, эмулятор обеспечит совместимость с RenderDoc, позволяя захватывать и анализировать текущие кадры во время рендеринга. + + + collectShaderCheckBox + Собирать шейдеры:\nВам необходимо включить эту функцию для редактирования шейдеров с помощью меню отладки (Ctrl + F10). + + + crashDiagnosticsCheckBox + Диагностика сбоев:\nСоздает .yaml файл с информацией о состоянии Vulkan в момент падения.\nПолезно для отладки ошибок 'Device lost'. Если эта функция включена, вам следует включить Маркеры отладки хоста и Гостя.\nНе работает на видеокартах Intel.\nДля работы вам необходимо включить Слои валидации Vulkan и установить Vulkan SDK. + + + copyGPUBuffersCheckBox + Копировать буферы GPU:\nПозволяет обойти состояния гонки, связанные с отправками GPU.\nМожет помочь или не помочь при сбоях PM4 типа 0. + + + hostMarkersCheckBox + Маркеры отладки хоста:\nДобавляет информацию на стороне эмулятора, например маркеры для определенных команд AMDGPU, вокруг команд Vulkan, а также присваивает ресурсам отладочные имена.\nЕсли эта функция включена, вам следует включить Диагностику сбоев.\nПолезно для таких программ, как RenderDoc. + + + guestMarkersCheckBox + Маркеры отладки гостя:\nДобавляет любые отладочные маркеры, добавленные самой игрой, в буфер команд.\nЕсли эта функция включена, вам следует включить Диагностику сбоев.\nПолезно для таких программ, как RenderDoc. + + + saveDataBox + Путь сохранений:\nПапка, в которой будут храниться сохранения игр. + + + browseButton + Обзор:\nНайдите папку, которую можно указать в качестве пути для сохранений. + + + Borderless + Без полей + + + True + Истинный + + + Release + Release + + + Nightly + Nightly + + + GUI + Интерфейс + + + Set the volume of the background music. + Установите громкость фоновой музыки. + + + Enable Motion Controls + Включить управление движением + + + Save Data Path + Путь сохранений + + + Browse + Обзор + + + async + асинхронный + + + sync + синхронный + + + Directory to install games + Каталог для установки игр + + + Directory to save data + Каталог для сохранений + + + Auto Select + Автовыбор + + + + CheatsPatches + + Cheats / Patches for + Читы и патчи для + + + defaultTextEdit_MSG + Читы и патчи экспериментальны.\nИспользуйте с осторожностью.\n\nСкачивайте читы, выбрав репозиторий и нажав на кнопку загрузки.\nВо вкладке "Патчи" вы можете скачать все патчи сразу, выбирать какие вы хотите использовать, и сохранять выбор.\n\nПоскольку мы не разрабатываем читы/патчи,\nпожалуйста сообщайте о проблемах автору чита/патча.\n\nСоздали новый чит? Посетите:\nhttps://github.com/shadps4-emu/ps4_cheats + + + No Image Available + Изображение недоступно + + + Serial: + Серийный номер: + + + Version: + Версия: + + + Size: + Размер: + + + Select Cheat File: + Выберите файл чита: + + + Repository: + Репозиторий: + + + Download Cheats + Скачать читы + + + Delete File + Удалить файл + + + No files selected. + Файлы не выбраны. + + + You can delete the cheats you don't want after downloading them. + Вы можете удалить ненужные читы после их скачивания. + + + Do you want to delete the selected file?\n%1 + Вы хотите удалить выбранный файл?\n%1 + + + Select Patch File: + Выберите файл патча: + + + Download Patches + Скачать патчи + + + Save + Сохранить + + + Cheats + Читы + + + Patches + Патчи + + + Error + Ошибка + + + No patch selected. + Патч не выбран. + + + Unable to open files.json for reading. + Не удалось открыть файл files.json для чтения. + + + No patch file found for the current serial. + Не найден файл патча для текущего серийного номера. + + + Unable to open the file for reading. + Не удалось открыть файл для чтения. + + + Unable to open the file for writing. + Не удалось открыть файл для записи. + + + Failed to parse XML: + Не удалось разобрать XML: + + + Success + Успех + + + Options saved successfully. + Опции успешно сохранены. + + + Invalid Source + Неверный источник + + + The selected source is invalid. + Выбранный источник недействителен. + + + File Exists + Файл существует + + + File already exists. Do you want to replace it? + Файл уже существует. Хотите заменить его? + + + Failed to save file: + Не удалось сохранить файл: + + + Failed to download file: + Не удалось скачать файл: + + + Cheats Not Found + Читы не найдены + + + CheatsNotFound_MSG + Читы не найдены для этой игры в выбранном репозитории. Попробуйте другой репозиторий или другую версию игры. + + + Cheats Downloaded Successfully + Читы успешно скачаны + + + CheatsDownloadedSuccessfully_MSG + Вы успешно скачали читы для этой версии игры из выбранного репозитория. Вы можете попробовать скачать из другого репозитория. Если он доступен, его также можно будет использовать, выбрав файл из списка. + + + Failed to save: + Не удалось сохранить: + + + Failed to download: + Не удалось скачать: + + + Download Complete + Скачивание завершено + + + DownloadComplete_MSG + Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами. Если патч не появляется, возможно, его не существует для конкретного серийного номера и версии игры. + + + Failed to parse JSON data from HTML. + Не удалось разобрать данные JSON из HTML. + + + Failed to retrieve HTML page. + Не удалось получить HTML-страницу. + + + The game is in version: %1 + Игра в версии: %1 + + + The downloaded patch only works on version: %1 + Скачанный патч работает только на версии: %1 + + + You may need to update your game. + Вам может понадобиться обновить игру. + + + Incompatibility Notice + Уведомление о несовместимости + + + Failed to open file: + Не удалось открыть файл: + + + XML ERROR: + ОШИБКА XML: + + + Failed to open files.json for writing + Не удалось открыть файл files.json для записи + + + Author: + Автор: + + + Directory does not exist: + Каталог не существует: + + + Failed to open files.json for reading. + Не удалось открыть файл files.json для чтения. + + + Name: + Имя: + + + Can't apply cheats before the game is started + Невозможно применить читы до начала игры + + + Close + Закрыть + + + Error: + Ошибка: + + + ERROR + ОШИБКА + + + + GameListFrame + + Icon + Иконка + + + Name + Название + + + Serial + Серийный номер + + + Compatibility + Совместимость + + + Region + Регион + + + Firmware + Прошивка + + + Size + Размер + + + Version + Версия + + + Path + Путь + + + Play Time + Время в игре + + + Never Played + Нет + + + h + ч + + + m + м + + + s + с + + + Compatibility is untested + Совместимость не проверена + + + Game does not initialize properly / crashes the emulator + Игра не инициализируется правильно / эмулятор вылетает + + + Game boots, but only displays a blank screen + Игра запускается, но показывает только пустой экран + + + Game displays an image but does not go past the menu + Игра показывает картинку, но не проходит дальше меню + + + Game has game-breaking glitches or unplayable performance + Игра имеет ломающие игру глюки или плохую производительность + + + Game can be completed with playable performance and no major glitches + Игра может быть пройдена с хорошей производительностью и без серьезных сбоев + + + Click to go to issue + Нажмите, чтобы перейти к проблеме + + + Last updated + Последнее обновление + + + + CheckUpdate + + Auto Updater + Автообновление + + + Error + Ошибка + + + Network error: + Сетевая ошибка: + + + Failed to parse update information. + Не удалось разобрать информацию об обновлении. + + + No pre-releases found. + Предварительных версий не найдено. + + + Invalid release data. + Недопустимые данные релиза. + + + No download URL found for the specified asset. + Не найден URL для загрузки указанного ресурса. + + + Your version is already up to date! + Ваша версия уже обновлена! + + + Update Available + Доступно обновление + + + Update Channel + Канал обновления + + + Current Version + Текущая версия + + + Latest Version + Последняя версия + + + Do you want to update? + Вы хотите обновиться? + + + Show Changelog + Показать журнал изменений + + + Check for Updates at Startup + Проверка при запуске + + + Update + Обновить + + + No + Нет + + + Hide Changelog + Скрыть журнал изменений + + + Changes + Журнал изменений + + + Network error occurred while trying to access the URL + Произошла сетевая ошибка при попытке доступа к URL + + + Download Complete + Скачивание завершено + + + The update has been downloaded, press OK to install. + Обновление загружено, нажмите OK для установки. + + + Failed to save the update file at + Не удалось сохранить файл обновления в + + + Starting Update... + Начинаем обновление... + + + Failed to create the update script file + Не удалось создать файл скрипта обновления + + + + GameListUtils + + B + Б + + + KB + КБ + + + MB + МБ + + + GB + ГБ + + + TB + ТБ + + + + CompatibilityInfoClass + + Fetching compatibility data, please wait + Загрузка данных о совместимости, пожалуйста, подождите + + + Cancel + Отмена + + + Loading... + Загрузка... + + + Error + Ошибка + + + Unable to update compatibility data! Try again later. + Не удалось обновить данные совместимости! Повторите попытку позже. + + + Unable to open compatibility.json for writing. + Не удалось открыть файл compatibility.json для записи. + + + Unknown + Неизвестно + + + Nothing + Ничего + + + Boots + Запускается + + + Menus + В меню + + + Ingame + В игре + + + Playable + Играбельно + + diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 00f4337c0..1e6124c85 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -52,7 +52,7 @@ Select which directory you want to install to. - 选择你想要安装到的目录。 + 选择您想要安装到的目录。 @@ -186,7 +186,7 @@ requiresEnableSeparateUpdateFolder_MSG - 这个功能需要“启用单独的更新目录”配置选项才能正常运行,如果你想要使用这个功能,请启用它。 + 这个功能需要“启用单独的更新目录”配置选项才能正常运行,如果您想要使用这个功能,请启用它。 This game has no update to delete! @@ -210,7 +210,7 @@ Are you sure you want to delete %1's %2 directory? - 你确定要删除 %1 的%2目录? + 您确定要删除 %1 的%2目录? @@ -702,24 +702,25 @@ Enable Crash Diagnostics - Enable Crash Diagnostics + 启用崩溃诊断 Collect Shaders - Collect Shaders + 收集着色器 Copy GPU Buffers - Copy GPU Buffers + 复制 GPU 缓冲区 Host Debug Markers - Host Debug Markers + Host 调试标记 Guest Debug Markers - Guest Debug Markers + Geust 调试标记 + Update 更新 @@ -954,23 +955,23 @@ collectShaderCheckBox - Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + 收集着色器:\n您需要启用此功能才能使用调试菜单(Ctrl + F10)编辑着色器。 crashDiagnosticsCheckBox - Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. + 崩溃诊断:\n创建一个包含崩溃时 Vulkan 状态的 .yaml 文件。\n对于调试“Device lost”错误很有用。如果您启用了此功能,您应该同时启用 Host 和 Guest 调试标记。\n此功能在 Intel 显卡上不可用。\n您需要启用 Vulkan 验证层并安装 Vulkan SDK 才能使用此功能。 copyGPUBuffersCheckBox - Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + 复制 GPU 缓冲区:\n绕过涉及 GPU 提交的竞态条件。\n对于 PM4 type 0 崩溃可能有帮助,也可能没有帮助。 hostMarkersCheckBox - Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Host 调试标记:\n在 Vulkan 命令周围插入模拟器端信息,如特定 AMD GPU 命令的标记,以及为资源提供调试名称。\n如果您已启用此功能,应同时启用崩溃诊断。\n对 RenderDoc 等程序很有用。 guestMarkersCheckBox - Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Guest 调试标记:\n在命令缓冲区中插入游戏本身添加的任何调试标记。\n如果您已启用此功能,应同时启用崩溃诊断。\n对 RenderDoc 等程序很有用。 saveDataBox @@ -1284,7 +1285,7 @@ Game can be completed with playable performance and no major glitches - 游戏能在可玩的性能下完成且没有重大 Bug + 游戏能在可玩的性能下通关且没有重大 Bug @@ -1413,4 +1414,31 @@ TB + + CompatibilityInfoClass + + Unknown + 未知 + + + Nothing + 无法启动 + + + Boots + 可启动 + + + Menus + 可进入菜单 + + + Ingame + 可进入游戏内 + + + Playable + 可通关 + + diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index ec5c117f7..126cb4eb6 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -148,41 +148,46 @@ void CFG::EmitDivergenceLabels() { const size_t end_index = GetIndex(end); s32 curr_begin = -1; + s32 last_exec_idx = -1; for (size_t index = GetIndex(start); index < end_index; index++) { const auto& inst = inst_list[index]; - const bool is_close = is_close_scope(inst); - if ((is_close || index == end_index - 1) && curr_begin != -1) { - // If there are no instructions inside scope don't do anything. - if (index - curr_begin == 1) { - curr_begin = -1; - continue; + if (curr_begin != -1) { + // Keep note of the last instruction that does not ignore exec, so we know where + // to end the divergence block without impacting trailing instructions that do. + if (!IgnoresExecMask(inst)) { + last_exec_idx = index; } - // If all instructions in the scope ignore exec masking, we shouldn't insert a - // scope. - const auto start = inst_list.begin() + curr_begin + 1; - if (!std::ranges::all_of(start, inst_list.begin() + index, IgnoresExecMask)) { - // Add a label to the instruction right after the open scope call. - // It is the start of a new basic block. - const auto& save_inst = inst_list[curr_begin]; - const Label label = index_to_pc[curr_begin] + save_inst.length; - AddLabel(label); - // Add a label to the close scope instruction. - // There are 3 cases where we need to close a scope. - // * Close scope instruction inside the block - // * Close scope instruction at the end of the block (cbranch or endpgm) - // * Normal instruction at the end of the block - // For the last case we must NOT add a label as that would cause - // the instruction to be separated into its own basic block. - if (is_close) { - AddLabel(index_to_pc[index]); + // Consider a close scope on certain instruction types or at the last instruction + // before the next label. + if (is_close_scope(inst) || index == end_index - 1) { + // Only insert a scope if, since the open-scope instruction, there is at least + // one instruction that does not ignore exec. + if (index - curr_begin > 1 && last_exec_idx != -1) { + // Add a label to the instruction right after the open scope call. + // It is the start of a new basic block. + const auto& save_inst = inst_list[curr_begin]; + AddLabel(index_to_pc[curr_begin] + save_inst.length); + // Add a label to the close scope instruction. + // There are 3 cases where we need to close a scope. + // * Close scope instruction inside the block + // * Close scope instruction at the end of the block (cbranch or endpgm) + // * Normal instruction at the end of the block + // If the instruction we want to close the scope at is at the end of the + // block, we do not need to insert a new label. + if (last_exec_idx != end_index - 1) { + // Add the label after the last instruction affected by exec. + const auto& last_exec_inst = inst_list[last_exec_idx]; + AddLabel(index_to_pc[last_exec_idx] + last_exec_inst.length); + } } + // Reset scope begin. + curr_begin = -1; } - // Reset scope begin. - curr_begin = -1; } // Mark a potential start of an exec scope. if (is_open_scope(inst)) { curr_begin = index; + last_exec_idx = -1; } } } diff --git a/src/shader_recompiler/frontend/translate/export.cpp b/src/shader_recompiler/frontend/translate/export.cpp index 84c2ee658..28c4685db 100644 --- a/src/shader_recompiler/frontend/translate/export.cpp +++ b/src/shader_recompiler/frontend/translate/export.cpp @@ -17,7 +17,11 @@ u32 SwizzleMrtComponent(const FragmentRuntimeInfo::PsColorBuffer& color_buffer, void Translator::ExportMrtValue(IR::Attribute attribute, u32 comp, const IR::F32& value, const FragmentRuntimeInfo::PsColorBuffer& color_buffer) { - const auto converted = ApplyWriteNumberConversion(ir, value, color_buffer.num_conversion); + auto converted = ApplyWriteNumberConversion(ir, value, color_buffer.num_conversion); + if (color_buffer.needs_unorm_fixup) { + // FIXME: Fix-up for GPUs where float-to-unorm rounding is off from expected. + converted = ir.FPSub(converted, ir.Imm32(1.f / 127500.f)); + } ir.SetAttribute(attribute, converted, comp); } diff --git a/src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp b/src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp index ef9319891..bbf3fe8fb 100644 --- a/src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp +++ b/src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp @@ -219,7 +219,7 @@ void FlattenExtendedUserdataPass(IR::Program& program) { }; auto base0 = IR::BreadthFirstSearch(ptr_composite->Arg(0), pred); auto base1 = IR::BreadthFirstSearch(ptr_composite->Arg(1), pred); - ASSERT_MSG(base0 && base1 && "ReadConst not from constant memory"); + ASSERT_MSG(base0 && base1, "ReadConst not from constant memory"); IR::Inst* ptr_lo = base0.value(); ptr_lo = pass_info.DeduplicateInstruction(ptr_lo); @@ -250,4 +250,4 @@ void FlattenExtendedUserdataPass(IR::Program& program) { info.RefreshFlatBuf(); } -} // namespace Shader::Optimization \ No newline at end of file +} // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir/type.cpp b/src/shader_recompiler/ir/type.cpp index 08157f108..74c56740c 100644 --- a/src/shader_recompiler/ir/type.cpp +++ b/src/shader_recompiler/ir/type.cpp @@ -9,9 +9,10 @@ namespace Shader::IR { std::string NameOf(Type type) { static constexpr std::array names{ - "Opaque", "Label", "Reg", "Pred", "Attribute", "U1", "U8", "U16", "U32", - "U64", "F16", "F32", "F64", "U32x2", "U32x3", "U32x4", "F16x2", "F16x3", - "F16x4", "F32x2", "F32x3", "F32x4", "F64x2", "F64x3", "F64x4", "StringLiteral"}; + "Opaque", "ScalarReg", "VectorReg", "Attribute", "Patch", "U1", "U8", + "U16", "U32", "U64", "F16", "F32", "F64", "U32x2", + "U32x3", "U32x4", "F16x2", "F16x3", "F16x4", "F32x2", "F32x3", + "F32x4", "F64x2", "F64x3", "F64x4", "StringLiteral"}; const size_t bits{static_cast(type)}; if (bits == 0) { return "Void"; diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 138a707b3..d1ae2c09d 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -185,6 +185,7 @@ struct FragmentRuntimeInfo { AmdGpu::NumberConversion num_conversion; AmdGpu::CompMapping swizzle; AmdGpu::Liverpool::ShaderExportFormat export_format; + bool needs_unorm_fixup; auto operator<=>(const PsColorBuffer&) const noexcept = default; }; diff --git a/src/shadps4.qrc b/src/shadps4.qrc index 30f234ed8..40aeb9fb9 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -30,5 +30,6 @@ images/ko-fi.png images/youtube.png images/website.png + images/ps4_controller.png diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 629899a33..d8f6a08d0 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -330,6 +330,16 @@ bool PipelineCache::RefreshGraphicsKey() { continue; } + // Metal seems to have an issue where 8-bit unorm/snorm/sRGB outputs to render target + // need a bias applied to round correctly; detect and set the flag for that here. + const auto needs_unorm_fixup = instance.GetDriverID() == vk::DriverId::eMoltenvk && + (col_buf.GetNumberFmt() == AmdGpu::NumberFormat::Unorm || + col_buf.GetNumberFmt() == AmdGpu::NumberFormat::Snorm || + col_buf.GetNumberFmt() == AmdGpu::NumberFormat::Srgb) && + (col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8 || + col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8 || + col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8_8_8); + key.color_formats[remapped_cb] = LiverpoolToVK::SurfaceFormat(col_buf.GetDataFmt(), col_buf.GetNumberFmt()); key.color_buffers[remapped_cb] = { @@ -337,6 +347,7 @@ bool PipelineCache::RefreshGraphicsKey() { .num_conversion = col_buf.GetNumberConversion(), .swizzle = col_buf.Swizzle(), .export_format = regs.color_export_format.GetFormat(cb), + .needs_unorm_fixup = needs_unorm_fixup, }; }