From 831903799b51c675804c50fc0b7d0cd305dda844 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 2 Feb 2025 14:51:45 -0800 Subject: [PATCH 01/20] shader_recompiler: Insert end of divergence scope at last relevant instruction. (#2325) --- src/shader_recompiler/frontend/control_flow_graph.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index ec5c117f7..12d2a2922 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -161,6 +161,12 @@ void CFG::EmitDivergenceLabels() { // scope. const auto start = inst_list.begin() + curr_begin + 1; if (!std::ranges::all_of(start, inst_list.begin() + index, IgnoresExecMask)) { + // Determine the last instruction affected by the exec mask, so that any + // trailing instructions not affected can be excluded from the scope. + s32 curr_end = index; + while (IgnoresExecMask(inst_list[curr_end - 1])) { + --curr_end; + } // 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]; @@ -173,8 +179,8 @@ void CFG::EmitDivergenceLabels() { // * 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]); + if (curr_end != end_index - 1) { + AddLabel(index_to_pc[curr_end]); } } // Reset scope begin. From 460c266e045766e7d6a2c1e7b54a95124d476a62 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:37:08 -0800 Subject: [PATCH 02/20] fix: Restore previous version of divergence PR. --- .../frontend/control_flow_graph.cpp | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index 12d2a2922..126cb4eb6 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -148,47 +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) { + 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; + } + // 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; - continue; } - // 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)) { - // Determine the last instruction affected by the exec mask, so that any - // trailing instructions not affected can be excluded from the scope. - s32 curr_end = index; - while (IgnoresExecMask(inst_list[curr_end - 1])) { - --curr_end; - } - // 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 (curr_end != end_index - 1) { - AddLabel(index_to_pc[curr_end]); - } - } - // 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; } } } From c5cd4bc5cfade900bc4853a0e1a57b7032a5afd9 Mon Sep 17 00:00:00 2001 From: hspir404 Date: Mon, 3 Feb 2025 10:37:11 +0000 Subject: [PATCH 03/20] Remove log line that was consuming as much as 0.6ms frame time (#2335) --- src/core/libraries/kernel/process.cpp | 1 - 1 file changed, 1 deletion(-) 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; } From 0d65ef6f6e691af3133759e86d55a7cc521a5fa7 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Mon, 3 Feb 2025 11:51:54 +0100 Subject: [PATCH 04/20] Adding french translation to game status and translating a forgotten sentence (#2315) * Add translation to game status and translate a forgotten sentence * menu * vulkan ts --- src/qt_gui/translations/fr.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) 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 + + From 56b2f6c4cf37aa0a55d2f5dc5a86fe9d1f2a3f30 Mon Sep 17 00:00:00 2001 From: Martin <67326368+Martini-141@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:52:10 +0100 Subject: [PATCH 05/20] add missing translations nb (#2317) * add gamestatus tr nb * add missing compatibilityinfoclass tr nb * add missing GameListFrame tr nb --- src/qt_gui/translations/nb.ts | 49 ++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) 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... + + From 8d4261efba96a7d76b5202cbebc4894fa8340d82 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Mon, 3 Feb 2025 07:52:23 -0300 Subject: [PATCH 06/20] Cheats/Patches: Fix Mask Offset (#2323) --- src/common/memory_patcher.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 From a55acae5ee26db97fede27498f2ac3b630c37742 Mon Sep 17 00:00:00 2001 From: Yury <27062841+f1amy@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:52:41 +0500 Subject: [PATCH 07/20] Update ru_RU translation for 6.0 (#2318) * Update ru translation for 6.0 * Escape special characters for ru * Add and translate new strings for ru_RU * Translate `Auto Select` in GPU selection * Ru translation fixes - added translation for `Auto Select` * Fix typos in ru translation --- src/qt_gui/settings_dialog.cpp | 2 +- src/qt_gui/translations/ru_RU.ts | 3011 ++++++++++++++++-------------- 2 files changed, 1608 insertions(+), 1405 deletions(-) diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 802325126..7505db106 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); } 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 + Играбельно + + From 83abaafdfad7685f62a97e0610b169ab713d7320 Mon Sep 17 00:00:00 2001 From: C4ndyF1sh <128715345+C4ndyF1sh@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:53:26 +0100 Subject: [PATCH 08/20] qt: Add more options to the "Copy info..." section + update en.ts/de.ts (#2322) * Update gui_context_menus.h * Update gui_context_menus.h * Update en.ts * Update de.ts * Update gui_context_menus.h * Update gui_context_menus.h * Update gui_context_menus.h * Update gui_context_menus.h * Update gui_context_menus.h * Update gui_context_menus.h * Update gui_context_menus.h * Update gui_context_menus.h * Update gui_context_menus.h * Update gui_context_menus.h * Update en.ts * Update de.ts * Update gui_context_menus.h (last one) * very small fix en.ts * remove empty line * merge https://github.com/shadps4-emu/shadPS4/pull/2316 Adds german translations to compatibility status --- src/qt_gui/gui_context_menus.h | 14 ++++++++++++++ src/qt_gui/translations/de.ts | 35 ++++++++++++++++++++++++++++++++++ src/qt_gui/translations/en.ts | 8 ++++++++ 3 files changed, 57 insertions(+) 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/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..afaa17520 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 From cef7edaea9799122f4dd3787015e15367c13562e Mon Sep 17 00:00:00 2001 From: DemoJameson Date: Mon, 3 Feb 2025 18:53:40 +0800 Subject: [PATCH 09/20] Update zh_CN translation (#2328) --- src/qt_gui/translations/zh_CN.ts | 56 ++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 14 deletions(-) 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 + 可通关 + + From 02ad2b78faf190fb9bc545ccdfadb091913c17c1 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:53:57 +0100 Subject: [PATCH 10/20] Fork detection: Fix Windows naming + add a new check for fork detection (#2321) * Possible fix for Windows * Check if remote.pushDefault is set when generating the remote link * Remove left-in lines I missed before --- CMakeLists.txt | 15 +++++++++++++-- src/emulator.cpp | 10 +++++++--- src/qt_gui/main_window.cpp | 10 +++++++--- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4822658c6..b0e1115b3 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) 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 Date: Mon, 3 Feb 2025 09:37:28 -0600 Subject: [PATCH 11/20] Fix VideoOut events (#2330) * Fix event data for VideoOut events Fix is based on some decompilation work shared by red_prig. * Cleanup * Oops * Style fixes * Clang * Fix libSceVideoOut event idents Based on some decompilation work, events coming from libSceVideoOut use a separate set of values for identifiers. These values are only converted to OrbisVideoOutEventId values during calls to sceVideoOutGetEventId. For convenience, I've placed all relevant identifiers into a enum called OrbisVideoOutInternalEventId. Thanks to @red_prig for the tips. * Fix? Seems like `static_cast(hint) & 0xFF == event.ident` here, and doing those right shifts on the event.ident winds up breaking stuff. Without this change, the if always fails because event_id was getting set to 0 instead. * Clang --- src/core/libraries/kernel/equeue.cpp | 6 +++++- src/core/libraries/kernel/equeue.h | 20 +++++++++++++++++ src/core/libraries/videoout/driver.cpp | 10 +++++---- src/core/libraries/videoout/video_out.cpp | 26 +++++++++++++++++++---- src/core/libraries/videoout/video_out.h | 17 ++++++++++++++- 5 files changed, 69 insertions(+), 10 deletions(-) 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/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..27a3fe082 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; @@ -76,7 +76,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 +156,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) { 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, From 97441b62d1749b0ddeaf20dc8639a1c942d6941a Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 4 Feb 2025 03:49:16 -0300 Subject: [PATCH 12/20] Fix game title sorting - Grid view (#2341) --- src/qt_gui/game_info.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 +}; From e0d85a7e58f56c67a5e2d621738afa12b0436ee7 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:50:32 +0800 Subject: [PATCH 13/20] add controller remapping GUI (#2311) * Initial Version - controller remapping GUI * rework saving to allow for multiple outputs to an input * License header * Remove PS logo from image * Add per game checkbox, fix filename, better whitespace deletion * Reorganize variables, correctly set common config label when selected * Add option to unmap, set mapping to unmapped when config entry is absent * Add XBox labels * Remove parsing from save function, make window more compact * Fix typo * Code cleanup * Additional cleanup --------- Co-authored-by: rainmakerv2 <30595646+jpau02@users.noreply.github.com> --- CMakeLists.txt | 3 + REUSE.toml | 1 + src/images/ps4_controller.png | Bin 0 -> 93841 bytes src/qt_gui/control_settings.cpp | 498 +++++++++++ src/qt_gui/control_settings.h | 52 ++ src/qt_gui/control_settings.ui | 1379 +++++++++++++++++++++++++++++++ src/qt_gui/main_window.cpp | 5 +- src/shadps4.qrc | 1 + 8 files changed, 1937 insertions(+), 2 deletions(-) create mode 100644 src/images/ps4_controller.png create mode 100644 src/qt_gui/control_settings.cpp create mode 100644 src/qt_gui/control_settings.h create mode 100644 src/qt_gui/control_settings.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index b0e1115b3..8ecbbf0d6 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -897,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/src/images/ps4_controller.png b/src/images/ps4_controller.png new file mode 100644 index 0000000000000000000000000000000000000000..0e2c1d4f1a18ad2af8146e9f08fb0b63687b2018 GIT binary patch literal 93841 zcmeAS@N?(olHy`uVBq!ia0y~yV2WU1V4T3g#=yW(WGXjO>_ z%)r1c48n{Iv*t)JFfg!}c>21szhq)!5HMSz_va)71M~T+kcg6?#Bzm#qWrYXoK%I9 z%7RpdirfMQ29M6A;hvKYoAK;{yq%IJw7YAOr$9!I zZ{)v!pY?w-&$rv%<-2X#!jgNtf1WP(ytT9H+oP-EvqPtU{aUqlRmA#vJ0^8MHHn`7 z?2>Bpn+Ktn0**B`-#j&A+8h~w4U@x)q0zY)&n+{$5y5ucaen5R>bE~`tH*a0`e!g^ zOj>^=G5Pk5qs%AWd9TIUO#CK%>#5?|ox-X+wjP<38hfUcZQi{}yb+pV8?ViNT%O)` zmvePjW&d2!^Nh*)YF5V|bzC$&D3GVdWWj>`PKVA7wq<2k; z=7dYC0@F9d{Nz>X3N$=-mO(&N`vkvo5Zew`?mPC28CaHZbq1^#T-n9p_-OC@y$T80 zjtQNsSijBSX8X}+-lIETBr&h)B~LipgO?m~3*X+``D?`y!D`ibnr2TqDCOw3JE*pM#fG$5wYNOhOyP0uX-<=htld?5-8PJ4 zTi!Hl*`fe;{sn7%UyG|1dA;>yzF3~M^Xsf_eWx{NmELhP`c&QFkkUk_{scL)0SCBC6x5r&0nb}6l=-;@|nglR$B*|?E%J3 zZ?<+GH(xJ3%UNx)$#Ivu4G#NVZV5_ynMqFZo2pQfq_~XH=~WlY*#$e~{>(Tr-LG!R z

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 $3eWOd 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

ycBZ^mQyPDF?q-8{py<@b>EfC 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 +#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/main_window.cpp b/src/qt_gui/main_window.cpp index 4a6cb9103..0ab9d3a42 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" @@ -301,8 +302,8 @@ void MainWindow::CreateConnects() { // 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/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 From f8f732e78cd73899580c0b393c347e97a43bfa3b Mon Sep 17 00:00:00 2001 From: makigumo Date: Tue, 4 Feb 2025 07:51:07 +0100 Subject: [PATCH 14/20] fix ASSERT_MSG arguments (#2337) --- .../ir/passes/flatten_extended_userdata_pass.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From fffd3736529578eea87cf756ba01afc90c52f5f9 Mon Sep 17 00:00:00 2001 From: makigumo Date: Tue, 4 Feb 2025 08:24:56 +0100 Subject: [PATCH 15/20] Fix shader type names (#2336) Names didn't match definition in type.h --- src/shader_recompiler/ir/type.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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"; From e4598e882116a9f8c06ce4a32cb2689ea08e4b16 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 4 Feb 2025 09:27:48 +0200 Subject: [PATCH 16/20] sceVideoOutDeleteFlipEvent (#2339) --- src/core/libraries/videoout/video_out.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index 27a3fe082..65713019c 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -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); @@ -374,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); From 363604c6f0c4429877a8c1a55e88eb98538930ed Mon Sep 17 00:00:00 2001 From: Kolja Date: Tue, 4 Feb 2025 08:28:25 +0100 Subject: [PATCH 17/20] Add emulator category (#2320) --- dist/net.shadps4.shadPS4.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From b6ad512e344c5de064d630691436548a402a37c0 Mon Sep 17 00:00:00 2001 From: pdaloxd <31321612+pablodrake@users.noreply.github.com> Date: Tue, 4 Feb 2025 08:33:38 +0100 Subject: [PATCH 18/20] Change Background Image for games (#2334) * Added opacity change instead of blur for background image * Fixed integer overflow when refreshing grid list * Added slider to control background image opacity * Added show background image button * Added UI code for checkbox and English and Spanish translations for new UI elements * Removed background image caching * Background image update on apply/save * Only recompute image if opacity or game changes * Fixed segfault when trying to change opacity after table refresh * Placed background image settings under GUI in settings file --- src/common/config.cpp | 24 +++++++++ src/common/config.h | 4 ++ src/qt_gui/game_grid_frame.cpp | 92 ++++++++++++++++++++------------ src/qt_gui/game_grid_frame.h | 2 + src/qt_gui/game_list_frame.cpp | 51 ++++++++++-------- src/qt_gui/game_list_frame.h | 5 +- src/qt_gui/game_list_utils.h | 26 +++++++++ src/qt_gui/main_window.cpp | 17 ++++++ src/qt_gui/settings_dialog.cpp | 11 ++++ src/qt_gui/settings_dialog.h | 1 + src/qt_gui/settings_dialog.ui | 70 ++++++++++++++++++++++++ src/qt_gui/translations/en.ts | 16 ++++++ src/qt_gui/translations/es_ES.ts | 16 ++++++ 13 files changed, 279 insertions(+), 56 deletions(-) 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/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_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/main_window.cpp b/src/qt_gui/main_window.cpp index 0ab9d3a42..67615a1b6 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -297,6 +297,23 @@ 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(); }); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 7505db106..8f4b22c6d 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -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/en.ts b/src/qt_gui/translations/en.ts index afaa17520..d0540d7cd 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -757,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 @@ -853,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. From b879dd59c674b9dae0bf1c4c090cabdce4521d05 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:01:59 -0800 Subject: [PATCH 19/20] shader_recompiler: Add workaround for drivers with unexpected unorm rounding behavior. (#2310) --- src/shader_recompiler/frontend/translate/export.cpp | 6 +++++- src/shader_recompiler/runtime_info.h | 1 + src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 11 +++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) 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/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/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, }; } From 131b6f90e0a15ace346dcfe64189cb4e2363c5f5 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:37:23 +0100 Subject: [PATCH 20/20] Format log lines to make it possible to ctrl click on them and go to the log location (#2345) --- src/common/logging/text_formatter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) {