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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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 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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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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) { From e757063d3120e68fe53b9c888bc6c5fe7372b406 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 5 Feb 2025 09:21:05 -0600 Subject: [PATCH 21/28] Improved error handling in sceKernelAllocateDirectMemory (#2037) * Handle errors in sceKernelAllocateDirectMemory * Improve accuracy of error cases Some of our existing cases are normally EAGAIN returns. * Improve logging on errors * Clang * TEMPORARY HACK FOR NBA TESTS This will be removed before this PR is marked as ready, and is only here to make sure the other NBA games (and perhaps DOA3) work if some missing init behavior is handled. * Revert "TEMPORARY HACK FOR NBA TESTS" This reverts commit a0e27b0229811778c8390066d00b5221aa5d54a6. * Change error message --- src/core/libraries/kernel/memory.cpp | 28 ++++++++++++++++++++++------ src/core/memory.cpp | 5 ++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 8deefb496..551fd8e3e 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -26,17 +26,20 @@ u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() { int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, u64 alignment, int memoryType, s64* physAddrOut) { - if (searchStart < 0 || searchEnd <= searchStart) { - LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!"); + if (searchStart < 0 || searchEnd < 0) { + LOG_ERROR(Kernel_Vmm, "Invalid parameters!"); return ORBIS_KERNEL_ERROR_EINVAL; } - const bool is_in_range = searchEnd - searchStart >= len; - if (len <= 0 || !Common::Is16KBAligned(len) || !is_in_range) { - LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!"); + if (len <= 0 || !Common::Is16KBAligned(len)) { + LOG_ERROR(Kernel_Vmm, "Length {:#x} is invalid!", len); return ORBIS_KERNEL_ERROR_EINVAL; } if (alignment != 0 && !Common::Is16KBAligned(alignment)) { - LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!"); + LOG_ERROR(Kernel_Vmm, "Alignment {:#x} is invalid!", alignment); + return ORBIS_KERNEL_ERROR_EINVAL; + } + if (memoryType > 10) { + LOG_ERROR(Kernel_Vmm, "Memory type {:#x} is invalid!", memoryType); return ORBIS_KERNEL_ERROR_EINVAL; } if (physAddrOut == nullptr) { @@ -44,8 +47,21 @@ int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u return ORBIS_KERNEL_ERROR_EINVAL; } + const bool is_in_range = searchEnd - searchStart >= len; + if (searchEnd <= searchStart || searchEnd < len || !is_in_range) { + LOG_ERROR(Kernel_Vmm, + "Provided address range is too small!" + " searchStart = {:#x}, searchEnd = {:#x}, length = {:#x}", + searchStart, searchEnd, len); + return ORBIS_KERNEL_ERROR_EAGAIN; + } + auto* memory = Core::Memory::Instance(); PAddr phys_addr = memory->Allocate(searchStart, searchEnd, len, alignment, memoryType); + if (phys_addr == -1) { + return ORBIS_KERNEL_ERROR_EAGAIN; + } + *physAddrOut = static_cast(phys_addr); LOG_INFO(Kernel_Vmm, diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 271092eaf..4717b3a74 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -117,7 +117,10 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, dmem_area->second.GetEnd() <= search_end) { ++dmem_area; } - ASSERT_MSG(is_suitable(), "Unable to find free direct memory area: size = {:#x}", size); + if (!is_suitable()) { + LOG_ERROR(Kernel_Vmm, "Unable to find free direct memory area: size = {:#x}", size); + return -1; + } // Align free position PAddr free_addr = dmem_area->second.base; From ecfc940381a752a7a5ef728ad50826e643b671ee Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 5 Feb 2025 09:24:53 -0600 Subject: [PATCH 22/28] libSceHmd Stubs (#2355) * Add generated libSceHmd stubs * Implement ReprojectionQuery functions These constant returns come from decompiling libSceHmd. * Clang * Clang --- CMakeLists.txt | 5 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + src/core/libraries/hmd/hmd.cpp | 1219 ++++++++++++++++++++++++++++++++ src/core/libraries/hmd/hmd.h | 203 ++++++ src/core/libraries/libs.cpp | 2 + 6 files changed, 1431 insertions(+) create mode 100644 src/core/libraries/hmd/hmd.cpp create mode 100644 src/core/libraries/hmd/hmd.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ecbbf0d6..8837a6584 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -486,6 +486,10 @@ set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp src/core/libraries/zlib/zlib_error.h ) +set(VR_LIBS src/core/libraries/hmd/hmd.cpp + src/core/libraries/hmd/hmd.h +) + set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp src/core/libraries/screenshot/screenshot.h src/core/libraries/move/move.cpp @@ -663,6 +667,7 @@ set(CORE src/core/aerolib/stubs.cpp ${IME_LIB} ${FIBER_LIB} ${VDEC_LIB} + ${VR_LIBS} ${DEV_TOOLS} src/core/debug_state.cpp src/core/debug_state.h diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index dd708c528..1a781cb4c 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -134,6 +134,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, WebBrowserDialog) \ SUB(Lib, NpParty) \ SUB(Lib, Zlib) \ + SUB(Lib, Hmd) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 54f8cdd0b..4078afcef 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -101,6 +101,7 @@ enum class Class : u8 { Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation Lib_NpParty, ///< The LibSceNpParty implementation Lib_Zlib, ///< The LibSceZlib implementation. + Lib_Hmd, ///< The LibSceHmd implementation. Frontend, ///< Emulator UI Render, ///< Video Core Render_Vulkan, ///< Vulkan backend diff --git a/src/core/libraries/hmd/hmd.cpp b/src/core/libraries/hmd/hmd.cpp new file mode 100644 index 000000000..b43789822 --- /dev/null +++ b/src/core/libraries/hmd/hmd.cpp @@ -0,0 +1,1219 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/hmd/hmd.h" +#include "core/libraries/libs.h" + +namespace Libraries::Hmd { + +s32 PS4_SYSV_ABI sceHmdReprojectionStartMultilayer() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdDistortionGet2dVrCommand() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdDistortionGetCompoundEyeCorrectionCommand() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdDistortionGetCorrectionCommand() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdDistortionGetWideNearCorrectionCommand() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdDistortionGetWorkMemoryAlign() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdDistortionGetWorkMemorySize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdDistortionInitialize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdDistortionSetOutputMinColor() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_B26430EA74FC3DC0() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdClose() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGet2DEyeOffset() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGet2dVrCommand() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetAssyError() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetDeviceInformation() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetDeviceInformationByHandle() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetDistortionCorrectionCommand() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetDistortionParams() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetDistortionWorkMemoryAlign() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetDistortionWorkMemorySize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetFieldOfView() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetInertialSensorData() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdGetWideNearDistortionCorrectionCommand() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInitialize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInitialize315() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternal3dAudioClose() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternal3dAudioOpen() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternal3dAudioSendData() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenClose() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenGetAudioStatus() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenGetFadeState() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenGetVideoStatus() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenOpen() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenSendAudio() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenSendVideo() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenSetFadeAndSwitch() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalBindDeviceWithUserId() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalCheckDeviceModelMk3() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalCheckS3dPassModeAvailable() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalCrashReportCancel() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalCrashReportClose() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalCrashReportOpen() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalCrashReportReadData() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalCrashReportReadDataSize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalCreateSharedMemory() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuCheckAfterPvt() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuCheckPartialUpdateAvailable() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuClose() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuGetStatus() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuOpen() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuReset() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuSend() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuSendSize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuSetMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalDfuStart() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalEventInitialize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetBrightness() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetCrashDumpInfo() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetDebugMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetDebugSocialScreenMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetDebugTextMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetDefaultLedData() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetDemoMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetDeviceInformation() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetDeviceInformationByHandle() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetDeviceStatus() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetEyeStatus() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetHmuOpticalParam() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetHmuPowerStatusForDebug() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetHmuSerialNumber() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetIPD() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetIpdSettingEnableForSystemService() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetPuBuildNumber() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetPuPositionParam() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetPuRevision() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetPUSerialNumber() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetPUVersion() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetRequiredPUPVersion() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetStatusReport() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetTv4kCapability() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetVirtualDisplayDepth() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetVirtualDisplayHeight() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetVirtualDisplaySize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalGetVr2dData() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalIsCommonDlgMiniAppVr2d() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalIsCommonDlgVr2d() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalIsGameVr2d() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalIsMiniAppVr2d() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalMapSharedMemory() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalMirroringModeSetAspect() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalMirroringModeSetAspectDebug() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalMmapGetCount() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalMmapGetModeId() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalMmapGetSensorCalibrationData() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalMmapIsConnect() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalPushVr2dData() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalRegisterEventCallback() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalResetInertialSensor() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalResetLedForVrTracker() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalResetLedForVsh() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeClose() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeGetAudioStatus() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeGetVideoStatus() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeOpen() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeSendAudio() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeSendVideo() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetBrightness() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetCrashReportCommand() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetDebugGpo() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetDebugMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetDebugSocialScreenMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetDebugTextMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetDefaultLedData() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetDemoMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetDeviceConnection() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetForcedCrash() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetHmuPowerControl() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetHmuPowerControlForDebug() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetIPD() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetIpdSettingEnableForSystemService() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetLedOn() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetM2LedBrightness() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetM2LedOn() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetPortConnection() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetPortStatus() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetS3dPassMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetSidetone() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetUserType() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetVirtualDisplayDepth() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetVirtualDisplayHeight() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetVirtualDisplaySize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSetVRMode() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSocialScreenGetFadeState() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSocialScreenSetFadeAndSwitch() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdInternalSocialScreenSetOutput() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdOpen() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionAddDisplayBuffer() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionClearUserEventEnd() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionClearUserEventStart() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionDebugGetLastInfo() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionDebugGetLastInfoMultilayer() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionFinalize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionFinalizeCapture() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionInitialize() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionInitializeCapture() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionQueryGarlicBuffAlign() { + return 0x100; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionQueryGarlicBuffSize() { + return 0x100000; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionQueryOnionBuffAlign() { + return 0x100; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionQueryOnionBuffSize() { + return 0x810; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionSetCallback() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionSetDisplayBuffers() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionSetOutputMinColor() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionSetUserEventEnd() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionSetUserEventStart() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStart() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStart2dVr() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStartCapture() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStartLiveCapture() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStartMultilayer2() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStartWideNear() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStartWideNearWithOverlay() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStartWithOverlay() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStop() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStopCapture() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionStopLiveCapture() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionUnsetCallback() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdReprojectionUnsetDisplayBuffers() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceHmdTerminate() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_202D0D1A687FCD2F() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_358DBF818A3D8A12() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_5CCBADA76FE8F40E() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_63D403167DC08CF0() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_69383B2B4E3AEABF() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_791560C32F4F6D68() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_7C955961EA85B6D3() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_9952277839236BA7() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_9A276E739E54EEAF() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_9E501994E289CBE7() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_A31A0320D80EAD99() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_A31F4DA8B3BD2E12() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_A92D7C23AC364993() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_ADCCC25CB876FDBE() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_B16652641FE69F0E() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_B614F290B67FB59B() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_B9A6FA0735EC7E49() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_FC193BD653F2AF2E() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_FF2E0E53015FE231() { + LOG_ERROR(Lib_Hmd, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceHmd(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("8gH1aLgty5I", "libsceHmdReprojectionMultilayer", 1, "libSceHmd", 1, 1, + sceHmdReprojectionStartMultilayer); + LIB_FUNCTION("gEokC+OGI8g", "libSceHmdDistortion", 1, "libSceHmd", 1, 1, + sceHmdDistortionGet2dVrCommand); + LIB_FUNCTION("ER2ar8yUmbk", "libSceHmdDistortion", 1, "libSceHmd", 1, 1, + sceHmdDistortionGetCompoundEyeCorrectionCommand); + LIB_FUNCTION("HT8qWOTOGmo", "libSceHmdDistortion", 1, "libSceHmd", 1, 1, + sceHmdDistortionGetCorrectionCommand); + LIB_FUNCTION("Vkkhy8RFIuk", "libSceHmdDistortion", 1, "libSceHmd", 1, 1, + sceHmdDistortionGetWideNearCorrectionCommand); + LIB_FUNCTION("1cS7W5J-v3k", "libSceHmdDistortion", 1, "libSceHmd", 1, 1, + sceHmdDistortionGetWorkMemoryAlign); + LIB_FUNCTION("36xDKk+Hw7o", "libSceHmdDistortion", 1, "libSceHmd", 1, 1, + sceHmdDistortionGetWorkMemorySize); + LIB_FUNCTION("ao8NZ+FRYJE", "libSceHmdDistortion", 1, "libSceHmd", 1, 1, + sceHmdDistortionInitialize); + LIB_FUNCTION("8A4T5ahi790", "libSceHmdDistortion", 1, "libSceHmd", 1, 1, + sceHmdDistortionSetOutputMinColor); + LIB_FUNCTION("smQw6nT8PcA", "libSceHmdDistortion", 1, "libSceHmd", 1, 1, Func_B26430EA74FC3DC0); + LIB_FUNCTION("6biw1XHTSqQ", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdClose); + LIB_FUNCTION("BWY-qKM5hxE", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdGet2DEyeOffset); + LIB_FUNCTION("za4xJfzCBcM", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdGet2dVrCommand); + LIB_FUNCTION("Yx+CuF11D3Q", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdGetAssyError); + LIB_FUNCTION("thDt9upZlp8", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdGetDeviceInformation); + LIB_FUNCTION("1pxQfif1rkE", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdGetDeviceInformationByHandle); + LIB_FUNCTION("grCYks4m8Jw", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdGetDistortionCorrectionCommand); + LIB_FUNCTION("mP2ZcYmDg-o", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdGetDistortionParams); + LIB_FUNCTION("8Ick-e6cDVY", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdGetDistortionWorkMemoryAlign); + LIB_FUNCTION("D5JfdpJKvXk", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdGetDistortionWorkMemorySize); + LIB_FUNCTION("NPQwYFqi0bs", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdGetFieldOfView); + LIB_FUNCTION("rU3HK9Q0r8o", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdGetInertialSensorData); + LIB_FUNCTION("goi5ASvH-V8", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdGetWideNearDistortionCorrectionCommand); + LIB_FUNCTION("K4KnH0QkT2c", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInitialize); + LIB_FUNCTION("s-J66ar9g50", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInitialize315); + LIB_FUNCTION("riPQfAdebHk", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternal3dAudioClose); + LIB_FUNCTION("wHnZU1qtiqw", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternal3dAudioOpen); + LIB_FUNCTION("NuEjeN8WCBA", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternal3dAudioSendData); + LIB_FUNCTION("QasPTUPWVZE", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalAnotherScreenClose); + LIB_FUNCTION("Wr5KVtyVDG0", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalAnotherScreenGetAudioStatus); + LIB_FUNCTION("whRxl6Hhrzg", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalAnotherScreenGetFadeState); + LIB_FUNCTION("w8BEUsIYn8w", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalAnotherScreenGetVideoStatus); + LIB_FUNCTION("0cQDAbkOt2A", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalAnotherScreenOpen); + LIB_FUNCTION("Asczi8gw1NM", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalAnotherScreenSendAudio); + LIB_FUNCTION("6+v7m1vwE+0", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalAnotherScreenSendVideo); + LIB_FUNCTION("E0BLvy57IiQ", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalAnotherScreenSetFadeAndSwitch); + LIB_FUNCTION("UTqrWB+1+SU", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalBindDeviceWithUserId); + LIB_FUNCTION("ego1YdqNGpI", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalCheckDeviceModelMk3); + LIB_FUNCTION("WR7XsLdjcqQ", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalCheckS3dPassModeAvailable); + LIB_FUNCTION("eMI1Hq+NEwY", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalCrashReportCancel); + LIB_FUNCTION("dI3StPLQlMM", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalCrashReportClose); + LIB_FUNCTION("lqPT-Bf1s4I", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalCrashReportOpen); + LIB_FUNCTION("QxhJs6zHUmU", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalCrashReportReadData); + LIB_FUNCTION("A2jWOLPzHHE", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalCrashReportReadDataSize); + LIB_FUNCTION("E9scVxt0DNg", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalCreateSharedMemory); + LIB_FUNCTION("6RclvsKxr3I", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalDfuCheckAfterPvt); + LIB_FUNCTION("cE99PJR6b8w", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalDfuCheckPartialUpdateAvailable); + LIB_FUNCTION("SuE90Qscg0s", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalDfuClose); + LIB_FUNCTION("5f-6lp7L5cY", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalDfuGetStatus); + LIB_FUNCTION("dv2RqD7ZBd4", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalDfuOpen); + LIB_FUNCTION("pN0HjRU86Jo", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalDfuReset); + LIB_FUNCTION("mdc++HCXSsQ", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalDfuSend); + LIB_FUNCTION("gjyqnphjGZE", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalDfuSendSize); + LIB_FUNCTION("bl4MkWNLxKs", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalDfuSetMode); + LIB_FUNCTION("a1LmvXhZ6TM", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalDfuStart); + LIB_FUNCTION("+UzzSnc0z9A", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalEventInitialize); + LIB_FUNCTION("uQc9P8Hrr6U", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetBrightness); + LIB_FUNCTION("nK1g+MwMV10", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetCrashDumpInfo); + LIB_FUNCTION("L5WZgOTw41Y", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetDebugMode); + LIB_FUNCTION("3w8SkMfCHY0", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetDebugSocialScreenMode); + LIB_FUNCTION("1Xmb76MHXug", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetDebugTextMode); + LIB_FUNCTION("S0ITgPRkfUg", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetDefaultLedData); + LIB_FUNCTION("mxjolbeBa78", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetDemoMode); + LIB_FUNCTION("RFIi20Wp9j0", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetDeviceInformation); + LIB_FUNCTION("P04LQJQZ43Y", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetDeviceInformationByHandle); + LIB_FUNCTION("PPCqsD8B5uM", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetDeviceStatus); + LIB_FUNCTION("-u82z1UhOq4", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetEyeStatus); + LIB_FUNCTION("iINSFzCIaB8", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetHmuOpticalParam); + LIB_FUNCTION("Csuvq2MMXHU", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetHmuPowerStatusForDebug); + LIB_FUNCTION("UhFPniZvm8U", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetHmuSerialNumber); + LIB_FUNCTION("9exeDpk7JU8", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetIPD); + LIB_FUNCTION("yNtYRsxZ6-A", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetIpdSettingEnableForSystemService); + LIB_FUNCTION("EKn+IFVsz0M", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetPuBuildNumber); + LIB_FUNCTION("AxQ6HtktYfQ", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetPuPositionParam); + LIB_FUNCTION("ynKv9QCSbto", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetPuRevision); + LIB_FUNCTION("3jcyx7XOm7A", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetPUSerialNumber); + LIB_FUNCTION("+PDyXnclP5w", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetPUVersion); + LIB_FUNCTION("67q17ERGBuw", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetRequiredPUPVersion); + LIB_FUNCTION("uGyN1CkvwYU", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetStatusReport); + LIB_FUNCTION("p9lSvZujLuo", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetTv4kCapability); + LIB_FUNCTION("-Z+-9u98m9o", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetVirtualDisplayDepth); + LIB_FUNCTION("df+b0FQnnVQ", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetVirtualDisplayHeight); + LIB_FUNCTION("i6yROd9ygJs", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalGetVirtualDisplaySize); + LIB_FUNCTION("Aajiktl6JXU", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalGetVr2dData); + LIB_FUNCTION("GwFVF2KkIT4", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalIsCommonDlgMiniAppVr2d); + LIB_FUNCTION("LWQpWHOSUvk", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalIsCommonDlgVr2d); + LIB_FUNCTION("YiIVBPLxmfE", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalIsGameVr2d); + LIB_FUNCTION("LMlWs+oKHTg", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalIsMiniAppVr2d); + LIB_FUNCTION("nBv4CKUGX0Y", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalMapSharedMemory); + LIB_FUNCTION("4hTD8I3CyAk", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalMirroringModeSetAspect); + LIB_FUNCTION("EJwPtSSZykY", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalMirroringModeSetAspectDebug); + LIB_FUNCTION("r7f7M5q3snU", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalMmapGetCount); + LIB_FUNCTION("gCjTEtEsOOw", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalMmapGetModeId); + LIB_FUNCTION("HAr740Mt9Hs", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalMmapGetSensorCalibrationData); + LIB_FUNCTION("1PNiQR-7L6k", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalMmapIsConnect); + LIB_FUNCTION("9-jaAXUNG-A", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalPushVr2dData); + LIB_FUNCTION("1gkbLH5+kxU", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalRegisterEventCallback); + LIB_FUNCTION("6kHBllapJas", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalResetInertialSensor); + LIB_FUNCTION("k1W6RPkd0mc", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalResetLedForVrTracker); + LIB_FUNCTION("dp1wu22jSGc", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalResetLedForVsh); + LIB_FUNCTION("d2TeoKeqM5U", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSeparateModeClose); + LIB_FUNCTION("WxsnAsjPF7Q", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSeparateModeGetAudioStatus); + LIB_FUNCTION("eOOeG9SpEuc", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSeparateModeGetVideoStatus); + LIB_FUNCTION("gA4Xnn+NSGk", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSeparateModeOpen); + LIB_FUNCTION("stQ7AsondmE", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSeparateModeSendAudio); + LIB_FUNCTION("jfnS-OoDayM", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSeparateModeSendVideo); + LIB_FUNCTION("roHN4ml+tB8", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetBrightness); + LIB_FUNCTION("0z2qLqedQH0", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetCrashReportCommand); + LIB_FUNCTION("xhx5rVZEpnw", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetDebugGpo); + LIB_FUNCTION("e7laRxRGCHc", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetDebugMode); + LIB_FUNCTION("CRyJ7Q-ap3g", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetDebugSocialScreenMode); + LIB_FUNCTION("dG4XPW4juU4", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetDebugTextMode); + LIB_FUNCTION("rAXmGoO-VmE", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetDefaultLedData); + LIB_FUNCTION("lu9I7jnUvWQ", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetDemoMode); + LIB_FUNCTION("hyATMTuQSoQ", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetDeviceConnection); + LIB_FUNCTION("c4mSi64bXUw", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetForcedCrash); + LIB_FUNCTION("U9kPT4g1mFE", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetHmuPowerControl); + LIB_FUNCTION("dX-MVpXIPwQ", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetHmuPowerControlForDebug); + LIB_FUNCTION("4KIjvAf8PCA", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetIPD); + LIB_FUNCTION("NbxTfUKO184", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetIpdSettingEnableForSystemService); + LIB_FUNCTION("NnRKjf+hxW4", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetLedOn); + LIB_FUNCTION("4AP0X9qGhqw", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetM2LedBrightness); + LIB_FUNCTION("Mzzz2HPWM+8", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetM2LedOn); + LIB_FUNCTION("LkBkse9Pit0", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetPortConnection); + LIB_FUNCTION("v243mvYg0Y0", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetPortStatus); + LIB_FUNCTION("EwXvkZpo9Go", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetS3dPassMode); + LIB_FUNCTION("g3DKNOy1tYw", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetSidetone); + LIB_FUNCTION("mjMsl838XM8", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetUserType); + LIB_FUNCTION("8IS0KLkDNQY", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetVirtualDisplayDepth); + LIB_FUNCTION("afhK5KcJOJY", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetVirtualDisplayHeight); + LIB_FUNCTION("+zPvzIiB+BU", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSetVirtualDisplaySize); + LIB_FUNCTION("9z8Lc64NF1c", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdInternalSetVRMode); + LIB_FUNCTION("s5EqYh5kbwM", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSocialScreenGetFadeState); + LIB_FUNCTION("a1LMFZtK9b0", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSocialScreenSetFadeAndSwitch); + LIB_FUNCTION("-6FjKlMA+Yc", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdInternalSocialScreenSetOutput); + LIB_FUNCTION("d2g5Ij7EUzo", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdOpen); + LIB_FUNCTION("NTIbBpSH9ik", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionAddDisplayBuffer); + LIB_FUNCTION("94+Ggm38KCg", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionClearUserEventEnd); + LIB_FUNCTION("mdyFbaJj66M", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionClearUserEventStart); + LIB_FUNCTION("MdV0akauNow", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionDebugGetLastInfo); + LIB_FUNCTION("ymiwVjPB5+k", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionDebugGetLastInfoMultilayer); + LIB_FUNCTION("ZrV5YIqD09I", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionFinalize); + LIB_FUNCTION("utHD2Ab-Ixo", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionFinalizeCapture); + LIB_FUNCTION("OuygGEWkins", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionInitialize); + LIB_FUNCTION("BTrQnC6fcAk", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionInitializeCapture); + LIB_FUNCTION("TkcANcGM0s8", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionQueryGarlicBuffAlign); + LIB_FUNCTION("z0KtN1vqF2E", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionQueryGarlicBuffSize); + LIB_FUNCTION("IWybWbR-xvA", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionQueryOnionBuffAlign); + LIB_FUNCTION("kLUAkN6a1e8", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionQueryOnionBuffSize); + LIB_FUNCTION("6CRWGc-evO4", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionSetCallback); + LIB_FUNCTION("E+dPfjeQLHI", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionSetDisplayBuffers); + LIB_FUNCTION("LjdLRysHU6Y", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionSetOutputMinColor); + LIB_FUNCTION("knyIhlkpLgE", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionSetUserEventEnd); + LIB_FUNCTION("7as0CjXW1B8", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionSetUserEventStart); + LIB_FUNCTION("dntZTJ7meIU", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionStart); + LIB_FUNCTION("q3e8+nEguyE", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionStart2dVr); + LIB_FUNCTION("RrvyU1pjb9A", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionStartCapture); + LIB_FUNCTION("XZ5QUzb4ae0", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionStartLiveCapture); + LIB_FUNCTION("8gH1aLgty5I", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionStartMultilayer); + LIB_FUNCTION("gqAG7JYeE7A", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionStartMultilayer2); + LIB_FUNCTION("3JyuejcNhC0", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionStartWideNear); + LIB_FUNCTION("mKa8scOc4-k", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionStartWideNearWithOverlay); + LIB_FUNCTION("kcldQ7zLYQQ", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionStartWithOverlay); + LIB_FUNCTION("vzMEkwBQciM", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionStop); + LIB_FUNCTION("F7Sndm5teWw", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionStopCapture); + LIB_FUNCTION("PAa6cUL5bR4", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionStopLiveCapture); + LIB_FUNCTION("0wnZViigP9o", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdReprojectionUnsetCallback); + LIB_FUNCTION("iGNNpDDjcwo", "libSceHmd", 1, "libSceHmd", 1, 1, + sceHmdReprojectionUnsetDisplayBuffers); + LIB_FUNCTION("z-RMILqP6tE", "libSceHmd", 1, "libSceHmd", 1, 1, sceHmdTerminate); + LIB_FUNCTION("IC0NGmh-zS8", "libSceHmd", 1, "libSceHmd", 1, 1, Func_202D0D1A687FCD2F); + LIB_FUNCTION("NY2-gYo9ihI", "libSceHmd", 1, "libSceHmd", 1, 1, Func_358DBF818A3D8A12); + LIB_FUNCTION("XMutp2-o9A4", "libSceHmd", 1, "libSceHmd", 1, 1, Func_5CCBADA76FE8F40E); + LIB_FUNCTION("Y9QDFn3AjPA", "libSceHmd", 1, "libSceHmd", 1, 1, Func_63D403167DC08CF0); + LIB_FUNCTION("aTg7K0466r8", "libSceHmd", 1, "libSceHmd", 1, 1, Func_69383B2B4E3AEABF); + LIB_FUNCTION("eRVgwy9PbWg", "libSceHmd", 1, "libSceHmd", 1, 1, Func_791560C32F4F6D68); + LIB_FUNCTION("fJVZYeqFttM", "libSceHmd", 1, "libSceHmd", 1, 1, Func_7C955961EA85B6D3); + LIB_FUNCTION("mVIneDkja6c", "libSceHmd", 1, "libSceHmd", 1, 1, Func_9952277839236BA7); + LIB_FUNCTION("miduc55U7q8", "libSceHmd", 1, "libSceHmd", 1, 1, Func_9A276E739E54EEAF); + LIB_FUNCTION("nlAZlOKJy+c", "libSceHmd", 1, "libSceHmd", 1, 1, Func_9E501994E289CBE7); + LIB_FUNCTION("oxoDINgOrZk", "libSceHmd", 1, "libSceHmd", 1, 1, Func_A31A0320D80EAD99); + LIB_FUNCTION("ox9NqLO9LhI", "libSceHmd", 1, "libSceHmd", 1, 1, Func_A31F4DA8B3BD2E12); + LIB_FUNCTION("qS18I6w2SZM", "libSceHmd", 1, "libSceHmd", 1, 1, Func_A92D7C23AC364993); + LIB_FUNCTION("rczCXLh2-b4", "libSceHmd", 1, "libSceHmd", 1, 1, Func_ADCCC25CB876FDBE); + LIB_FUNCTION("sWZSZB-mnw4", "libSceHmd", 1, "libSceHmd", 1, 1, Func_B16652641FE69F0E); + LIB_FUNCTION("thTykLZ-tZs", "libSceHmd", 1, "libSceHmd", 1, 1, Func_B614F290B67FB59B); + LIB_FUNCTION("uab6BzXsfkk", "libSceHmd", 1, "libSceHmd", 1, 1, Func_B9A6FA0735EC7E49); + LIB_FUNCTION("-Bk71lPyry4", "libSceHmd", 1, "libSceHmd", 1, 1, Func_FC193BD653F2AF2E); + LIB_FUNCTION("-y4OUwFf4jE", "libSceHmd", 1, "libSceHmd", 1, 1, Func_FF2E0E53015FE231); +}; + +} // namespace Libraries::Hmd \ No newline at end of file diff --git a/src/core/libraries/hmd/hmd.h b/src/core/libraries/hmd/hmd.h new file mode 100644 index 000000000..12f1ac70a --- /dev/null +++ b/src/core/libraries/hmd/hmd.h @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Hmd { + +s32 PS4_SYSV_ABI sceHmdReprojectionStartMultilayer(); +s32 PS4_SYSV_ABI sceHmdDistortionGet2dVrCommand(); +s32 PS4_SYSV_ABI sceHmdDistortionGetCompoundEyeCorrectionCommand(); +s32 PS4_SYSV_ABI sceHmdDistortionGetCorrectionCommand(); +s32 PS4_SYSV_ABI sceHmdDistortionGetWideNearCorrectionCommand(); +s32 PS4_SYSV_ABI sceHmdDistortionGetWorkMemoryAlign(); +s32 PS4_SYSV_ABI sceHmdDistortionGetWorkMemorySize(); +s32 PS4_SYSV_ABI sceHmdDistortionInitialize(); +s32 PS4_SYSV_ABI sceHmdDistortionSetOutputMinColor(); +s32 PS4_SYSV_ABI Func_B26430EA74FC3DC0(); +s32 PS4_SYSV_ABI sceHmdClose(); +s32 PS4_SYSV_ABI sceHmdGet2DEyeOffset(); +s32 PS4_SYSV_ABI sceHmdGet2dVrCommand(); +s32 PS4_SYSV_ABI sceHmdGetAssyError(); +s32 PS4_SYSV_ABI sceHmdGetDeviceInformation(); +s32 PS4_SYSV_ABI sceHmdGetDeviceInformationByHandle(); +s32 PS4_SYSV_ABI sceHmdGetDistortionCorrectionCommand(); +s32 PS4_SYSV_ABI sceHmdGetDistortionParams(); +s32 PS4_SYSV_ABI sceHmdGetDistortionWorkMemoryAlign(); +s32 PS4_SYSV_ABI sceHmdGetDistortionWorkMemorySize(); +s32 PS4_SYSV_ABI sceHmdGetFieldOfView(); +s32 PS4_SYSV_ABI sceHmdGetInertialSensorData(); +s32 PS4_SYSV_ABI sceHmdGetWideNearDistortionCorrectionCommand(); +s32 PS4_SYSV_ABI sceHmdInitialize(); +s32 PS4_SYSV_ABI sceHmdInitialize315(); +s32 PS4_SYSV_ABI sceHmdInternal3dAudioClose(); +s32 PS4_SYSV_ABI sceHmdInternal3dAudioOpen(); +s32 PS4_SYSV_ABI sceHmdInternal3dAudioSendData(); +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenClose(); +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenGetAudioStatus(); +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenGetFadeState(); +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenGetVideoStatus(); +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenOpen(); +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenSendAudio(); +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenSendVideo(); +s32 PS4_SYSV_ABI sceHmdInternalAnotherScreenSetFadeAndSwitch(); +s32 PS4_SYSV_ABI sceHmdInternalBindDeviceWithUserId(); +s32 PS4_SYSV_ABI sceHmdInternalCheckDeviceModelMk3(); +s32 PS4_SYSV_ABI sceHmdInternalCheckS3dPassModeAvailable(); +s32 PS4_SYSV_ABI sceHmdInternalCrashReportCancel(); +s32 PS4_SYSV_ABI sceHmdInternalCrashReportClose(); +s32 PS4_SYSV_ABI sceHmdInternalCrashReportOpen(); +s32 PS4_SYSV_ABI sceHmdInternalCrashReportReadData(); +s32 PS4_SYSV_ABI sceHmdInternalCrashReportReadDataSize(); +s32 PS4_SYSV_ABI sceHmdInternalCreateSharedMemory(); +s32 PS4_SYSV_ABI sceHmdInternalDfuCheckAfterPvt(); +s32 PS4_SYSV_ABI sceHmdInternalDfuCheckPartialUpdateAvailable(); +s32 PS4_SYSV_ABI sceHmdInternalDfuClose(); +s32 PS4_SYSV_ABI sceHmdInternalDfuGetStatus(); +s32 PS4_SYSV_ABI sceHmdInternalDfuOpen(); +s32 PS4_SYSV_ABI sceHmdInternalDfuReset(); +s32 PS4_SYSV_ABI sceHmdInternalDfuSend(); +s32 PS4_SYSV_ABI sceHmdInternalDfuSendSize(); +s32 PS4_SYSV_ABI sceHmdInternalDfuSetMode(); +s32 PS4_SYSV_ABI sceHmdInternalDfuStart(); +s32 PS4_SYSV_ABI sceHmdInternalEventInitialize(); +s32 PS4_SYSV_ABI sceHmdInternalGetBrightness(); +s32 PS4_SYSV_ABI sceHmdInternalGetCrashDumpInfo(); +s32 PS4_SYSV_ABI sceHmdInternalGetDebugMode(); +s32 PS4_SYSV_ABI sceHmdInternalGetDebugSocialScreenMode(); +s32 PS4_SYSV_ABI sceHmdInternalGetDebugTextMode(); +s32 PS4_SYSV_ABI sceHmdInternalGetDefaultLedData(); +s32 PS4_SYSV_ABI sceHmdInternalGetDemoMode(); +s32 PS4_SYSV_ABI sceHmdInternalGetDeviceInformation(); +s32 PS4_SYSV_ABI sceHmdInternalGetDeviceInformationByHandle(); +s32 PS4_SYSV_ABI sceHmdInternalGetDeviceStatus(); +s32 PS4_SYSV_ABI sceHmdInternalGetEyeStatus(); +s32 PS4_SYSV_ABI sceHmdInternalGetHmuOpticalParam(); +s32 PS4_SYSV_ABI sceHmdInternalGetHmuPowerStatusForDebug(); +s32 PS4_SYSV_ABI sceHmdInternalGetHmuSerialNumber(); +s32 PS4_SYSV_ABI sceHmdInternalGetIPD(); +s32 PS4_SYSV_ABI sceHmdInternalGetIpdSettingEnableForSystemService(); +s32 PS4_SYSV_ABI sceHmdInternalGetPuBuildNumber(); +s32 PS4_SYSV_ABI sceHmdInternalGetPuPositionParam(); +s32 PS4_SYSV_ABI sceHmdInternalGetPuRevision(); +s32 PS4_SYSV_ABI sceHmdInternalGetPUSerialNumber(); +s32 PS4_SYSV_ABI sceHmdInternalGetPUVersion(); +s32 PS4_SYSV_ABI sceHmdInternalGetRequiredPUPVersion(); +s32 PS4_SYSV_ABI sceHmdInternalGetStatusReport(); +s32 PS4_SYSV_ABI sceHmdInternalGetTv4kCapability(); +s32 PS4_SYSV_ABI sceHmdInternalGetVirtualDisplayDepth(); +s32 PS4_SYSV_ABI sceHmdInternalGetVirtualDisplayHeight(); +s32 PS4_SYSV_ABI sceHmdInternalGetVirtualDisplaySize(); +s32 PS4_SYSV_ABI sceHmdInternalGetVr2dData(); +s32 PS4_SYSV_ABI sceHmdInternalIsCommonDlgMiniAppVr2d(); +s32 PS4_SYSV_ABI sceHmdInternalIsCommonDlgVr2d(); +s32 PS4_SYSV_ABI sceHmdInternalIsGameVr2d(); +s32 PS4_SYSV_ABI sceHmdInternalIsMiniAppVr2d(); +s32 PS4_SYSV_ABI sceHmdInternalMapSharedMemory(); +s32 PS4_SYSV_ABI sceHmdInternalMirroringModeSetAspect(); +s32 PS4_SYSV_ABI sceHmdInternalMirroringModeSetAspectDebug(); +s32 PS4_SYSV_ABI sceHmdInternalMmapGetCount(); +s32 PS4_SYSV_ABI sceHmdInternalMmapGetModeId(); +s32 PS4_SYSV_ABI sceHmdInternalMmapGetSensorCalibrationData(); +s32 PS4_SYSV_ABI sceHmdInternalMmapIsConnect(); +s32 PS4_SYSV_ABI sceHmdInternalPushVr2dData(); +s32 PS4_SYSV_ABI sceHmdInternalRegisterEventCallback(); +s32 PS4_SYSV_ABI sceHmdInternalResetInertialSensor(); +s32 PS4_SYSV_ABI sceHmdInternalResetLedForVrTracker(); +s32 PS4_SYSV_ABI sceHmdInternalResetLedForVsh(); +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeClose(); +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeGetAudioStatus(); +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeGetVideoStatus(); +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeOpen(); +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeSendAudio(); +s32 PS4_SYSV_ABI sceHmdInternalSeparateModeSendVideo(); +s32 PS4_SYSV_ABI sceHmdInternalSetBrightness(); +s32 PS4_SYSV_ABI sceHmdInternalSetCrashReportCommand(); +s32 PS4_SYSV_ABI sceHmdInternalSetDebugGpo(); +s32 PS4_SYSV_ABI sceHmdInternalSetDebugMode(); +s32 PS4_SYSV_ABI sceHmdInternalSetDebugSocialScreenMode(); +s32 PS4_SYSV_ABI sceHmdInternalSetDebugTextMode(); +s32 PS4_SYSV_ABI sceHmdInternalSetDefaultLedData(); +s32 PS4_SYSV_ABI sceHmdInternalSetDemoMode(); +s32 PS4_SYSV_ABI sceHmdInternalSetDeviceConnection(); +s32 PS4_SYSV_ABI sceHmdInternalSetForcedCrash(); +s32 PS4_SYSV_ABI sceHmdInternalSetHmuPowerControl(); +s32 PS4_SYSV_ABI sceHmdInternalSetHmuPowerControlForDebug(); +s32 PS4_SYSV_ABI sceHmdInternalSetIPD(); +s32 PS4_SYSV_ABI sceHmdInternalSetIpdSettingEnableForSystemService(); +s32 PS4_SYSV_ABI sceHmdInternalSetLedOn(); +s32 PS4_SYSV_ABI sceHmdInternalSetM2LedBrightness(); +s32 PS4_SYSV_ABI sceHmdInternalSetM2LedOn(); +s32 PS4_SYSV_ABI sceHmdInternalSetPortConnection(); +s32 PS4_SYSV_ABI sceHmdInternalSetPortStatus(); +s32 PS4_SYSV_ABI sceHmdInternalSetS3dPassMode(); +s32 PS4_SYSV_ABI sceHmdInternalSetSidetone(); +s32 PS4_SYSV_ABI sceHmdInternalSetUserType(); +s32 PS4_SYSV_ABI sceHmdInternalSetVirtualDisplayDepth(); +s32 PS4_SYSV_ABI sceHmdInternalSetVirtualDisplayHeight(); +s32 PS4_SYSV_ABI sceHmdInternalSetVirtualDisplaySize(); +s32 PS4_SYSV_ABI sceHmdInternalSetVRMode(); +s32 PS4_SYSV_ABI sceHmdInternalSocialScreenGetFadeState(); +s32 PS4_SYSV_ABI sceHmdInternalSocialScreenSetFadeAndSwitch(); +s32 PS4_SYSV_ABI sceHmdInternalSocialScreenSetOutput(); +s32 PS4_SYSV_ABI sceHmdOpen(); +s32 PS4_SYSV_ABI sceHmdReprojectionAddDisplayBuffer(); +s32 PS4_SYSV_ABI sceHmdReprojectionClearUserEventEnd(); +s32 PS4_SYSV_ABI sceHmdReprojectionClearUserEventStart(); +s32 PS4_SYSV_ABI sceHmdReprojectionDebugGetLastInfo(); +s32 PS4_SYSV_ABI sceHmdReprojectionDebugGetLastInfoMultilayer(); +s32 PS4_SYSV_ABI sceHmdReprojectionFinalize(); +s32 PS4_SYSV_ABI sceHmdReprojectionFinalizeCapture(); +s32 PS4_SYSV_ABI sceHmdReprojectionInitialize(); +s32 PS4_SYSV_ABI sceHmdReprojectionInitializeCapture(); +s32 PS4_SYSV_ABI sceHmdReprojectionQueryGarlicBuffAlign(); +s32 PS4_SYSV_ABI sceHmdReprojectionQueryGarlicBuffSize(); +s32 PS4_SYSV_ABI sceHmdReprojectionQueryOnionBuffAlign(); +s32 PS4_SYSV_ABI sceHmdReprojectionQueryOnionBuffSize(); +s32 PS4_SYSV_ABI sceHmdReprojectionSetCallback(); +s32 PS4_SYSV_ABI sceHmdReprojectionSetDisplayBuffers(); +s32 PS4_SYSV_ABI sceHmdReprojectionSetOutputMinColor(); +s32 PS4_SYSV_ABI sceHmdReprojectionSetUserEventEnd(); +s32 PS4_SYSV_ABI sceHmdReprojectionSetUserEventStart(); +s32 PS4_SYSV_ABI sceHmdReprojectionStart(); +s32 PS4_SYSV_ABI sceHmdReprojectionStart2dVr(); +s32 PS4_SYSV_ABI sceHmdReprojectionStartCapture(); +s32 PS4_SYSV_ABI sceHmdReprojectionStartLiveCapture(); +s32 PS4_SYSV_ABI sceHmdReprojectionStartMultilayer2(); +s32 PS4_SYSV_ABI sceHmdReprojectionStartWideNear(); +s32 PS4_SYSV_ABI sceHmdReprojectionStartWideNearWithOverlay(); +s32 PS4_SYSV_ABI sceHmdReprojectionStartWithOverlay(); +s32 PS4_SYSV_ABI sceHmdReprojectionStop(); +s32 PS4_SYSV_ABI sceHmdReprojectionStopCapture(); +s32 PS4_SYSV_ABI sceHmdReprojectionStopLiveCapture(); +s32 PS4_SYSV_ABI sceHmdReprojectionUnsetCallback(); +s32 PS4_SYSV_ABI sceHmdReprojectionUnsetDisplayBuffers(); +s32 PS4_SYSV_ABI sceHmdTerminate(); +s32 PS4_SYSV_ABI Func_202D0D1A687FCD2F(); +s32 PS4_SYSV_ABI Func_358DBF818A3D8A12(); +s32 PS4_SYSV_ABI Func_5CCBADA76FE8F40E(); +s32 PS4_SYSV_ABI Func_63D403167DC08CF0(); +s32 PS4_SYSV_ABI Func_69383B2B4E3AEABF(); +s32 PS4_SYSV_ABI Func_791560C32F4F6D68(); +s32 PS4_SYSV_ABI Func_7C955961EA85B6D3(); +s32 PS4_SYSV_ABI Func_9952277839236BA7(); +s32 PS4_SYSV_ABI Func_9A276E739E54EEAF(); +s32 PS4_SYSV_ABI Func_9E501994E289CBE7(); +s32 PS4_SYSV_ABI Func_A31A0320D80EAD99(); +s32 PS4_SYSV_ABI Func_A31F4DA8B3BD2E12(); +s32 PS4_SYSV_ABI Func_A92D7C23AC364993(); +s32 PS4_SYSV_ABI Func_ADCCC25CB876FDBE(); +s32 PS4_SYSV_ABI Func_B16652641FE69F0E(); +s32 PS4_SYSV_ABI Func_B614F290B67FB59B(); +s32 PS4_SYSV_ABI Func_B9A6FA0735EC7E49(); +s32 PS4_SYSV_ABI Func_FC193BD653F2AF2E(); +s32 PS4_SYSV_ABI Func_FF2E0E53015FE231(); + +void RegisterlibSceHmd(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Hmd \ No newline at end of file diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 8cf286d13..074cf524e 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -11,6 +11,7 @@ #include "core/libraries/disc_map/disc_map.h" #include "core/libraries/game_live_streaming/gamelivestreaming.h" #include "core/libraries/gnmdriver/gnmdriver.h" +#include "core/libraries/hmd/hmd.h" #include "core/libraries/ime/error_dialog.h" #include "core/libraries/ime/ime.h" #include "core/libraries/ime/ime_dialog.h" @@ -113,6 +114,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::WebBrowserDialog::RegisterlibSceWebBrowserDialog(sym); Libraries::NpParty::RegisterlibSceNpParty(sym); Libraries::Zlib::RegisterlibSceZlib(sym); + Libraries::Hmd::RegisterlibSceHmd(sym); } } // namespace Libraries From 126cb824eaf914f798b8e9ab6b5bcd5d3e4e9bd1 Mon Sep 17 00:00:00 2001 From: Daniel Nylander Date: Wed, 5 Feb 2025 16:25:15 +0100 Subject: [PATCH 23/28] Updated Swedish translation (#2327) * Adding Swedish translation * Updated Swedish translation with additional strings Updated the Swedish translations using lupdate to found additional strings cd src/qt_gui/treanslations lupdate ../../../../shadPS4/ -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATE,QT_TRANSLATE_NOOP+=TRANSLATE_SV,QT_TRANSLATE_NOOP+=TRANSLATE_STR,QT_TRANSLATE_NOOP+=TRANSLATE_FS,QT_TRANSLATE_N_NOOP3+=TRANSLATE_FMT,QT_TRANSLATE_NOOP+=TRANSLATE_NOOP,translate+=TRANSLATE_PLURAL_STR,translate+=TRANSLATE_PLURAL_FS -no-obsolete -locations none -source-language en -ts sv.ts * Update sv.ts * Updated Swedish translation * Adding copyright boilerplate * Updated Swedish translation * Updated Swedish translation * Update sv.ts whitespace in boilerplate --- src/qt_gui/translations/sv.ts | 114 ++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 41 deletions(-) diff --git a/src/qt_gui/translations/sv.ts b/src/qt_gui/translations/sv.ts index deefa99d8..179064ef4 100644 --- a/src/qt_gui/translations/sv.ts +++ b/src/qt_gui/translations/sv.ts @@ -1,7 +1,7 @@ - AboutDialog @@ -210,7 +210,7 @@ Incompatibility Notice - Inkompatibilitetsmeddelande + Meddelande om inkompatibilitet Failed to open file: @@ -386,6 +386,30 @@ Unable to open compatibility.json for writing. Kunde inte öppna compatibility.json för skrivning. + + Unknown + Okänt + + + Nothing + Ingenting + + + Boots + Startar upp + + + Menus + Menyer + + + Ingame + Problem + + + Playable + Spelbart + ElfViewer @@ -756,7 +780,7 @@ Open shadPS4 Folder - Open shadPS4 Folder + Öppna shadPS4-mapp Exit @@ -788,7 +812,7 @@ Medium - Medel + Medelstora Large @@ -828,7 +852,7 @@ File - Arkiv + Fil View @@ -860,19 +884,19 @@ Dark - Mörk + Mörkt Light - Ljus + Ljust Green - Grön + Grönt Blue - Blå + Blått Violet @@ -880,7 +904,7 @@ toolBar - toolBar + Verktygsrad Game List @@ -1084,6 +1108,10 @@ Show Splash Visa startskärm + + ps4proCheckBox + Är PS4 Pro:\nGör att emulatorn agerar som en PS4 PRO, vilket kan aktivera speciella funktioner i spel som har stöd för det + Enable Discord Rich Presence Aktivera Discord Rich Presence @@ -1098,7 +1126,7 @@ Trophy - Trofé + Troféer Logger @@ -1122,15 +1150,15 @@ Cursor - Pekare + Muspekare Hide Cursor - Dölj pekare + Dölj muspekare Hide Cursor Idle Timeout - Dölj pekare vid overksam + Dölj muspekare vid overksam s @@ -1174,7 +1202,7 @@ Advanced - Avancerat + Avancerat Enable Shaders Dumping @@ -1222,23 +1250,23 @@ Enable Crash Diagnostics - Enable Crash Diagnostics + Aktivera kraschdiagnostik Collect Shaders - Collect Shaders + Samla shaders Copy GPU Buffers - Copy GPU Buffers + Kopiera GPU-buffertar Host Debug Markers - Host Debug Markers + Felsökningsmarkörer för värd Guest Debug Markers - Guest Debug Markers + Felsökningsmarkörer för gäst Update @@ -1262,7 +1290,7 @@ Title Music - Title Music + Titelmusik Disable Trophy Pop-ups @@ -1310,7 +1338,7 @@ Point your mouse at an option to display its description. - Peka din mus på ett alternativ för att visa dess beskrivning. + Flytta muspekaren till ett alternativ för att visa dess beskrivning. consoleLanguageGroupBox @@ -1465,25 +1493,25 @@ Aktivera RenderDoc-felsökning:\nOm aktiverad kommer emulatorn att tillhandahålla kompatibilitet med Renderdoc för att tillåta fångst och analys för aktuell renderad bildruta - 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. - + collectShaderCheckBox + Samla shaders:\nDu behöver aktivera detta för att redigera shaders med felsökningsmenyn (Ctrl + F10) + + + crashDiagnosticsCheckBox + Krashdiagnostik:\nSkapar en .yaml-fil med information om Vulkan-tillståndet vid tid för kraschen.\nAnvändbart för felsökning av 'Device lost'-fel. Om du har aktiverat detta bör du aktivera felsökningsmarkörer för Värd OCH Gäst.\nFungerar inte på Intel GPUer.\nDu behöver aktivera Vulkan Validation Layers och Vulkan SDK för att detta ska fungera + + + copyGPUBuffersCheckBox + Kopiera GPU-buffertar:\nGör att man kan komma runt race conditions som involverar GPU submits.\nKan eller kan inte hjälpa med PM4 type 0-kraschar + + + hostMarkersCheckBox + Felsökningsmarkörer för värd:\nInfogar informationsliknande markörer i emulatorn för specifika AMDGPU-kommandon runt Vulkan-kommandon, så väl som ger resurser felsökningsnamn.\nOm du har detta aktiverat bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc + + + guestMarkersCheckBox + Felsökningsmarkörer för gäst:\nInfogar felsökningsmarkörer som själva spelet har lagt till i kommandobufferten.\nOm du har aktiverat detta bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc + Release Release @@ -1540,6 +1568,10 @@ browseButton Bläddra:\nBläddra efter en mapp att ställa in som sökväg för sparat data + + GUI + Gränssnitt + TrophyViewer From 00465d5e41a29ac0160d8d5400c071f2de7b4a12 Mon Sep 17 00:00:00 2001 From: SAN4EZDREAMS <126363936+SAN4EZDREAMS@users.noreply.github.com> Date: Wed, 5 Feb 2025 17:25:36 +0200 Subject: [PATCH 24/28] Updated uk_UA language to v0.6.1 (#2347) * Add Ukrainian localization * Fixed langIndexes * Fixed langIndexes_2 * Added uk_UA language support * Added uk_UA language support * Updated uk_UA localization to the latest version && corrected lexical mistakes * Added missing lines in the translation and minor edits * Correction * Second Correction * Added missed strings work test * Added more missed strings --------- Co-authored-by: SAN4EZDREAMS --- src/qt_gui/translations/uk_UA.ts | 332 ++++++++++++++++++++++--------- 1 file changed, 240 insertions(+), 92 deletions(-) diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index e80d363d3..3fb26546e 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -54,6 +54,14 @@ Select which directory you want to install to. Виберіть папку, до якої ви хочете встановити. + + Install All Queued to Selected Folder + Встановити все з черги до вибраної папки + + + Delete PKG File on Install + Видалити файл PKG під час встановлення + GameInstallDialog @@ -102,15 +110,19 @@ Open Game Folder - Відкрити папку з грою + Відкрити папку гри + + Open Update Folder + Відкрити папку оновлень + Open Save Data Folder - Відкрити Папку Збережених Даних + Відкрити папку збережень гри Open Log Folder - Відкрити Папку Логів + Відкрити папку логів Copy info... @@ -118,19 +130,27 @@ Copy Name - Копіювати Ім’я + Копіювати назву гри Copy Serial Копіювати серійний номер + + Copy Version + Копіювати версію + + + Copy Size + Копіювати розмір + Copy All Копіювати все Delete... - Видалення... + Видалити... Delete Game @@ -140,25 +160,29 @@ Delete Update Видалити оновлення + + Delete Save Data + Видалити збереження + Delete DLC Видалити DLC Compatibility... - Compatibility... + Сумісність... Update database - Update database + Оновити базу даних View report - View report + Переглянути звіт Submit a report - Submit a report + Створити звіт Shortcut creation @@ -182,7 +206,7 @@ Game - Ігри + гри requiresEnableSeparateUpdateFolder_MSG @@ -192,10 +216,22 @@ This game has no update to delete! Ця гра не має оновлень для видалення! + + This game has no update folder to open! + Ця гра не має папки оновленнь, щоб відкрити її! + Update Оновлення + + This game has no save data to delete! + Ця гра не містить збережень, які можна видалити! + + + Save Data + Збереження + This game has no DLC to delete! Ця гра не має DLC для видалення! @@ -206,11 +242,11 @@ Delete %1 - Видалити %1 + Видалення %1 Are you sure you want to delete %1's %2 directory? - Ви впевнені, що хочете видалити папку %1 з папки %2?? + Ви впевнені, що хочете видалити %1 з папки %2? @@ -229,7 +265,7 @@ Check for Updates - Перевити наявність оновлень + Перевірити наявність оновлень About shadPS4 @@ -249,7 +285,7 @@ Open shadPS4 Folder - Open shadPS4 Folder + Відкрити папку shadPS4 Exit @@ -297,15 +333,15 @@ Elf Viewer - Elf + Виконуваний файл Game Install Directory - Каталог встановлення гри + Каталоги встановлення ігор та оновлень Download Cheats/Patches - Завантажити Чити або Патчі + Завантажити Чити/Патчі Dump Game List @@ -329,7 +365,7 @@ Game List Icons - Розмір значків списку игр + Розмір значків списку ігор Game List Mode @@ -385,7 +421,7 @@ Download Cheats For All Installed Games - Завантажити чити для всіх встановлених ігор + Завантажити чити для усіх встановлених ігор Download Patches For All Games @@ -397,7 +433,7 @@ You have downloaded cheats for all the games you have installed. - Ви завантажили чити для всіх встановлених ігор. + Ви завантажили чити для усіх встановлених ігор. Patches Downloaded Successfully! @@ -417,7 +453,7 @@ ELF files (*.bin *.elf *.oelf) - Файл ELF (*.bin *.elf *.oelf) + Файли ELF (*.bin *.elf *.oelf) Game Boot @@ -429,7 +465,7 @@ PKG Extraction - Видобуток PKG + Розпакування PKG Patch detected! @@ -449,7 +485,7 @@ Game is installed: - Гра встановлена: + Встановлена гра: Would you like to install Patch: @@ -461,7 +497,7 @@ Would you like to install DLC: %1? - Ви бажаєте встановити DLC: %1?? + Ви бажаєте встановити DLC: %1? DLC already installed: @@ -481,11 +517,11 @@ Extracting PKG %1/%2 - Вилучення PKG %1/%2 + Витягування PKG %1/%2 Extraction Finished - Вилучення завершено + Розпакування завершено Game successfully installed at %1 @@ -542,8 +578,16 @@ Fullscreen Mode - Режим Повноекранний + Тип повноекранного режиму + + Borderless + Без рамок + + + True + Повний екран + Enable Separate Update Folder Увімкнути окрему папку оновлень @@ -554,7 +598,7 @@ Show Game Size In List - Показати розмір гри в списку + Показати розмір гри у списку Show Splash @@ -574,11 +618,11 @@ Trophy Key - Trophy Key + Ключ трофеїв Trophy - Trophy + Трофеї Logger @@ -588,6 +632,14 @@ Log Type Тип логів + + async + Асинхронний + + + sync + Синхронний + Log Filter Фільтр логів @@ -614,7 +666,7 @@ s - s + сек Controller @@ -622,14 +674,18 @@ Back Button Behavior - Поведінка кнопки назад + Перепризначення кнопки назад + + Enable Motion Controls + Увімкнути керування рухом + Graphics Графіка - Gui + GUI Інтерфейс @@ -660,6 +716,10 @@ Enable Shaders Dumping Увімкнути дамп шейдерів + + Auto Select + Автовибір + Enable NULL GPU Увімкнути NULL GPU @@ -678,8 +738,12 @@ Remove - Видалити + Вилучити + + Save Data Path + Шлях до файлів збережень + Debug Налагодження @@ -702,36 +766,45 @@ Enable Crash Diagnostics - Enable Crash Diagnostics + Увімкнути діагностику збоїв Collect Shaders - Collect Shaders + Збирати шейдери Copy GPU Buffers - Copy GPU Buffers + Копіювати буфери GPU Host Debug Markers - Host Debug Markers + Хостові маркери налагодження Guest Debug Markers - Guest Debug Markers + Гостьові маркери налагодження + Update Оновлення Check for Updates at Startup - Перевірка оновлень під час запуску + Перевіряти оновлення під час запуску Update Channel Канал оновлення + + Release + Релізний + + + Nightly + Тестовий + Check for Updates Перевірити оновлення @@ -742,31 +815,43 @@ Title Music - Title Music + Титульна музика Disable Trophy Pop-ups - Disable Trophy Pop-ups + Вимкнути спливаючі вікна трофеїв + + + Background Image + Фонове зображення + + + Show Background Image + Показувати фонове зображення + + + Opacity + Непрозорість Play title music - Програвати заголовну музику + Програвати титульну музику Update Compatibility Database On Startup - Update Compatibility Database On Startup + Оновлення даних ігрової сумісності під час запуску Game Compatibility - Game Compatibility + Сумісність з іграми Display Compatibility Data - Display Compatibility Data + Відображати данні ігрової сумістністі Update Compatibility Database - Update Compatibility Database + Оновити данні ігрової сумістності Volume @@ -774,7 +859,7 @@ Audio Backend - Audio Backend + Аудіосистема Save @@ -826,11 +911,11 @@ userName - Ім'я користувача:\nВстановіть ім'я користувача акаунта PS4. Це може відображатися в деяких іграх. + Ім'я користувача:\nВстановіть ім'я користувача акаунта PS4. Воно може відображатися в деяких іграх. TrophyKey - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + Ключ трофеїв:\nКлюч для розшифровки трофеїв. Може бути отриманий зі зламаної консолі.\nПовинен містити лише шістнадцяткові символи. logTypeGroupBox @@ -842,19 +927,23 @@ updaterGroupBox - Оновлення:\nRelease: Офіційні версії, які випускаються щомісяця і можуть бути дуже старими, але вони більш надійні та перевірені.\nNightly: Версії для розробників, які мають усі найновіші функції та виправлення, але можуть містити помилки та є менш стабільними. + Оновлення:\nРелізний: Офіційні версії, які випускаються щомісяця і можуть бути дуже старими, але вони більш надійні та перевірені.\nТестовий: Версії для розробників, які мають усі найновіші функції та виправлення, але можуть містити помилки та є менш стабільними. + + + GUIBackgroundImageGroupBox + Фонове зображення:\nКерує непрозорістю фонового зображення гри. GUIMusicGroupBox - Грати заголовну музику:\nВмикає відтворення спеціальної музики під час вибору гри в списку, якщо вона це підтримує. + Грати титульну музику:\nВмикає відтворення спеціальної музики під час вибору гри в списку, якщо вона це підтримує. disableTrophycheckBox - Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + Вимкнути спливаючі вікна трофеїв:\nВимикає сповіщення про ігрові трофеї. Прогрес трофея все ще можна відстежувати за допомогою "Перегляд трофеїв" (клацніть правою кнопкою миші на грі у головному вікні). hideCursorGroupBox - Приховувати курсор:\nВиберіть, коли курсор зникне:\nНіколи: Ви завжди будете бачити мишу.\nПри бездіяльності: Встановіть час, через який курсор зникне в разі бездіяльності.\nЗавжди: Ви ніколи не будете бачити мишу. + Приховувати курсор:\nВиберіть, коли курсор зникатиме:\nНіколи: Курсор миші завжди буде видимий.\nПри бездіяльності: Встановіть час, через який курсор зникне в разі бездіяльності.\nЗавжди: Курсор миші завжди буде прихований. idleTimeoutGroupBox @@ -862,19 +951,19 @@ backButtonBehaviorGroupBox - Поведінка кнопки «Назад»:\nНалаштовує кнопку «Назад» контролера на емуляцію натискання на зазначену область на сенсорній панелі контролера PS4. + Перепризначення кнопки «Назад»:\nНалаштовує кнопку «Назад» контролера на емуляцію натискання на зазначену область на сенсорній панелі контролера PS4. enableCompatibilityCheckBox - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + Відображати данні ігрової сумістністі:\nВідображає інформацію про сумісність ігор у вигляді таблиці. Увімкніть "Оновлення даних ігрової сумісності під час запуску" для отримання актуальної інформації. checkCompatibilityOnStartupCheckBox - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + Оновлення даних ігрової сумісності під час запуску:\nАвтоматично оновлює базу даних ігрової сумісності під час запуску shadPS4. updateCompatibilityButton - Update Compatibility Database:\nImmediately update the compatibility database. + Оновити данні ігрової сумістності:\nНегайно оновить базу даних ігрової сумісності. Never @@ -890,23 +979,23 @@ Touchpad Left - Тачпад ліворуч + Ліва сторона тачпаду Touchpad Right - Тачпад праворуч + Права сторона тачпаду Touchpad Center - Тачпад по центру + Середина тачпаду None - Ні + Без змін graphicsAdapterGroupBox - Графічний пристрій:\nУ системах із кількома GPU виберіть з випадаючого списку GPU, який буде використовувати емулятор,\nабо виберіть "Auto Select", щоб визначити його автоматично. + Графічний пристрій:\nУ системах із кількома GPU виберіть з випадаючого списку GPU, який буде використовувати емулятор,\nабо виберіть "Автовибір", щоб визначити його автоматично. resolutionLayout @@ -926,7 +1015,7 @@ gameFoldersBox - Ігрові папки:\nСписок папок для перевірки встановлених ігор. + Ігрові папки:\nСписок папок, що скануватимуться для виявлення ігор. addFolderButton @@ -934,7 +1023,7 @@ removeFolderButton - Видалити:\nВидалити папку зі списку. + Вилучити:\nВилучити папку зі списку. debugDump @@ -954,30 +1043,38 @@ 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Створює .yaml файл з інформацією про стан Vulkan на момент збою.\nКорисно для налагодження помилок 'Device lost'. Якщо у вас увімкнено цей параметр, вам слід увімкнути маркери налагодження Хоста ТА Гостя.\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 (тип 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. + Хостові маркери налагодження:\nДодає інформацію емулятора, наприклад маркери для конкретних команд AMDGPU у Vulkan, також присвоює ресурсам налагоджувані назви.\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. + Гостьові маркери налагодження:\nВставляє налагоджувані маркери, які сама гра додала до командного буфера.\nЯкщо ця опція увімкнена, рекомендується також активувати діагностику збоїв.\nКорисно для програм на кшталт RenderDoc. + + + saveDataBox + Шлях до файлів збережень:\nПапка, де будуть зберігатися ігрові збереження. + + + browseButton + Вибрати:\nВиберіть папку для ігрових збережень. CheatsPatches Cheats / Patches for - Cheats / Patches for + Чити та Патчі для defaultTextEdit_MSG @@ -1149,7 +1246,7 @@ The game is in version: %1 - Гра у версії: %1 + Версія гри: %1 The downloaded patch only works on version: %1 @@ -1189,7 +1286,7 @@ Name: - Ім'я: + Назва: Can't apply cheats before the game is started @@ -1212,7 +1309,7 @@ Compatibility - Compatibility + Сумісність Region @@ -1240,43 +1337,43 @@ Never Played - Never Played + Ніколи не запускалась h - h + год m - m + хв s - s + сек Compatibility is untested - Compatibility is untested + Сумісність не перевірена Game does not initialize properly / crashes the emulator - Game does not initialize properly / crashes the emulator + Гра не ініціалізується належним чином або спричиняє збій емулятора. Game boots, but only displays a blank screen - Game boots, but only displays a blank screen + Гра запускається, але відображає лише чорний екран. Game displays an image but does not go past the menu - Game displays an image but does not go past the menu + Гра відображає зображення, але не проходить далі меню. Game has game-breaking glitches or unplayable performance - Game has game-breaking glitches or unplayable performance + У грі є критичні баги або погана продуктивність Game can be completed with playable performance and no major glitches - Game can be completed with playable performance and no major glitches + Гру можна пройти з хорошою продуктивністю та без серйозних глюків. @@ -1303,7 +1400,7 @@ Invalid release data. - Неприпустимі дані релізу. + Некоректні дані про випуск. No download URL found for the specified asset. @@ -1311,7 +1408,7 @@ Your version is already up to date! - Вашу версію вже оновлено! + У вас актуальна версія! Update Available @@ -1386,23 +1483,74 @@ GameListUtils B - B + Б KB - KB + КБ MB - MB + GB - GB + ГБ TB - TB + ТБ - \ No newline at end of file + + 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 f5d64239cbc5afa89addc0caa281a50558661865 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:26:14 +0100 Subject: [PATCH 25/28] Add outer deadzone config (#2348) * Add outer deadzone * Documentation * Add max outer deadzone to the controller remapping GUI * Fix init values * fix GUI saving syntax --- src/common/config.cpp | 4 +- src/input/input_handler.cpp | 73 ++++++++++++++++++++++----------- src/qt_gui/control_settings.cpp | 4 +- src/qt_gui/kbm_help_dialog.h | 7 ++-- 4 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index d9dfb861f..86e28285d 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -1008,8 +1008,8 @@ axis_right_x = axis_right_x axis_right_y = axis_right_y # Range of deadzones: 1 (almost none) to 127 (max) -analog_deadzone = leftjoystick, 2 -analog_deadzone = rightjoystick, 2 +analog_deadzone = leftjoystick, 2, 127 +analog_deadzone = rightjoystick, 2, 127 )"; } std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id) { diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 5394e4818..9c51ec8be 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -55,7 +55,8 @@ Don't be an idiot and test only the changed part expecting everything else to no */ bool leftjoystick_halfmode = false, rightjoystick_halfmode = false; -int leftjoystick_deadzone, rightjoystick_deadzone, lefttrigger_deadzone, righttrigger_deadzone; +std::pair leftjoystick_deadzone, rightjoystick_deadzone, lefttrigger_deadzone, + righttrigger_deadzone; std::list> pressed_keys; std::list toggled_keys; @@ -208,10 +209,10 @@ void ParseInputConfig(const std::string game_id = "") { float mouse_speed = 1; float mouse_speed_offset = 0.125; - leftjoystick_deadzone = 1; - rightjoystick_deadzone = 1; - lefttrigger_deadzone = 1; - righttrigger_deadzone = 1; + leftjoystick_deadzone = {1, 127}; + rightjoystick_deadzone = {1, 127}; + lefttrigger_deadzone = {1, 127}; + righttrigger_deadzone = {1, 127}; int lineCount = 0; @@ -298,26 +299,45 @@ void ParseInputConfig(const std::string game_id = "") { continue; } else if (output_string == "analog_deadzone") { std::stringstream ss(input_string); - std::string device; - int deadzone; - std::getline(ss, device, ','); - ss >> deadzone; - if (ss.fail()) { - LOG_WARNING(Input, "Failed to parse deadzone config from line: {}", line); + std::string device, inner_deadzone_str, outer_deadzone_str; + + if (!std::getline(ss, device, ',') || !std::getline(ss, inner_deadzone_str, ',') || + !std::getline(ss, outer_deadzone_str)) { + LOG_WARNING(Input, "Malformed deadzone config at line {}: \"{}\"", lineCount, line); continue; - } else { - LOG_DEBUG(Input, "Parsed deadzone: {} {}", device, deadzone); } - if (device == "leftjoystick") { - leftjoystick_deadzone = deadzone; - } else if (device == "rightjoystick") { - rightjoystick_deadzone = deadzone; - } else if (device == "l2") { - lefttrigger_deadzone = deadzone; - } else if (device == "r2") { - righttrigger_deadzone = deadzone; + + auto parseInt = [](const std::string& s) -> std::optional { + try { + return std::stoi(s); + } catch (...) { + return std::nullopt; + } + }; + + auto inner_deadzone = parseInt(inner_deadzone_str); + auto outer_deadzone = parseInt(outer_deadzone_str); + + if (!inner_deadzone || !outer_deadzone) { + LOG_WARNING(Input, "Invalid deadzone values at line {}: \"{}\"", lineCount, line); + continue; + } + + std::pair deadzone = {*inner_deadzone, *outer_deadzone}; + + static std::unordered_map&> deadzone_map = { + {"leftjoystick", leftjoystick_deadzone}, + {"rightjoystick", rightjoystick_deadzone}, + {"l2", lefttrigger_deadzone}, + {"r2", righttrigger_deadzone}, + }; + + if (auto it = deadzone_map.find(device); it != deadzone_map.end()) { + it->second = deadzone; + LOG_DEBUG(Input, "Parsed deadzone: {} {} {}", device, inner_deadzone_str, + outer_deadzone_str); } else { - LOG_WARNING(Input, "Invalid axis name at line: {}, data: \"{}\", skipping line.", + LOG_WARNING(Input, "Invalid axis name at line {}: \"{}\", skipping line.", lineCount, line); } continue; @@ -493,9 +513,14 @@ void ControllerOutput::FinalizeUpdate() { // avoid double-updating axes, but don't skip directional button bindings float multiplier = 1.0; int deadzone = 0; - auto ApplyDeadzone = [](s16* value, int deadzone) { - if (std::abs(*value) <= deadzone) { + auto ApplyDeadzone = [](s16* value, std::pair deadzone) { + if (std::abs(*value) <= deadzone.first || deadzone.first == deadzone.second) { *value = 0; + } else { + *value = (*value >= 0 ? 1 : -1) * + std::clamp((int)((128.0 * (std::abs(*value) - deadzone.first)) / + (float)(deadzone.second - deadzone.first)), + 0, 128); } }; Axis c_axis = GetAxisFromSDLAxis(axis); diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index 0374d2049..a07d36292 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -220,10 +220,10 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { 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); + lines.push_back("analog_deadzone = leftjoystick, " + deadzonevalue + ", 127"); deadzonevalue = std::to_string(ui->RightDeadzoneSlider->value()); - lines.push_back("analog_deadzone = rightjoystick, " + deadzonevalue); + lines.push_back("analog_deadzone = rightjoystick, " + deadzonevalue + ", 127"); std::vector save; bool CurrentLineEmpty = false, LastLineEmpty = false; diff --git a/src/qt_gui/kbm_help_dialog.h b/src/qt_gui/kbm_help_dialog.h index c482d2b5c..3e39d4397 100644 --- a/src/qt_gui/kbm_help_dialog.h +++ b/src/qt_gui/kbm_help_dialog.h @@ -167,9 +167,10 @@ You can find these here, with detailed comments, examples and suggestions for mo You can make an input toggleable with this, for example: Let's say we want to be able to toggle l1 with t. You can then bind l1 to a key you won't use, like kpenter, then bind t to toggle that, so you will end up with this: l1 = kpenter; key_toggle = t, kpenter; -'analog_deadzone' = , ; - value goes from 1 to 127 (no deadzone to max deadzone) - devices: leftjoystick, rightjoystick, l2, r2 +'analog_deadzone' = , , ; + Values go from 1 to 127 (no deadzone to max deadzone), first is the inner, second is the outer deadzone + If you only want inner or outer deadzone, set the other to 1 or 127, respectively + Devices: leftjoystick, rightjoystick, l2, r2 )"; } }; \ No newline at end of file From e972a8805d97820fccddd840e47ae360d852a09c Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:54:13 -0600 Subject: [PATCH 26/28] Bump size of buffer_views (#2357) Uncharted 4 (and perhaps some other games) fill this up, causing a `Unhandled exception: boost::container::bad_alloc thrown` exception --- src/video_core/renderer_vulkan/vk_rasterizer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index ed6cc7e71..6e1a1d82e 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -111,7 +111,7 @@ private: cb_descs; std::optional> db_desc; boost::container::static_vector image_infos; - boost::container::static_vector buffer_views; + boost::container::static_vector buffer_views; boost::container::static_vector buffer_infos; boost::container::static_vector bound_images; From e1f0cd65af065583fab113acfd824a642eed91a3 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Thu, 6 Feb 2025 01:54:27 +0800 Subject: [PATCH 27/28] Set focus, refresh GUI after closing text editor, prevent no comma crash (#2354) Co-authored-by: rainmakerv2 <30595646+jpau02@users.noreply.github.com> --- src/qt_gui/control_settings.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index a07d36292..644576feb 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -16,6 +16,7 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, Q AddBoxItems(); SetUIValuestoMappings(); + ui->KBMButton->setFocus(); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) { if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { @@ -31,6 +32,7 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, Q connect(ui->KBMButton, &QPushButton::clicked, this, [this] { auto KBMWindow = new EditorDialog(this); KBMWindow->exec(); + SetUIValuestoMappings(); }); connect(ui->ProfileComboBox, &QComboBox::currentTextChanged, this, [this] { GetGameTitle(); @@ -416,14 +418,24 @@ void ControlSettings::SetUIValuestoMappings() { 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)); + if (comma_pos != std::string::npos) { + int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); + ui->LeftDeadzoneSlider->setValue(deadzonevalue); + ui->LeftDeadzoneValue->setText(QString::number(deadzonevalue)); + } else { + ui->LeftDeadzoneSlider->setValue(2); + ui->LeftDeadzoneValue->setText("2"); + } } 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 (comma_pos != std::string::npos) { + int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); + ui->RightDeadzoneSlider->setValue(deadzonevalue); + ui->RightDeadzoneValue->setText(QString::number(deadzonevalue)); + } else { + ui->RightDeadzoneSlider->setValue(2); + ui->RightDeadzoneValue->setText("2"); + } } } } From 0c3260b1b4b7f6069e7ca6d884712cbd5d1f35d8 Mon Sep 17 00:00:00 2001 From: Martin <67326368+Martini-141@users.noreply.github.com> Date: Wed, 5 Feb 2025 18:54:39 +0100 Subject: [PATCH 28/28] Update nb.ts (#2353) * add copyinfo and backgroundimage nb tr * better wording and fix spelling mistakes --- src/qt_gui/translations/nb.ts | 54 +++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index b2a355c95..de6341a48 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -15,7 +15,7 @@ shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 er en eksperimentell åpen kildekode-etterligner for PlayStation 4. + shadPS4 er en eksperimentell åpen kildekode-emulator for PlayStation 4. This software should not be used to play games you have not legally obtained. @@ -124,6 +124,14 @@ Copy Serial Kopier serienummer + + Copy Version + Kopier versjon + + + Copy Size + Kopier størrelse + Copy All Kopier alt @@ -530,11 +538,11 @@ Emulator Language - Etterlignerspråk + Emulatorspråk Emulator - Etterligner + Emulator Enable Fullscreen @@ -764,6 +772,18 @@ Disable Trophy Pop-ups Deaktiver trofé hurtigmeny + + Background Image + Bakgrunnsbilde + + + Show Background Image + Vis bakgrunnsbilde + + + Opacity + Synlighet + Play title music Spill tittelmusikk @@ -818,7 +838,7 @@ emulatorLanguageGroupBox - Etterlignerspråket:\nAngir språket for etterlignerens brukergrensesnitt. + Emulatorspråket:\nAngir språket for emulatorens brukergrensesnitt. fullscreenCheckBox @@ -834,11 +854,11 @@ ps4proCheckBox - Er PS4 Pro:\nFår etterligneren til å fungere som en PS4 PRO, noe som kan aktivere spesielle funksjoner i spill som støtter dette. + Er PS4 Pro:\nFår emulatoren til å fungere som en PS4 PRO, noe som kan aktivere spesielle funksjoner i spill som støtter dette. discordRPCCheckbox - Aktiver Discord Rich Presence:\nViser etterlignerikonet og relevant informasjon på Discord-profilen din. + Aktiver Discord Rich Presence:\nViser emulatorikonet og relevant informasjon på Discord-profilen din. userName @@ -850,7 +870,7 @@ logTypeGroupBox - Logg type:\nAngir om loggvinduets utdata skal synkroniseres for ytelse. Kan ha negative effekter for etterligneren. + Logg type:\nAngir om loggvinduets utdata skal synkroniseres for ytelse. Kan ha negative effekter for emulatoren. logFilter @@ -860,6 +880,10 @@ updaterGroupBox Oppdatering:\nRelease: Offisielle versjoner utgitt hver måned som kan være veldig utdaterte, men er mer pålitelige og testet.\nNightly: Utviklingsversjoner som har alle de nyeste funksjonene og feilrettingene, men som kan inneholde feil og er mindre stabile. + + GUIBackgroundImageGroupBox + Bakgrunnsbilde:\nEndrer synligheten til spillets bakgrunnsbilde. + GUIMusicGroupBox Spille tittelmusikk:\nHvis et spill støtter det, så aktiveres det spesiell musikk når du velger spillet i menyen. @@ -922,15 +946,15 @@ graphicsAdapterGroupBox - Grafikkenhet:\nI systemer med flere GPU-er, velg GPU-en etterligneren skal bruke fra rullegardinlisten,\neller velg "Auto Select" for å velge automatisk. + Grafikkenhet:\nI systemer med flere GPU-er, velg GPU-en emulatoren skal bruke fra rullegardinlisten,\neller velg "Auto Select" for å velge automatisk. resolutionLayout - Bredde/Høyde:\nAngir størrelsen på etterlignerkvinduet ved oppstart, som kan endres under spillingen.\nDette er forskjellig fra oppløsningen i spillet. + Bredde/Høyde:\nAngir størrelsen på emulatorvinduet ved oppstart, som kan endres under spillingen.\nDette er forskjellig fra oppløsningen i spillet. heightDivider - Vblank skillelinje:\nBildehastigheten som etterligneren oppdaterer ved, multipliseres med dette tallet. Endring av dette kan ha negative effekter, som å øke hastigheten av spillet, eller ødelegge kritisk spillfunksjonalitet som ikke forventer at dette endres! + Vblank skillelinje:\nBildehastigheten som emulatoren oppdaterer ved, multipliseres med dette tallet. Endring av dette kan ha negative effekter, som å øke hastigheten av spillet, eller ødelegge kritisk spillfunksjonalitet som ikke forventer at dette endres! dumpShadersCheckBox @@ -958,15 +982,15 @@ vkValidationCheckBox - Aktiver Vulkan Validation Layers:\nAktiverer et system som validerer tilstanden til Vulkan-gjengiveren og logger informasjon om dens indre tilstand. Dette vil redusere ytelsen og sannsynligvis endre etterlignerens atferd. + Aktiver Vulkan Validation Layers:\nAktiverer et system som validerer tilstanden til Vulkan-gjengiveren og logger informasjon om dens indre tilstand. Dette vil redusere ytelsen og sannsynligvis endre emulatorens atferd. vkSyncValidationCheckBox - Aktiver Vulkan synkronisering validering:\nAktiverer et system som validerer frekvens tiden av Vulkan-gjengivelsensoppgaver. Dette vil redusere ytelsen og sannsynligvis endre etterlignerens atferd. + Aktiver Vulkan synkronisering validering:\nAktiverer et system som validerer frekvens tiden av Vulkan-gjengivelsensoppgaver. Dette vil redusere ytelsen og sannsynligvis endre emulatorens atferd. rdocCheckBox - Aktiver RenderDoc feilsøking:\nHvis aktivert, vil etterligneren gi kompatibilitet med Renderdoc for å tillate opptak og analyse av det nåværende gjengitte bildet. + Aktiver RenderDoc feilsøking:\nHvis aktivert, vil emulatoren gi kompatibilitet med RenderDoc for å tillate opptak og analyse av det nåværende gjengitte bildet. collectShaderCheckBox @@ -982,7 +1006,7 @@ hostMarkersCheckBox - Vertsfeilsøkingsmarkører:\nSetter inn etterligner-side informasjon som markører for spesifikke AMDGPU-kommandoer rundt Vulkan-kommandoer, i tillegg til å gi ressurser feilrettingsnavn.\nHvis du har dette aktivert, burde du aktivere krasjdiagnostikk.\nNyttig for programmer som RenderDoc. + Vertsfeilsøkingsmarkører:\nSetter inn emulator-side informasjon som markører for spesifikke AMDGPU-kommandoer rundt Vulkan-kommandoer, i tillegg til å gi ressurser feilrettingsnavn.\nHvis du har dette aktivert, burde du aktivere krasjdiagnostikk.\nNyttig for programmer som RenderDoc. guestMarkersCheckBox @@ -1280,7 +1304,7 @@ Game does not initialize properly / crashes the emulator - Spillet initialiseres ikke riktig / krasjer etterligneren + Spillet initialiseres ikke riktig / krasjer emulatoren Game boots, but only displays a blank screen