From db868ea40076a981ac010b1f85e2503cbac526fb Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Fri, 28 Feb 2025 18:11:41 -0300 Subject: [PATCH 001/194] Fix: sce_sys/snd0.at9 remains after game deletion (#2565) --- src/qt_gui/background_music_player.cpp | 1 + src/qt_gui/gui_context_menus.h | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/qt_gui/background_music_player.cpp b/src/qt_gui/background_music_player.cpp index a63f1d1be..e2b7177b3 100644 --- a/src/qt_gui/background_music_player.cpp +++ b/src/qt_gui/background_music_player.cpp @@ -40,4 +40,5 @@ void BackgroundMusicPlayer::playMusic(const QString& snd0path, bool loops) { void BackgroundMusicPlayer::stopMusic() { m_mediaPlayer->stop(); + m_mediaPlayer->setSource(QUrl("")); } diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 8a6e07847..df86686f3 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -10,6 +10,7 @@ #include #include +#include #include "cheats_patches.h" #include "common/config.h" #include "common/version.h" @@ -442,9 +443,12 @@ public: Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / m_games[itemID].serial / "TrophyFiles"); - QString message_type = tr("Game"); + QString message_type; - if (selected == deleteUpdate) { + if (selected == deleteGame) { + BackgroundMusicPlayer::getInstance().stopMusic(); + message_type = tr("Game"); + } else if (selected == deleteUpdate) { if (!std::filesystem::exists(Common::FS::PathFromQString(game_update_path))) { QMessageBox::critical(nullptr, tr("Error"), QString(tr("This game has no update to delete!"))); From 636a90386ba2ceb03f20d3ade01ab7b524048dcd Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 1 Mar 2025 20:02:29 +0200 Subject: [PATCH 002/194] fix for ime (#2475) * fix for ime * typo --- src/core/libraries/ime/ime.cpp | 11 +++++++++-- src/core/libraries/ime/ime.h | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index dfd659db8..1c61bc276 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -49,9 +49,9 @@ public: // Are we supposed to call the event handler on init with // ADD_OSK? - if (!ime_mode && False(m_param.key.option & OrbisImeKeyboardOption::AddOsk)) { + /* if (!ime_mode && False(m_param.key.option & OrbisImeKeyboardOption::AddOsk)) { Execute(nullptr, &openEvent, true); - } + }*/ if (ime_mode) { g_ime_state = ImeState(&m_param.ime); @@ -274,6 +274,13 @@ s32 PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* par if (!param) { return ORBIS_IME_ERROR_INVALID_ADDRESS; } + if (!param->arg) { + return ORBIS_IME_ERROR_INVALID_ARG; + } + if (!param->handler) { + return ORBIS_IME_ERROR_INVALID_HANDLER; + } + if (g_keyboard_handler) { return ORBIS_IME_ERROR_BUSY; } diff --git a/src/core/libraries/ime/ime.h b/src/core/libraries/ime/ime.h index 448ee6896..fcf381048 100644 --- a/src/core/libraries/ime/ime.h +++ b/src/core/libraries/ime/ime.h @@ -20,7 +20,7 @@ enum class OrbisImeKeyboardOption : u32 { Repeat = 1, RepeatEachKey = 2, AddOsk = 4, - EffectiveWithTime = 8, + EffectiveWithIme = 8, DisableResume = 16, DisableCapslockWithoutShift = 32, }; From 0bdd21b4e49c25955b16a3651255381b4a60f538 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sat, 1 Mar 2025 17:48:57 -0300 Subject: [PATCH 003/194] Fix list after deleting a game (#2577) --- src/qt_gui/gui_context_menus.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index df86686f3..24b421b9d 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -13,6 +13,7 @@ #include #include "cheats_patches.h" #include "common/config.h" +#include "common/path_util.h" #include "common/version.h" #include "compatibility_info.h" #include "game_info.h" @@ -26,12 +27,11 @@ #include #include #endif -#include "common/path_util.h" class GuiContextMenus : public QObject { Q_OBJECT public: - void RequestGameMenu(const QPoint& pos, QVector m_games, + void RequestGameMenu(const QPoint& pos, QVector& m_games, std::shared_ptr m_compat_info, QTableWidget* widget, bool isList) { QPoint global_pos = widget->viewport()->mapToGlobal(pos); From 9061028ce588037fa6f467cd2c0740d10ed725ed Mon Sep 17 00:00:00 2001 From: Randomuser8219 <168323856+Randomuser8219@users.noreply.github.com> Date: Sun, 2 Mar 2025 01:55:45 -0800 Subject: [PATCH 004/194] Reduce some service ID log spam (#2578) * Change systemserivce param ID calling to debug only Some games, including Namco Museum Archives spam this. * Update userservice.cpp Also reduces log spam in Dysmantle. --- src/core/libraries/system/systemservice.cpp | 2 +- src/core/libraries/system/userservice.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/libraries/system/systemservice.cpp b/src/core/libraries/system/systemservice.cpp index a67b9a2fc..9dc9dd781 100644 --- a/src/core/libraries/system/systemservice.cpp +++ b/src/core/libraries/system/systemservice.cpp @@ -1893,7 +1893,7 @@ int PS4_SYSV_ABI sceSystemServiceNavigateToGoHome() { s32 PS4_SYSV_ABI sceSystemServiceParamGetInt(OrbisSystemServiceParamId param_id, int* value) { // TODO this probably should be stored in config for UI configuration - LOG_INFO(Lib_SystemService, "called param_id {}", u32(param_id)); + LOG_DEBUG(Lib_SystemService, "called param_id {}", u32(param_id)); if (value == nullptr) { LOG_ERROR(Lib_SystemService, "value is null"); return ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER; diff --git a/src/core/libraries/system/userservice.cpp b/src/core/libraries/system/userservice.cpp index e1f9f75e8..b4bf189ea 100644 --- a/src/core/libraries/system/userservice.cpp +++ b/src/core/libraries/system/userservice.cpp @@ -1043,7 +1043,7 @@ int PS4_SYSV_ABI sceUserServiceGetTraditionalChineseInputType() { s32 PS4_SYSV_ABI sceUserServiceGetUserColor(int user_id, OrbisUserServiceUserColor* color) { // TODO fix me better - LOG_INFO(Lib_UserService, "called user_id = {}", user_id); + LOG_DEBUG(Lib_UserService, "called user_id = {}", user_id); if (color == nullptr) { LOG_ERROR(Lib_UserService, "color is null"); return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; @@ -1068,7 +1068,7 @@ int PS4_SYSV_ABI sceUserServiceGetUserGroupNum() { } s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::size_t size) { - LOG_INFO(Lib_UserService, "called user_id = {} ,size = {} ", user_id, size); + LOG_DEBUG(Lib_UserService, "called user_id = {} ,size = {} ", user_id, size); if (user_name == nullptr) { LOG_ERROR(Lib_UserService, "user_name is null"); return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; From 4c7c703ea27cea318b5dfea47b74f76a19140cd1 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 2 Mar 2025 21:16:01 +0200 Subject: [PATCH 005/194] New Crowdin updates (#2562) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Russian) * New translations en_us.ts (Albanian) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Italian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Russian) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Italian) * New translations en_us.ts (Italian) * New translations en_us.ts (Albanian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Swedish) --- src/qt_gui/translations/ar_SA.ts | 60 +++++++++++++++++++- src/qt_gui/translations/da_DK.ts | 60 +++++++++++++++++++- src/qt_gui/translations/de_DE.ts | 60 +++++++++++++++++++- src/qt_gui/translations/el_GR.ts | 60 +++++++++++++++++++- src/qt_gui/translations/es_ES.ts | 60 +++++++++++++++++++- src/qt_gui/translations/fa_IR.ts | 60 +++++++++++++++++++- src/qt_gui/translations/fi_FI.ts | 60 +++++++++++++++++++- src/qt_gui/translations/fr_FR.ts | 60 +++++++++++++++++++- src/qt_gui/translations/hu_HU.ts | 60 +++++++++++++++++++- src/qt_gui/translations/id_ID.ts | 60 +++++++++++++++++++- src/qt_gui/translations/it_IT.ts | 60 +++++++++++++++++++- src/qt_gui/translations/ja_JP.ts | 60 +++++++++++++++++++- src/qt_gui/translations/ko_KR.ts | 60 +++++++++++++++++++- src/qt_gui/translations/lt_LT.ts | 60 +++++++++++++++++++- src/qt_gui/translations/nb_NO.ts | 60 +++++++++++++++++++- src/qt_gui/translations/nl_NL.ts | 60 +++++++++++++++++++- src/qt_gui/translations/pl_PL.ts | 62 ++++++++++++++++++++- src/qt_gui/translations/pt_BR.ts | 96 +++++++++++++++++++++++++------- src/qt_gui/translations/pt_PT.ts | 60 +++++++++++++++++++- src/qt_gui/translations/ro_RO.ts | 60 +++++++++++++++++++- src/qt_gui/translations/ru_RU.ts | 60 +++++++++++++++++++- src/qt_gui/translations/sq_AL.ts | 60 +++++++++++++++++++- src/qt_gui/translations/sv_SE.ts | 60 +++++++++++++++++++- src/qt_gui/translations/tr_TR.ts | 68 ++++++++++++++++++++-- src/qt_gui/translations/uk_UA.ts | 60 +++++++++++++++++++- src/qt_gui/translations/vi_VN.ts | 60 +++++++++++++++++++- src/qt_gui/translations/zh_CN.ts | 60 +++++++++++++++++++- src/qt_gui/translations/zh_TW.ts | 60 +++++++++++++++++++- 28 files changed, 1647 insertions(+), 79 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index f1b16d4b0..493a33f82 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger المسجل @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never أبداً @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer عارض الجوائز + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index e6af07e74..6e8c8a6ff 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Aldrig @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophy Viewer + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 5d57b6361..64b28c179 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -775,6 +775,10 @@ Delete DLC Lösche DLC + + Delete Trophy + Delete Trophy + Compatibility... Kompatibilität... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophäe + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Titelmusik - Disable Trophy Pop-ups - Deaktiviere Trophäen Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Aktualisiere Kompatibilitätsdatenbank:\nAktualisiere sofort die Kompatibilitätsdatenbank. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Niemals @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophäenansicht + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index 3d2763409..3d0b89bb8 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Ποτέ @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophy Viewer + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 148613422..a9db8860e 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -775,6 +775,10 @@ Delete DLC Eliminar DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibilidad... @@ -851,6 +855,10 @@ This game has no update folder to open! ¡Este juego no tiene carpeta de actualizaciones! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Error al convertir el icono. @@ -859,10 +867,18 @@ This game has no save data to delete! ¡Este juego no tiene datos guardados! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Datos guardados + + Trophy + Trophy + SFO Viewer for Visualizador de SFO para @@ -1311,6 +1327,10 @@ Trophy Trofeo + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Registro @@ -1476,8 +1496,8 @@ Música de título - Disable Trophy Pop-ups - Deshabilitar mensajes de trofeos + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Nunca @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Vista de trofeos + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index a173dc9d4..da9424a3a 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -775,6 +775,10 @@ Delete DLC حذف محتوای اضافی (DLC) + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - غیرفعال کردن نمایش جوایز + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. به‌روزرسانی پایگاه داده سازگاری:\nپایگاه داده سازگاری را بلافاصله به‌روزرسانی می‌کند. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never هرگز @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer مشاهده جوایز + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index bcbbd07f6..f20461015 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -775,6 +775,10 @@ Delete DLC Poista Lisäsisältö + + Delete Trophy + Delete Trophy + Compatibility... Yhteensopivuus... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Lokinkerääjä @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Poista Trophy Pop-upit Käytöstä + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Päivitä Yhteensopivuustietokanta:\nPäivitää yhteensopivuustietokannan heti. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Ei koskaan @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophy Selain + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index a812631f5..41a588aee 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -775,6 +775,10 @@ Delete DLC Supprimer DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibilité... @@ -851,6 +855,10 @@ This game has no update folder to open! Ce jeu n'a pas de dossier de mise à jour à ouvrir! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Échec de la conversion de l'icône. @@ -859,10 +867,18 @@ This game has no save data to delete! Ce jeu n'a pas de mise à jour à supprimer! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Enregistrer les Données + + Trophy + Trophy + SFO Viewer for Visionneuse SFO pour @@ -1311,6 +1327,10 @@ Trophy Trophée + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Journalisation @@ -1476,8 +1496,8 @@ Musique du titre - Disable Trophy Pop-ups - Désactiver les notifications de trophées + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Mettre à jour la compatibilité au démarrage:\nMet à jour immédiatement la base de données de compatibilité. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Jamais @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Visionneuse de trophées + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 93e266f2c..35ad71d59 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -775,6 +775,10 @@ Delete DLC DLC-k törlése + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Naplózó @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Soha @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trófeák Megtekintése + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index 335450ca2..25835f925 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Tidak Pernah @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophy Viewer + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index fd1f4d521..a3f06591d 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -775,6 +775,10 @@ Delete DLC Elimina DLC + + Delete Trophy + Elimina Trofei + Compatibility... Compatibilità... @@ -851,6 +855,10 @@ This game has no update folder to open! Questo gioco non ha nessuna cartella di aggiornamento da aprire! + + No log file found for this game! + Nessun file di log trovato per questo gioco! + Failed to convert icon. Impossibile convertire l'icona. @@ -859,10 +867,18 @@ This game has no save data to delete! Questo gioco non ha alcun salvataggio dati da eliminare! + + This game has no saved trophies to delete! + Questo gioco non ha nessun trofeo salvato da eliminare! + Save Data Dati Salvataggio + + Trophy + Trofei + SFO Viewer for Visualizzatore SFO per @@ -1311,6 +1327,10 @@ Trophy Trofei + + Open the custom trophy images/sounds folder + Apri la cartella personalizzata delle immagini/suoni dei trofei + Logger Registro @@ -1476,8 +1496,8 @@ Musica del Titolo - Disable Trophy Pop-ups - Disabilita Notifica Trofei + Disable Trophy Notification + Disabilita Notifiche Trofei Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Aggiorna Database Compatibilità:\nAggiorna immediatamente il database di compatibilità. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Apri la cartella personalizzata delle immagini/suoni dei trofei:\nPuoi aggiungere immagini e audio personalizzato ai trofei.\nAggiungi i file in custom_trophy con i seguenti nomi:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Mai @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. File di registro separati:\nScrive un file di registro separato per ogni gioco. + + Trophy Notification Position + Posizione Notifica Trofei + + + Left + Sinistra + + + Right + Destra + + + Notification Duration + Durata Notifica + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Visualizzatore Trofei + + Progress + Progresso + + + Show Earned Trophies + Mostra Trofei Guadagnati + + + Show Not Earned Trophies + Mostra Trofei Non Guadagnati + + + Show Hidden Trophies + Mostra Trofei Nascosti + diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 6e11dfe0c..f9f4d6370 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -775,6 +775,10 @@ Delete DLC DLCを削除 + + Delete Trophy + Delete Trophy + Compatibility... 互換性... @@ -851,6 +855,10 @@ This game has no update folder to open! このゲームにはアップデートフォルダがありません! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. アイコンの変換に失敗しました。 @@ -859,10 +867,18 @@ This game has no save data to delete! このゲームには削除するセーブデータがありません! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy トロフィー + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger ロガー @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - トロフィーのポップアップを無効化 + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. 互換性データベースを更新する:\n今すぐ互換性データベースを更新します。 + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never 無効 @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer トロフィービューアー + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index ad24f6f54..3e6c26dde 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Never @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophy Viewer + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index c1886a4fa..c9be4c048 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Niekada @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophy Viewer + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index daf44c3bb..d163396ee 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -775,6 +775,10 @@ Delete DLC Slett DLC + + Delete Trophy + Slett trofé + Compatibility... Kompatibilitet... @@ -851,6 +855,10 @@ This game has no update folder to open! Dette spillet har ingen oppdateringsmappe å åpne! + + No log file found for this game! + Fant ingen loggfil for dette spillet! + Failed to convert icon. Klarte ikke konvertere ikon. @@ -859,10 +867,18 @@ This game has no save data to delete! Dette spillet har ingen lagret data å slette! + + This game has no saved trophies to delete! + Dette spillet har ingen lagrede trofeer å slette! + Save Data Lagret data + + Trophy + Trofé + SFO Viewer for SFO-viser for @@ -1311,6 +1327,10 @@ Trophy Trofé + + Open the custom trophy images/sounds folder + Åpne mappa med tilpassede bilder og lyder for trofé + Logger Loggføring @@ -1476,8 +1496,8 @@ Tittelmusikk - Disable Trophy Pop-ups - Deaktiver trofé hurtigmeny + Disable Trophy Notification + Slå av trofévarsler Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Oppdater kompatibilitets-database:\nOppdater kompatibilitets-databasen nå. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Åpne mappa med tilpassede bilder og lyder for trofé:\nDu kan legge til tilpassede bilder til trofeene og en lyd.\nLegg filene til custom_trophy med følgende navn:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Aldri @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate loggfiler:\nOppretter en separat loggfil for hvert spill. + + Trophy Notification Position + Trofévarsel plassering + + + Left + Venstre + + + Right + Høyre + + + Notification Duration + Varslingsvarighet + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Troféviser + + Progress + Fremdrift + + + Show Earned Trophies + Vis opptjente trofeer + + + Show Not Earned Trophies + Vis ikke opptjente trofeer + + + Show Hidden Trophies + Vis skjulte trofeer + diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 762254a6e..a61ca1adf 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Nooit @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophy Viewer + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 407948fae..64134d055 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -775,6 +775,10 @@ Delete DLC Usuń dodatkową zawartość (DLC) + + Delete Trophy + Usuń trofeum + Compatibility... Kompatybilność... @@ -851,6 +855,10 @@ This game has no update folder to open! Ta gra nie ma folderu aktualizacji do otwarcia! + + No log file found for this game! + Nie znaleziono pliku dziennika dla tej gry! + Failed to convert icon. Nie udało się przekonwertować ikony. @@ -859,10 +867,18 @@ This game has no save data to delete! Ta gra nie ma zapisów do usunięcia! + + This game has no saved trophies to delete! + Ta gra nie ma zapisanych trofeuów do usunięcia! + Save Data Zapisane dane + + Trophy + Trofeum + SFO Viewer for Menedżer plików SFO dla @@ -1309,7 +1325,11 @@ Trophy - Trofeum + Trofea + + + Open the custom trophy images/sounds folder + Otwórz niestandardowy folder obrazów/dźwięków trofeów Logger @@ -1476,8 +1496,8 @@ Muzyka tytułowa - Disable Trophy Pop-ups - Wyłącz wyskakujące okienka trofeów + Disable Trophy Notification + Wyłącz powiadomienia o trofeach Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Zaktualizuj bazę danych zgodności:\nNatychmiast zaktualizuj bazę danych zgodności. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Otwórz niestandardowy folder obrazów/dźwięków:\nMożesz dodać własne obrazy do trofeów i dźwięku.\nDodaj pliki do custom_trophy o następujących nazwach:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Nigdy @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Oddzielne pliki dziennika:\nZapisuje oddzielny plik dziennika dla każdej gry. + + Trophy Notification Position + Pozycja powiadomień trofeów + + + Left + Z lewej + + + Right + Z prawej + + + Notification Duration + Czas trwania powiadomienia + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Menedżer trofeów + + Progress + Postęp + + + Show Earned Trophies + Pokaż zdobyte trofea + + + Show Not Earned Trophies + Pokaż niezdobyte trofea + + + Show Hidden Trophies + Pokaż ukryte trofea + diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 406109150..103f0b66e 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -22,11 +22,11 @@ CheatsPatches Cheats / Patches for - Trapaças / Patches para + Trapaças / Modificações para Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n - As Trapaças/Patches são experimentais.\nUse com cautela.\n\nBaixe as trapaças individualmente selecionando o repositório e clicando no botão de baixar.\nNa aba Patches, você pode baixar todos os patches de uma vez, escolha qual deseja usar e salve a opção.\n\nComo não desenvolvemos as Trapaças/Patches,\npor favor, reporte os problemas relacionados ao autor da trapaça.\n\nCriou uma nova trapaça? Visite:\n + As Trapaças/Modificações são experimentais.\nUse com cautela.\n\nBaixe as trapaças individualmente selecionando o repositório e clicando no botão de baixar.\nNa aba Modificações, você pode baixar todas as modificações de uma vez, escolha qual deseja usar e salve a opção.\n\nComo não desenvolvemos as Trapaças/Modificações,\npor favor, reporte os problemas relacionados ao autor da trapaça.\n\nCriou uma nova trapaça? Visite:\n No Image Available @@ -78,7 +78,7 @@ Download Patches - Baixar Patches + Baixar Modificações Save @@ -90,7 +90,7 @@ Patches - Patches + Modificações Error @@ -182,7 +182,7 @@ Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. - Patches Baixados com Sucesso! Todos os patches disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece em Trapaças. Se o patch não aparecer, pode ser que ele não exista para o serial e versão específicas do jogo. + Modificações Baixados com Sucesso! Todos as modificações disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece em trapaças. Se a modificação não aparecer, pode ser que ela não exista para o serial e versão específicas do jogo. Failed to parse JSON data from HTML. @@ -709,7 +709,7 @@ Cheats / Patches - Trapaças / Patches + Trapaças / Modificações SFO Viewer @@ -733,7 +733,7 @@ Open Log Folder - Abrir Pasta de Log + Abrir Pasta de Registros Copy info... @@ -775,6 +775,10 @@ Delete DLC Excluir DLC + + Delete Trophy + Excluir Troféu + Compatibility... Compatibilidade... @@ -851,6 +855,10 @@ This game has no update folder to open! Este jogo não possui pasta de atualização para abrir! + + No log file found for this game! + Nenhum arquivo de registro foi encontrado para este jogo! + Failed to convert icon. Falha ao converter o ícone. @@ -859,10 +867,18 @@ This game has no save data to delete! Este jogo não tem dados salvos para excluir! + + This game has no saved trophies to delete! + Este jogo não tem troféus salvos para excluir! + Save Data Dados Salvos + + Trophy + Troféus + SFO Viewer for Visualizador de SFO para @@ -915,7 +931,7 @@ Install application from a .pkg file - Instalar aplicação de um arquivo .pkg + Instalar aplicativo de um arquivo .pkg Recent Games @@ -935,7 +951,7 @@ Exit the application. - Sair da aplicação. + Sair do aplicativo. Show Game List @@ -979,7 +995,7 @@ Download Cheats/Patches - Baixar Trapaças/Patches + Baixar Trapaças/Modificações Dump Game List @@ -1063,7 +1079,7 @@ Download Patches For All Games - Baixar Patches para Todos os Jogos + Baixar Modificações para Todos os Jogos Download Complete @@ -1075,11 +1091,11 @@ Patches Downloaded Successfully! - Patches Baixados com Sucesso! + Modificações Baixadas com Sucesso! All Patches available for all games have been downloaded. - Todos os patches disponíveis para todos os jogos foram baixados. + Todos as modificações disponíveis para todos os jogos foram baixadas. Games: @@ -1309,11 +1325,15 @@ Trophy - Troféus + Troféu + + + Open the custom trophy images/sounds folder + Abrir a pasta de imagens/sons de troféus personalizados Logger - Registros de Log + Log/Registro Log Type @@ -1476,8 +1496,8 @@ Música no Menu - Disable Trophy Pop-ups - Desabilitar Pop-ups dos Troféus + Disable Trophy Notification + Desativar Notificações de Troféu Background Image @@ -1565,7 +1585,7 @@ Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. - Tipo de Registro:\nDetermina se a saída da janela de log deve ser sincronizada por motivos de desempenho. Pode impactar negativamente na emulação. + Tipo de Registro:\nDetermina se a saída da janela de registro deve ser sincronizada por motivos de desempenho. Pode impactar negativamente na emulação. Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Atualizar Lista de Compatibilidade:\nAtualiza imediatamente o banco de dados de compatibilidade. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Abrir a pasta de imagens/sons de troféus personalizados:\nVocê pode adicionar imagens e sons personalizados aos troféus.\nAdicione os arquivos em custom_trophy com os seguintes nomes:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Nunca @@ -1797,11 +1821,27 @@ Separate Log Files - Separar Arquivos de Log + Separar Arquivos de Registro Separate Log Files:\nWrites a separate logfile for each game. - Separar Arquivos de Log:\nGrava um arquivo de log para cada jogo. + Separar Arquivos de Registro:\nGrava um arquivo de registro para cada jogo. + + + Trophy Notification Position + Posição da Notificação do Troféu + + + Left + Esquerda + + + Right + Direita + + + Notification Duration + Duração da Notificação @@ -1810,5 +1850,21 @@ Trophy Viewer Visualizador de Troféus + + Progress + Progresso + + + Show Earned Trophies + Mostrar Troféus Conquistados + + + Show Not Earned Trophies + Mostrar Troféus Não Conquistados + + + Show Hidden Trophies + Mostrar Troféus Ocultos + diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index e5cbedb96..8db33ca5b 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -775,6 +775,10 @@ Delete DLC Eliminar DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibilidade... @@ -851,6 +855,10 @@ This game has no update folder to open! Este jogo não tem nenhuma pasta de atualização para abrir! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Falha ao converter ícone. @@ -859,10 +867,18 @@ This game has no save data to delete! Este jogo não tem dados guardados para eliminar! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Dados Guardados + + Trophy + Trophy + SFO Viewer for Visualizador SFO para @@ -1311,6 +1327,10 @@ Trophy Troféus + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Registos @@ -1476,8 +1496,8 @@ Música de Título - Disable Trophy Pop-ups - Desativar Pop-ups dos Troféus + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Atualizar Base de Dados de Compatibilidade:\nAtualiza imediatamente a base de dados de compatibilidade. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Nunca @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separar Ficheiros de Registo:\nEscreve um ficheiro de registo para cada jogo. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Visualizador de Troféus + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 7083e6d49..be1613c57 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! Acest joc nu are folderul de actualizări pentru a fi deschis! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Niciodată @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophy Viewer + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index d9e90d1a2..313233c92 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -775,6 +775,10 @@ Delete DLC Удалить DLC + + Delete Trophy + Удалить трофей + Compatibility... Совместимость... @@ -851,6 +855,10 @@ This game has no update folder to open! У этой игры нет папки обновлений, которую можно открыть! + + No log file found for this game! + Не найден файл журнала для этой игры! + Failed to convert icon. Не удалось преобразовать иконку. @@ -859,10 +867,18 @@ This game has no save data to delete! У этой игры нет сохранений, которые можно удалить! + + This game has no saved trophies to delete! + У этой игры нет сохраненных трофеев для удаления! + Save Data Сохранения + + Trophy + Трофей + SFO Viewer for Просмотр SFO для @@ -1311,6 +1327,10 @@ Trophy Трофеи + + Open the custom trophy images/sounds folder + Откройте папку с пользовательскими изображениями/звуками трофеев + Logger Логирование @@ -1476,8 +1496,8 @@ Заглавная музыка - Disable Trophy Pop-ups - Отключить уведомления о трофеях + Disable Trophy Notification + Отключить уведомления о трофее Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Обновить базу совместимости:\nНемедленно обновить базу данных совместимости. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Открыть пользовательскую папку с трофеями изображений/звуков:\nВы можете добавить пользовательские изображения к трофеям и аудио.\nДобавьте файлы в custom_trophy со следующими именами:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Никогда @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Отдельные файлы логов:\nПишет отдельный файл логов для каждой игры. + + Trophy Notification Position + Местоположение уведомления о трофее + + + Left + Влево + + + Right + Вправо + + + Notification Duration + Продолжительность уведомления + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Просмотр трофеев + + Progress + Прогресс + + + Show Earned Trophies + Показать заработанные трофеи + + + Show Not Earned Trophies + Показать не заработанные трофеи + + + Show Hidden Trophies + Показать скрытые трофеи + diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 0a90bcd10..0783fb1a7 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -775,6 +775,10 @@ Delete DLC Fshi DLC-në + + Delete Trophy + Fshi Trofeun + Compatibility... Përputhshmëria... @@ -851,6 +855,10 @@ This game has no update folder to open! Kjo lojë nuk ka dosje përditësimi për të hapur! + + No log file found for this game! + Nuk u gjet asnjë skedar ditari për këtë lojë! + Failed to convert icon. Konvertimi i ikonës dështoi. @@ -859,10 +867,18 @@ This game has no save data to delete! Kjo lojë nuk ka të dhëna ruajtje për të fshirë! + + This game has no saved trophies to delete! + Kjo lojë nuk ka trofe të ruajtur për të fshirë! + Save Data Të dhënat e ruajtjes + + Trophy + Trofeu + SFO Viewer for Shikuesi SFO për @@ -1311,6 +1327,10 @@ Trophy Trofeu + + Open the custom trophy images/sounds folder + Hap dosjen e imazheve/tingujve të trofeve të personalizuar + Logger Regjistruesi i ditarit @@ -1476,8 +1496,8 @@ Muzika e titullit - Disable Trophy Pop-ups - Çaktivizo njoftimet për Trofetë + Disable Trophy Notification + Çaktivizo Njoftimin e Trofeut Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Përditëso bazën e të dhënave të përputhshmërisë:\nPërditëso menjëherë bazën e të dhënave të përputhshmërisë. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Hap dosjen e imazheve/tingujve të trofeve të personalizuar:\nMund të shtosh imazhe të personalizuara për trofetë dhe një skedar audio.\nShto skedarët në dosjen custom_trophy me emrat që vijojnë:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Kurrë @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Skedarë të Ditarit të Ndarë:\nShkruan një skedar të ditarit të veçuar për secilën lojë. + + Trophy Notification Position + Pozicioni i Njoftimit të Trofeve + + + Left + Majtas + + + Right + Djathtas + + + Notification Duration + Kohëzgjatja e Njoftimit + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Shikuesi i Trofeve + + Progress + Ecuria + + + Show Earned Trophies + Shfaq Trofetë që janë fituar + + + Show Not Earned Trophies + Shfaq Trofetë që nuk janë fituar + + + Show Hidden Trophies + Shfaq Trofetë e Fshehur + diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index e9ea2f20a..6cbd33f17 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -775,6 +775,10 @@ Delete DLC Ta bort DLC + + Delete Trophy + Ta bort trofé + Compatibility... Kompatibilitet... @@ -851,6 +855,10 @@ This game has no update folder to open! Detta spel har ingen uppdateringsmapp att öppna! + + No log file found for this game! + Ingen loggfil hittades för detta spel! + Failed to convert icon. Misslyckades med att konvertera ikon. @@ -859,10 +867,18 @@ This game has no save data to delete! Detta spel har inget sparat data att ta bort! + + This game has no saved trophies to delete! + Detta spel har inga sparade troféer att ta bort! + Save Data Sparat data + + Trophy + Trofé + SFO Viewer for SFO-visare för @@ -1311,6 +1327,10 @@ Trophy Troféer + + Open the custom trophy images/sounds folder + Öppna mapp för anpassade trofébilder/ljud + Logger Loggning @@ -1476,8 +1496,8 @@ Titelmusik - Disable Trophy Pop-ups - Inaktivera popup för troféer + Disable Trophy Notification + Inaktivera troféaviseringar Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Öppna mappen med anpassade trofébilder/ljud:\nDu kan lägga till anpassade bilder till troféerna och ett ljud.\nLägg till filerna i custom_trophy med följande namn:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Aldrig @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separata loggfiler:\nSkriver en separat loggfil för varje spel. + + Trophy Notification Position + Aviseringsposition för trofé + + + Left + Vänster + + + Right + Höger + + + Notification Duration + Varaktighet för avisering + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trofé-visare + + Progress + Förlopp + + + Show Earned Trophies + Visa förtjänade troféer + + + Show Not Earned Trophies + Visa icke-förtjänade troféer + + + Show Hidden Trophies + Visa dolda troféer + diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index ac7dee9a4..cd4f1cadd 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -775,6 +775,10 @@ Delete DLC İndirilebilir İçeriği Sil + + Delete Trophy + Kupayı Sil + Compatibility... Uyumluluk... @@ -851,6 +855,10 @@ This game has no update folder to open! Bu oyunun açılacak güncelleme klasörü yok! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Simge dönüştürülemedi. @@ -859,10 +867,18 @@ This game has no save data to delete! Bu oyunun silinecek kayıt verisi yok! + + This game has no saved trophies to delete! + Bu oyunun silinecek kupası yok! + Save Data Kayıt Verisi + + Trophy + Kupa + SFO Viewer for SFO Görüntüleyici: @@ -1311,6 +1327,10 @@ Trophy Kupa + + Open the custom trophy images/sounds folder + Özel kupa görüntüleri/sesleri klasörünü aç + Logger Kayıt Tutucu @@ -1476,8 +1496,8 @@ Oyun Müziği - Disable Trophy Pop-ups - Kupa Açılır Pencerelerini Devre Dışı Bırak + Disable Trophy Notification + Kupa Bildirimini Devre Dışı Bırak Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Uyumluluk Veritabanını Güncelle:\nUyumluluk veri tabanını hemen güncelleyin. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Asla @@ -1661,7 +1685,7 @@ Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. - Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + HDR'yi Etkinleştir:\nDestekleyen oyunlarda HDR'yi etkinleştirir.\nMonitörünüz, BT2020 PQ renk alanını ve RGB10A2 takas zinciri biçimini desteklemelidir. Game Folders:\nThe list of folders to check for installed games. @@ -1765,15 +1789,15 @@ Video - Video + Görüntü Display Mode - Display Mode + Görüntü Modu Windowed - Windowed + Pencereli Fullscreen @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Ayrı Günlük Dosyaları:\nHer oyun için ayrı bir günlük dosyası yazar. + + Trophy Notification Position + Kupa Bildirim Konumu + + + Left + Sol + + + Right + Sağ + + + Notification Duration + Bildirim Süresi + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Kupa Görüntüleyici + + Progress + İlerleme + + + Show Earned Trophies + Kazanılmış Kupaları Göster + + + Show Not Earned Trophies + Kazanılmamış Kupaları Göster + + + Show Hidden Trophies + Gizli Kupaları Göster + diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 0a3865d49..63f8b012f 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -775,6 +775,10 @@ Delete DLC Видалити DLC + + Delete Trophy + Delete Trophy + Compatibility... Сумісність... @@ -851,6 +855,10 @@ This game has no update folder to open! Ця гра не має папки оновленнь, щоб відкрити її! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! Ця гра не містить збережень, які можна видалити! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Збереження + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Трофеї + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Логування @@ -1476,8 +1496,8 @@ Титульна музика - Disable Trophy Pop-ups - Вимкнути спливаючі вікна трофеїв + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Оновити данні ігрової сумістності:\nНегайно оновить базу даних ігрової сумісності. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Ніколи @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Трофеї + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 50a34ad7a..fe9ad915f 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never Không bao giờ @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trình xem chiến tích + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index b855f2dcd..a9cf7ca19 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -775,6 +775,10 @@ Delete DLC 删除 DLC + + Delete Trophy + 删除奖杯 + Compatibility... 兼容性... @@ -851,6 +855,10 @@ This game has no update folder to open! 这个游戏没有可打开的更新文件夹! + + No log file found for this game! + 没有找到这个游戏的日志文件! + Failed to convert icon. 转换图标失败。 @@ -859,10 +867,18 @@ This game has no save data to delete! 这个游戏没有更新可以删除! + + This game has no saved trophies to delete! + 这个游戏没有保存的奖杯可删除! + Save Data 存档数据 + + Trophy + 奖杯 + SFO Viewer for SFO 查看器 - @@ -1311,6 +1327,10 @@ Trophy 奖杯 + + Open the custom trophy images/sounds folder + 打开自定义奖杯图像/声音文件夹 + Logger 日志 @@ -1476,8 +1496,8 @@ 标题音乐 - Disable Trophy Pop-ups - 禁止弹出奖杯 + Disable Trophy Notification + 禁用奖杯通知 Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. 更新兼容性数据库:\n立即更新兼容性数据库。 + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + 打开自定义奖杯图像/声音文件夹:\n您可以自定义奖杯图像和声音。\n将文件添加到 custom_trophy 文件夹中,文件名如下:\nthophy.mp3、bronze.png、gold.png、platinum.png、silver.png + Never 从不 @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. 独立日志文件:\n每个游戏使用单独的日志文件。 + + Trophy Notification Position + 奖杯通知位置 + + + Left + 左边 + + + Right + 右边 + + + Notification Duration + 通知显示持续时间 + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer 奖杯查看器 + + Progress + 进度 + + + Show Earned Trophies + 显示获得的奖杯 + + + Show Not Earned Trophies + 显示未获得的奖杯 + + + Show Hidden Trophies + 显示隐藏奖杯 + diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 15c921d15..bd3ef8f0b 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -775,6 +775,10 @@ Delete DLC Delete DLC + + Delete Trophy + Delete Trophy + Compatibility... Compatibility... @@ -851,6 +855,10 @@ This game has no update folder to open! This game has no update folder to open! + + No log file found for this game! + No log file found for this game! + Failed to convert icon. Failed to convert icon. @@ -859,10 +867,18 @@ This game has no save data to delete! This game has no save data to delete! + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + Save Data Save Data + + Trophy + Trophy + SFO Viewer for SFO Viewer for @@ -1311,6 +1327,10 @@ Trophy Trophy + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + Logger Logger @@ -1476,8 +1496,8 @@ Title Music - Disable Trophy Pop-ups - Disable Trophy Pop-ups + Disable Trophy Notification + Disable Trophy Notification Background Image @@ -1611,6 +1631,10 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Never 從不 @@ -1803,6 +1827,22 @@ Separate Log Files:\nWrites a separate logfile for each game. Separate Log Files:\nWrites a separate logfile for each game. + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Notification Duration + Notification Duration + TrophyViewer @@ -1810,5 +1850,21 @@ Trophy Viewer Trophy Viewer + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + From 76483f9c7b9554ce82ece6c4358cbba0fd5c9fde Mon Sep 17 00:00:00 2001 From: Missake212 Date: Sun, 2 Mar 2025 19:31:49 +0000 Subject: [PATCH 006/194] opcode implementation test (#2567) --- src/shader_recompiler/frontend/translate/data_share.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 460f8913c..22f5b8644 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -61,6 +61,8 @@ void Translator::EmitDataShare(const GcnInst& inst) { return DS_WRITE(64, false, false, false, inst); case Opcode::DS_WRITE2_B64: return DS_WRITE(64, false, true, false, inst); + case Opcode::DS_WRITE2ST64_B64: + return DS_WRITE(64, false, true, true, inst); case Opcode::DS_READ_B64: return DS_READ(64, false, false, false, inst); case Opcode::DS_READ2_B64: From f4110c43a78a1b0d13c33ddbd75bd5b552bcfdda Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 2 Mar 2025 16:32:28 -0300 Subject: [PATCH 007/194] Fix time - sceKernelClockGettime (#2582) --- src/core/libraries/kernel/time.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/core/libraries/kernel/time.cpp b/src/core/libraries/kernel/time.cpp index 2565b8078..508e54089 100644 --- a/src/core/libraries/kernel/time.cpp +++ b/src/core/libraries/kernel/time.cpp @@ -115,14 +115,16 @@ int PS4_SYSV_ABI sceKernelClockGettime(s32 clock_id, OrbisKernelTimespec* tp) { break; } - timespec t{}; - int result = clock_gettime(pclock_id, &t); - tp->tv_sec = t.tv_sec; - tp->tv_nsec = t.tv_nsec; - if (result == 0) { - return ORBIS_OK; + time_t raw_time = time(nullptr); + + if (raw_time == (time_t)(-1)) { + return ORBIS_KERNEL_ERROR_EINVAL; } - return ORBIS_KERNEL_ERROR_EINVAL; + + tp->tv_sec = static_cast(raw_time); + tp->tv_nsec = 0; + + return ORBIS_OK; } int PS4_SYSV_ABI posix_clock_gettime(s32 clock_id, OrbisKernelTimespec* time) { From d59536a71c923508e8f2d730110f55fa43161696 Mon Sep 17 00:00:00 2001 From: Dmugetsu <168934208+diegolix29@users.noreply.github.com> Date: Sun, 2 Mar 2025 13:36:12 -0600 Subject: [PATCH 008/194] Adding Top and Bottom trophy option for pop window + Trophy improvements (#2566) * Adding top button option for trophy pop up * Ui fix * Clang format * improvements to trophy pr * improvements * Note: The sound will only work in QT versions * -. * Update path_util.cpp * Update path_util.cpp * centered text when using top and bottom option * Clang * trophy viewer now opens in window not fullscreen --------- Co-authored-by: DanielSvoboda --- src/common/config.cpp | 17 ++++---- src/common/config.h | 4 +- src/common/path_util.cpp | 18 +++++++- src/core/libraries/np_trophy/trophy_ui.cpp | 51 +++++++++++++++++----- src/qt_gui/settings_dialog.cpp | 24 ++++++++-- src/qt_gui/settings_dialog.ui | 20 ++++++--- src/qt_gui/translations/en_US.ts | 12 ++++- src/qt_gui/trophy_viewer.cpp | 40 ++++++++++++++++- src/qt_gui/trophy_viewer.h | 8 ++++ 9 files changed, 158 insertions(+), 36 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 36566a14c..514024c30 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -53,7 +53,7 @@ static bool isShaderDebug = false; static bool isShowSplash = false; static bool isAutoUpdate = false; static bool isAlwaysShowChangelog = false; -static bool isLeftSideTrophy = false; +static std::string isSideTrophy = "right"; static bool isNullGpu = false; static bool shouldCopyGPUBuffers = false; static bool shouldDumpShaders = false; @@ -270,8 +270,8 @@ bool alwaysShowChangelog() { return isAlwaysShowChangelog; } -bool leftSideTrophy() { - return isLeftSideTrophy; +std::string sideTrophy() { + return isSideTrophy; } bool nullGpu() { @@ -381,8 +381,9 @@ void setAutoUpdate(bool enable) { void setAlwaysShowChangelog(bool enable) { isAlwaysShowChangelog = enable; } -void setLeftSideTrophy(bool enable) { - isLeftSideTrophy = enable; + +void setSideTrophy(std::string side) { + isSideTrophy = side; } void setNullGpu(bool enable) { @@ -737,7 +738,7 @@ void load(const std::filesystem::path& path) { isShowSplash = toml::find_or(general, "showSplash", true); isAutoUpdate = toml::find_or(general, "autoUpdate", false); isAlwaysShowChangelog = toml::find_or(general, "alwaysShowChangelog", false); - isLeftSideTrophy = toml::find_or(general, "leftSideTrophy", false); + isSideTrophy = toml::find_or(general, "sideTrophy", "right"); separateupdatefolder = toml::find_or(general, "separateUpdateEnabled", false); compatibilityData = toml::find_or(general, "compatibilityEnabled", false); checkCompatibilityOnStartup = @@ -888,7 +889,7 @@ void save(const std::filesystem::path& path) { data["General"]["showSplash"] = isShowSplash; data["General"]["autoUpdate"] = isAutoUpdate; data["General"]["alwaysShowChangelog"] = isAlwaysShowChangelog; - data["General"]["leftSideTrophy"] = isLeftSideTrophy; + data["General"]["sideTrophy"] = isSideTrophy; data["General"]["separateUpdateEnabled"] = separateupdatefolder; data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; @@ -1018,7 +1019,7 @@ void setDefaultValues() { isShowSplash = false; isAutoUpdate = false; isAlwaysShowChangelog = false; - isLeftSideTrophy = false; + isSideTrophy = "right"; isNullGpu = false; shouldDumpShaders = false; vblankDivider = 1; diff --git a/src/common/config.h b/src/common/config.h index 988734b93..82d65d30e 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -63,7 +63,7 @@ bool collectShadersForDebug(); bool showSplash(); bool autoUpdate(); bool alwaysShowChangelog(); -bool leftSideTrophy(); +std::string sideTrophy(); bool nullGpu(); bool copyGPUCmdBuffers(); bool dumpShaders(); @@ -77,7 +77,7 @@ void setCollectShaderForDebug(bool enable); void setShowSplash(bool enable); void setAutoUpdate(bool enable); void setAlwaysShowChangelog(bool enable); -void setLeftSideTrophy(bool enable); +void setSideTrophy(std::string side); void setNullGpu(bool enable); void setAllowHDR(bool enable); void setCopyGPUCmdBuffers(bool enable); diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index d48e8c3fe..6bc73ee43 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/logging/log.h" #include "common/path_util.h" @@ -130,6 +131,21 @@ static auto UserPaths = [] { create_path(PathType::MetaDataDir, user_dir / METADATA_DIR); create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY); + std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt"); + if (notice_file.is_open()) { + notice_file + << "++++++++++++++++++++++++++++++++\n+ Custom Trophy Images / Sound " + "+\n++++++++++++++++++++++++++++++++\n\nYou can add custom images to the " + "trophies.\n*We recommend a square resolution image, for example 200x200, 500x500, " + "the same size as the height and width.\nIn this folder ('user\\custom_trophy'), " + "add the files with the following " + "names:\n\nbronze.png\nsilver.png\ngold.png\nplatinum.png\n\nYou can add a custom " + "sound for trophy notifications.\n*By default, no audio is played unless it is in " + "this folder and you are using the QT version.\nIn this folder " + "('user\\custom_trophy'), add the files with the following names:\n\ntrophy.mp3"; + notice_file.close(); + } + return paths; }(); @@ -223,4 +239,4 @@ std::filesystem::path PathFromQString(const QString& path) { } #endif -} // namespace Common::FS \ No newline at end of file +} // namespace Common::FS diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp index 2564cbf5d..aba56f341 100644 --- a/src/core/libraries/np_trophy/trophy_ui.cpp +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -27,14 +27,17 @@ namespace Libraries::NpTrophy { std::optional current_trophy_ui; std::queue trophy_queue; std::mutex queueMtx; -bool isLeftSide; + +std::string side = "right"; + double trophy_timer; TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::string& trophyName, const std::string_view& rarity) : trophy_name(trophyName), trophy_type(rarity) { - isLeftSide = Config::leftSideTrophy(); + side = Config::sideTrophy(); + trophy_timer = Config::getTrophyNotificationDuration(); if (std::filesystem::exists(trophyIconPath)) { @@ -115,8 +118,8 @@ float fade_out_duration = 0.5f; // Final fade duration void TrophyUI::Draw() { const auto& io = GetIO(); - float AdjustWidth = io.DisplaySize.x / 1280; - float AdjustHeight = io.DisplaySize.y / 720; + float AdjustWidth = io.DisplaySize.x / 1920; + float AdjustHeight = io.DisplaySize.y / 1080; const ImVec2 window_size{ std::min(io.DisplaySize.x, (350 * AdjustWidth)), std::min(io.DisplaySize.y, (70 * AdjustHeight)), @@ -125,21 +128,38 @@ void TrophyUI::Draw() { elapsed_time += io.DeltaTime; float progress = std::min(elapsed_time / animation_duration, 1.0f); - // left or right position - float final_pos_x; - if (isLeftSide) { - start_pos.x = -window_size.x; + float final_pos_x, start_x; + float final_pos_y, start_y; + + if (side == "top") { + start_x = (io.DisplaySize.x - window_size.x) * 0.5f; + start_y = -window_size.y; + final_pos_x = start_x; + final_pos_y = 20 * AdjustHeight; + } else if (side == "left") { + start_x = -window_size.x; + start_y = 50 * AdjustHeight; final_pos_x = 20 * AdjustWidth; - } else { - start_pos.x = io.DisplaySize.x; + final_pos_y = start_y; + } else if (side == "right") { + start_x = io.DisplaySize.x; + start_y = 50 * AdjustHeight; final_pos_x = io.DisplaySize.x - window_size.x - 20 * AdjustWidth; + final_pos_y = start_y; + } else if (side == "bottom") { + start_x = (io.DisplaySize.x - window_size.x) * 0.5f; + start_y = io.DisplaySize.y; + final_pos_x = start_x; + final_pos_y = io.DisplaySize.y - window_size.y - 20 * AdjustHeight; } - ImVec2 current_pos = ImVec2(start_pos.x + (final_pos_x - start_pos.x) * progress, - start_pos.y + (target_pos.y - start_pos.y) * progress); + ImVec2 current_pos = ImVec2(start_x + (final_pos_x - start_x) * progress, + start_y + (final_pos_y - start_y) * progress); trophy_timer -= io.DeltaTime; + ImGui::SetNextWindowPos(current_pos); + // If the remaining time of the trophy is less than or equal to 1 second, the fade-out begins. if (trophy_timer <= 1.0f) { float fade_out_time = 1.0f - (trophy_timer / 1.0f); @@ -192,6 +212,13 @@ void TrophyUI::Draw() { const float text_height = ImGui::CalcTextSize(combinedString.c_str()).y; SetCursorPosY((window_size.y - text_height) * 0.5); } + + if (side == "top" || side == "bottom") { + float text_width = ImGui::CalcTextSize(trophy_name.c_str()).x; + float centered_x = (window_size.x - text_width) * 0.5f; + ImGui::SetCursorPosX(std::max(centered_x, 10.0f * AdjustWidth)); + } + ImGui::PushTextWrapPos(window_size.x - (60 * AdjustWidth)); TextWrapped("Trophy earned!\n%s", trophy_name.c_str()); ImGui::SameLine(window_size.x - (60 * AdjustWidth)); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index bde104828..a3890b548 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -418,8 +418,14 @@ void SettingsDialog::LoadValuesFromConfig() { ui->disableTrophycheckBox->setChecked( toml::find_or(data, "General", "isTrophyPopupDisabled", false)); ui->popUpDurationSpinBox->setValue(Config::getTrophyNotificationDuration()); - ui->radioButton_Left->setChecked(Config::leftSideTrophy()); - ui->radioButton_Right->setChecked(!ui->radioButton_Left->isChecked()); + + QString side = QString::fromStdString(Config::sideTrophy()); + + ui->radioButton_Left->setChecked(side == "left"); + ui->radioButton_Right->setChecked(side == "right"); + ui->radioButton_Top->setChecked(side == "top"); + ui->radioButton_Bottom->setChecked(side == "bottom"); + ui->BGMVolumeSlider->setValue(toml::find_or(data, "General", "BGMvolume", 50)); ui->discordRPCCheckbox->setChecked( toml::find_or(data, "General", "enableDiscordRPC", true)); @@ -612,7 +618,7 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { //User if (elementName == "OpenCustomTrophyLocationButton") { - text = tr("Open the custom trophy images/sounds folder:\\nYou can add custom images to the trophies and an audio.\\nAdd the files to custom_trophy with the following names:\\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png"); + text = tr("Open the custom trophy images/sounds folder:\\nYou can add custom images to the trophies and an audio.\\nAdd the files to custom_trophy with the following names:\\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png\\nNote: The sound will only work in QT versions."); } // Input @@ -706,7 +712,17 @@ void SettingsDialog::UpdateSettings() { Config::setIsMotionControlsEnabled(ui->motionControlsCheckBox->isChecked()); Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked()); Config::setTrophyNotificationDuration(ui->popUpDurationSpinBox->value()); - Config::setLeftSideTrophy(ui->radioButton_Left->isChecked()); + + if (ui->radioButton_Top->isChecked()) { + Config::setSideTrophy("top"); + } else if (ui->radioButton_Left->isChecked()) { + Config::setSideTrophy("left"); + } else if (ui->radioButton_Right->isChecked()) { + Config::setSideTrophy("right"); + } else if (ui->radioButton_Bottom->isChecked()) { + Config::setSideTrophy("bottom"); + } + Config::setPlayBGM(ui->playBGMCheckBox->isChecked()); Config::setAllowHDR(ui->enableHDRCheckBox->isChecked()); Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString()); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index c793aced5..7db0afa59 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -1295,17 +1295,25 @@ - - - 0 - 0 - - Right + + + + Top + + + + + + + Bottom + + + diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 92fb59a02..29ce27f07 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index 148cbee06..bfa47e3cc 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "common/path_util.h" @@ -157,6 +158,15 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo // Adds the dock to the left area this->addDockWidget(Qt::LeftDockWidgetArea, trophyInfoDock); + expandButton = new QPushButton(">>", this); + expandButton->setGeometry(80, 0, 27, 27); + expandButton->hide(); + + connect(expandButton, &QPushButton::clicked, this, [this, trophyInfoDock] { + trophyInfoDock->setVisible(true); + expandButton->hide(); + }); + // Connects checkbox signals to update trophy display #if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) connect(showEarnedCheck, &QCheckBox::stateChanged, this, &TrophyViewer::updateTableFilters); @@ -173,6 +183,31 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo updateTrophyInfo(); updateTableFilters(); + + connect(trophyInfoDock, &QDockWidget::topLevelChanged, this, [this, trophyInfoDock] { + if (!trophyInfoDock->isVisible()) { + expandButton->show(); + } + }); + + connect(trophyInfoDock, &QDockWidget::visibilityChanged, this, [this, trophyInfoDock] { + if (!trophyInfoDock->isVisible()) { + expandButton->show(); + } else { + expandButton->hide(); + } + }); +} + +void TrophyViewer::onDockClosed() { + if (!trophyInfoDock->isVisible()) { + reopenButton->setVisible(true); + } +} + +void TrophyViewer::reopenLeftDock() { + trophyInfoDock->show(); + reopenButton->setVisible(false); } void TrophyViewer::PopulateTrophyWidget(QString title) { @@ -354,7 +389,10 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { tabWidget->addTab(tableWidget, tabName.insert(6, " ").replace(0, 1, tabName.at(0).toUpper())); - this->showMaximized(); + this->resize(width + 400, 720); + QSize mainWindowSize = QApplication::activeWindow()->size(); + this->resize(mainWindowSize.width() * 0.8, mainWindowSize.height() * 0.8); + this->show(); tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); tableWidget->setColumnWidth(3, 650); diff --git a/src/qt_gui/trophy_viewer.h b/src/qt_gui/trophy_viewer.h index bd99e1a8c..75fb500e7 100644 --- a/src/qt_gui/trophy_viewer.h +++ b/src/qt_gui/trophy_viewer.h @@ -4,12 +4,15 @@ #pragma once #include +#include #include +#include #include #include #include #include #include +#include #include #include #include @@ -26,6 +29,8 @@ public: void updateTrophyInfo(); void updateTableFilters(); + void onDockClosed(); + void reopenLeftDock(); private: void PopulateTrophyWidget(QString title); @@ -39,6 +44,9 @@ private: QCheckBox* showEarnedCheck; QCheckBox* showNotEarnedCheck; QCheckBox* showHiddenCheck; + QPushButton* expandButton; + QDockWidget* trophyInfoDock; + QPushButton* reopenButton; std::string GetTrpType(const QChar trp_) { switch (trp_.toLatin1()) { From 7a4244ac8b37caab876463421c07939a2e222831 Mon Sep 17 00:00:00 2001 From: baggins183 Date: Sun, 2 Mar 2025 11:52:32 -0800 Subject: [PATCH 009/194] Misc Cleanups (#2579) -dont do trivial phi removal during SRT pass, that's now done in ssa_rewrite -remove unused variable when walking tess attributes -fix some tess comments --- .../ir/passes/hull_shader_transform.cpp | 29 +++++------ src/shader_recompiler/ir/srt_gvn_table.h | 52 ++++--------------- 2 files changed, 24 insertions(+), 57 deletions(-) diff --git a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp index fced4b362..48727e32a 100644 --- a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp +++ b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp @@ -75,10 +75,12 @@ namespace Shader::Optimization { * * output_patch_stride and output_cp_stride are usually compile time constants in the gcn * - * Hull shaders can probably also read output control points corresponding to other threads, like - * shared memory (but we havent seen this yet). - * ^ This is an UNREACHABLE for now. We may need to insert additional barriers if this happens. - * They should also be able to read PatchConst values, + * Hull shaders can also read output control points corresponding to other threads. + * In HLSL style, this should only be possible in the Patch Constant function. + * TODO we may need to insert additional barriers if sync is free/more permissive + * on AMD LDS HW + + * They should also be able to read output PatchConst values, * although not sure if this happens in practice. * * To determine which type of attribute (input, output, patchconst) we the check the users of @@ -101,22 +103,22 @@ namespace Shader::Optimization { * layout (location = 0) in vec4 in_attrs[][NUM_INPUT_ATTRIBUTES]; * * Here the NUM_INPUT_ATTRIBUTES is derived from the ls_stride member of the TessConstants V#. - * We divide ls_stride (in bytes) by 16 to get the number of vec4 attributes. - * For TES, the number of attributes comes from hs_cp_stride / 16. + * We take ALIGN_UP(ls_stride, 16) / 16 to get the number of vec4 attributes. + * For TES, NUM_INPUT_ATTRIBUTES is ALIGN_UP(hs_cp_stride, 16) / 16. * The first (outer) dimension is unsized but corresponds to the number of vertices in the hs input * patch (for Hull) or the hs output patch (for Domain). * * For input reads in TCS or TES, we emit SPIR-V like: - * float value = in_attrs[addr / ls_stride][(addr % ls_stride) >> 4][(addr & 0xF) >> 2]; + * float value = in_attrs[addr / ls_stride][(addr % ls_stride) >> 4][(addr % ls_stride) >> 2]; * * For output writes, we assume the control point index is InvocationId, since high level languages * impose that restriction (although maybe it's technically possible on hardware). So SPIR-V looks * like this: * layout (location = 0) in vec4 in_attrs[][NUM_OUTPUT_ATTRIBUTES]; - * out_attrs[InvocationId][(addr % hs_cp_stride) >> 4][(addr & 0xF) >> 2] = value; + * out_attrs[InvocationId][(addr % hs_cp_stride) >> 4][(addr % hs_cp_stride) >> 2] = value; * - * NUM_OUTPUT_ATTRIBUTES is derived by hs_cp_stride / 16, so it can link with the TES in_attrs - * variable. + * NUM_OUTPUT_ATTRIBUTES is derived by ALIGN_UP(hs_cp_stride, 16) / 16, so it matches + * NUM_INPUT_ATTRIBUTES of the TES. * * Another challenge is the fact that the GCN shader needs to address attributes from LDS as a whole * which contains the attributes from many patches. On the other hand, higher level shading @@ -235,12 +237,11 @@ private: case IR::Opcode::Phi: { struct PhiCounter { u16 seq_num; - u8 unique_edge; - u8 counter; + u16 unique_edge; }; PhiCounter count = inst->Flags(); - ASSERT_MSG(count.counter == 0 || count.unique_edge == use.operand); + ASSERT_MSG(count.seq_num == 0 || count.unique_edge == use.operand); // the point of seq_num is to tell us if we've already traversed this // phi on the current walk. Alternatively we could keep a set of phi's // seen on the current walk. This is to handle phi cycles @@ -249,13 +250,11 @@ private: count.seq_num = seq_num; // Mark the phi as having been traversed originally through this edge count.unique_edge = use.operand; - count.counter = inc; } else if (count.seq_num < seq_num) { count.seq_num = seq_num; // For now, assume we are visiting this phi via the same edge // as on other walks. If not, some dataflow analysis might be necessary ASSERT(count.unique_edge == use.operand); - count.counter += inc; } else { // count.seq_num == seq_num // there's a cycle, and we've already been here on this walk diff --git a/src/shader_recompiler/ir/srt_gvn_table.h b/src/shader_recompiler/ir/srt_gvn_table.h index 232ee6152..3baa1c7da 100644 --- a/src/shader_recompiler/ir/srt_gvn_table.h +++ b/src/shader_recompiler/ir/srt_gvn_table.h @@ -52,24 +52,16 @@ private: switch (inst->GetOpcode()) { case IR::Opcode::Phi: { - // hack to get to parity with main - // Need to fix ssa_rewrite pass to remove certain phis - std::optional source = TryRemoveTrivialPhi(inst); - if (!source) { - const auto pred = [](IR::Inst* inst) -> std::optional { - if (inst->GetOpcode() == IR::Opcode::GetUserData || - inst->GetOpcode() == IR::Opcode::CompositeConstructU32x2 || - inst->GetOpcode() == IR::Opcode::ReadConst) { - return inst; - } - return std::nullopt; - }; - source = IR::BreadthFirstSearch(inst, pred).transform([](auto inst) { - return IR::Value{inst}; - }); - ASSERT(source); - } - vn = GetValueNumber(source.value()); + const auto pred = [](IR::Inst* inst) -> std::optional { + if (inst->GetOpcode() == IR::Opcode::GetUserData || + inst->GetOpcode() == IR::Opcode::CompositeConstructU32x2 || + inst->GetOpcode() == IR::Opcode::ReadConst) { + return inst; + } + return std::nullopt; + }; + IR::Inst* source = IR::BreadthFirstSearch(inst, pred).value(); + vn = GetValueNumber(source); value_numbers[IR::Value(inst)] = vn; break; } @@ -117,30 +109,6 @@ private: return iv; } - // Temp workaround for something like this: - // [0000555558a5baf8] %297 = Phi [ %24, {Block $1} ], [ %297, {Block $5} ] (uses: 4) - // [0000555558a4e038] %305 = CompositeConstructU32x2 %297, %296 (uses: 4) - // [0000555558a4e0a8] %306 = ReadConst %305, #0 (uses: 2) - // Should probably be fixed in ssa_rewrite - std::optional TryRemoveTrivialPhi(IR::Inst* phi) { - IR::Value single_source{}; - - for (auto i = 0; i < phi->NumArgs(); i++) { - IR::Value v = phi->Arg(i).Resolve(); - if (v == IR::Value(phi)) { - continue; - } - if (!single_source.IsEmpty() && single_source != v) { - return std::nullopt; - } - single_source = v; - } - - ASSERT(!single_source.IsEmpty()); - phi->ReplaceUsesWith(single_source); - return single_source; - } - struct HashInstVector { size_t operator()(const InstVector& iv) const { u32 h = 0; From a583a9abe08622ff90c0589f06c9abc2165e7994 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Sun, 2 Mar 2025 20:13:23 +0000 Subject: [PATCH 010/194] fixes to get in game (#2583) --- src/shader_recompiler/backend/spirv/emit_spirv.cpp | 2 ++ src/shader_recompiler/backend/spirv/spirv_emit_context.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 6b0d7228b..036df24d8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -32,6 +32,8 @@ static constexpr spv::ExecutionMode GetInputPrimitiveType(AmdGpu::PrimitiveType return spv::ExecutionMode::Triangles; case AmdGpu::PrimitiveType::AdjTriangleList: return spv::ExecutionMode::InputTrianglesAdjacency; + case AmdGpu::PrimitiveType::AdjLineList: + return spv::ExecutionMode::InputLinesAdjacency; default: UNREACHABLE_MSG("Unknown input primitive type {}", u32(type)); } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 7c25d1477..e20cfeae2 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -50,6 +50,8 @@ static constexpr u32 NumVertices(AmdGpu::PrimitiveType type) { return 3u; case AmdGpu::PrimitiveType::AdjTriangleList: return 6u; + case AmdGpu::PrimitiveType::AdjLineList: + return 4u; default: UNREACHABLE(); } From 951128389d3b87a95fdfccf033795486985c7bd5 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 2 Mar 2025 23:09:38 +0200 Subject: [PATCH 011/194] [Lib] libsceHttp (#2576) * implemented sceHttpUriParse * argg clang * improved in case of file://// (probably) * rewrote httpuriparse to support file://// * fixed uriparse --- CMakeLists.txt | 1 + src/core/libraries/network/http.cpp | 275 +++++++++++++++++++++++- src/core/libraries/network/http.h | 20 +- src/core/libraries/network/http_error.h | 66 ++++++ 4 files changed, 352 insertions(+), 10 deletions(-) create mode 100644 src/core/libraries/network/http_error.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b05a175bb..88d874af0 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,6 +298,7 @@ set(KERNEL_LIB src/core/libraries/kernel/sync/mutex.cpp set(NETWORK_LIBS src/core/libraries/network/http.cpp src/core/libraries/network/http.h + src/core/libraries/network/http_error.h src/core/libraries/network/http2.cpp src/core/libraries/network/http2.h src/core/libraries/network/net.cpp diff --git a/src/core/libraries/network/http.cpp b/src/core/libraries/network/http.cpp index 8e06c76fa..dbb5b096a 100644 --- a/src/core/libraries/network/http.cpp +++ b/src/core/libraries/network/http.cpp @@ -5,6 +5,7 @@ #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/network/http.h" +#include "http_error.h" namespace Libraries::Http { @@ -566,17 +567,277 @@ int PS4_SYSV_ABI sceHttpUriMerge() { return ORBIS_OK; } -int PS4_SYSV_ABI sceHttpUriParse() { +int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool, + size_t* require, size_t prepare) { + LOG_INFO(Lib_Http, "srcUri = {}", std::string(srcUri)); + if (!srcUri) { + LOG_ERROR(Lib_Http, "invalid url"); + return ORBIS_HTTP_ERROR_INVALID_URL; + } + if (!out && !pool && !require) { + LOG_ERROR(Lib_Http, "invalid values"); + return ORBIS_HTTP_ERROR_INVALID_VALUE; + } + + if (out && pool) { + memset(out, 0, sizeof(OrbisHttpUriElement)); + out->scheme = (char*)pool; + } + + // Track the total required buffer size + size_t requiredSize = 0; + + // Parse the scheme (e.g., "http:", "https:", "file:") + size_t schemeLength = 0; + while (srcUri[schemeLength] && srcUri[schemeLength] != ':') { + if (!isalnum(srcUri[schemeLength])) { + LOG_ERROR(Lib_Http, "invalid url"); + return ORBIS_HTTP_ERROR_INVALID_URL; + } + schemeLength++; + } + + if (pool && prepare < schemeLength + 1) { + LOG_ERROR(Lib_Http, "out of memory"); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + if (out && pool) { + memcpy(out->scheme, srcUri, schemeLength); + out->scheme[schemeLength] = '\0'; + } + + requiredSize += schemeLength + 1; + + // Move past the scheme and ':' character + size_t offset = schemeLength + 1; + + // Check if "//" appears after the scheme + if (strncmp(srcUri + offset, "//", 2) == 0) { + // "//" is present + if (out) { + out->opaque = false; + } + offset += 2; // Move past "//" + } else { + // "//" is not present + if (out) { + out->opaque = true; + } + } + + // Handle "file" scheme + if (strncmp(srcUri, "file", 4) == 0) { + // File URIs typically start with "file://" + if (out && !out->opaque) { + // Skip additional slashes (e.g., "////") + while (srcUri[offset] == '/') { + offset++; + } + + // Parse the path (everything after the slashes) + char* pathStart = (char*)srcUri + offset; + size_t pathLength = 0; + while (pathStart[pathLength] && pathStart[pathLength] != '?' && + pathStart[pathLength] != '#') { + pathLength++; + } + + // Ensure the path starts with '/' + if (pathLength > 0 && pathStart[0] != '/') { + // Prepend '/' to the path + requiredSize += pathLength + 2; // Include '/' and null terminator + + if (pool && prepare < requiredSize) { + LOG_ERROR(Lib_Http, "out of memory"); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + if (out && pool) { + out->path = (char*)pool + (requiredSize - pathLength - 2); + out->path[0] = '/'; // Add leading '/' + memcpy(out->path + 1, pathStart, pathLength); + out->path[pathLength + 1] = '\0'; + } + } else { + // Path already starts with '/' + requiredSize += pathLength + 1; + + if (pool && prepare < requiredSize) { + LOG_ERROR(Lib_Http, "out of memory"); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + if (out && pool) { + memcpy((char*)pool + (requiredSize - pathLength - 1), pathStart, pathLength); + out->path = (char*)pool + (requiredSize - pathLength - 1); + out->path[pathLength] = '\0'; + } + } + + // Move past the path + offset += pathLength; + } + } + + // Handle non-file schemes (e.g., "http", "https") + else { + // Parse the host and port + char* hostStart = (char*)srcUri + offset; + while (*hostStart == '/') { + hostStart++; + } + + size_t hostLength = 0; + while (hostStart[hostLength] && hostStart[hostLength] != '/' && + hostStart[hostLength] != '?' && hostStart[hostLength] != ':') { + hostLength++; + } + + requiredSize += hostLength + 1; + + if (pool && prepare < requiredSize) { + LOG_ERROR(Lib_Http, "out of memory"); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + if (out && pool) { + memcpy((char*)pool + (requiredSize - hostLength - 1), hostStart, hostLength); + out->hostname = (char*)pool + (requiredSize - hostLength - 1); + out->hostname[hostLength] = '\0'; + } + + // Move past the host + offset += hostLength; + + // Parse the port (if present) + if (hostStart[hostLength] == ':') { + char* portStart = hostStart + hostLength + 1; + size_t portLength = 0; + while (portStart[portLength] && isdigit(portStart[portLength])) { + portLength++; + } + + requiredSize += portLength + 1; + + if (pool && prepare < requiredSize) { + LOG_ERROR(Lib_Http, "out of memory"); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + // Convert the port string to a uint16_t + char portStr[6]; // Max length for a port number (65535) + if (portLength > 5) { + LOG_ERROR(Lib_Http, "invalid url"); + return ORBIS_HTTP_ERROR_INVALID_URL; + } + memcpy(portStr, portStart, portLength); + portStr[portLength] = '\0'; + + uint16_t port = (uint16_t)atoi(portStr); + if (port == 0 && portStr[0] != '0') { + LOG_ERROR(Lib_Http, "invalid url"); + return ORBIS_HTTP_ERROR_INVALID_URL; + } + + // Set the port in the output structure + if (out) { + out->port = port; + } + + // Move past the port + offset += portLength + 1; + } + } + + // Parse the path (if present) + if (srcUri[offset] == '/') { + char* pathStart = (char*)srcUri + offset; + size_t pathLength = 0; + while (pathStart[pathLength] && pathStart[pathLength] != '?' && + pathStart[pathLength] != '#') { + pathLength++; + } + + requiredSize += pathLength + 1; + + if (pool && prepare < requiredSize) { + LOG_ERROR(Lib_Http, "out of memory"); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + if (out && pool) { + memcpy((char*)pool + (requiredSize - pathLength - 1), pathStart, pathLength); + out->path = (char*)pool + (requiredSize - pathLength - 1); + out->path[pathLength] = '\0'; + } + + // Move past the path + offset += pathLength; + } + + // Parse the query (if present) + if (srcUri[offset] == '?') { + char* queryStart = (char*)srcUri + offset + 1; + size_t queryLength = 0; + while (queryStart[queryLength] && queryStart[queryLength] != '#') { + queryLength++; + } + + requiredSize += queryLength + 1; + + if (pool && prepare < requiredSize) { + LOG_ERROR(Lib_Http, "out of memory"); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + if (out && pool) { + memcpy((char*)pool + (requiredSize - queryLength - 1), queryStart, queryLength); + out->query = (char*)pool + (requiredSize - queryLength - 1); + out->query[queryLength] = '\0'; + } + + // Move past the query + offset += queryLength + 1; + } + + // Parse the fragment (if present) + if (srcUri[offset] == '#') { + char* fragmentStart = (char*)srcUri + offset + 1; + size_t fragmentLength = 0; + while (fragmentStart[fragmentLength]) { + fragmentLength++; + } + + requiredSize += fragmentLength + 1; + + if (pool && prepare < requiredSize) { + LOG_ERROR(Lib_Http, "out of memory"); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + if (out && pool) { + memcpy((char*)pool + (requiredSize - fragmentLength - 1), fragmentStart, + fragmentLength); + out->fragment = (char*)pool + (requiredSize - fragmentLength - 1); + out->fragment[fragmentLength] = '\0'; + } + } + + // Calculate the total required buffer size + if (require) { + *require = requiredSize; // Update with actual required size + } + + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize) { LOG_ERROR(Lib_Http, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceHttpUriSweepPath() { - LOG_ERROR(Lib_Http, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceHttpUriUnescape() { +int PS4_SYSV_ABI sceHttpUriUnescape(char* out, size_t* require, size_t prepare, const char* in) { LOG_ERROR(Lib_Http, "(STUBBED) called"); return ORBIS_OK; } diff --git a/src/core/libraries/network/http.h b/src/core/libraries/network/http.h index 24bc83020..c687c60c4 100644 --- a/src/core/libraries/network/http.h +++ b/src/core/libraries/network/http.h @@ -11,6 +11,19 @@ class SymbolsResolver; namespace Libraries::Http { +struct OrbisHttpUriElement { + bool opaque; + char* scheme; + char* username; + char* password; + char* hostname; + char* path; + char* query; + char* fragment; + u16 port; + u8 reserved[10]; +}; + int PS4_SYSV_ABI sceHttpAbortRequest(); int PS4_SYSV_ABI sceHttpAbortRequestForce(); int PS4_SYSV_ABI sceHttpAbortWaitRequest(); @@ -122,9 +135,10 @@ int PS4_SYSV_ABI sceHttpUriBuild(); int PS4_SYSV_ABI sceHttpUriCopy(); int PS4_SYSV_ABI sceHttpUriEscape(); int PS4_SYSV_ABI sceHttpUriMerge(); -int PS4_SYSV_ABI sceHttpUriParse(); -int PS4_SYSV_ABI sceHttpUriSweepPath(); -int PS4_SYSV_ABI sceHttpUriUnescape(); +int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool, + size_t* require, size_t prepare); +int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize); +int PS4_SYSV_ABI sceHttpUriUnescape(char* out, size_t* require, size_t prepare, const char* in); int PS4_SYSV_ABI sceHttpWaitRequest(); void RegisterlibSceHttp(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/network/http_error.h b/src/core/libraries/network/http_error.h new file mode 100644 index 000000000..49cc89766 --- /dev/null +++ b/src/core/libraries/network/http_error.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/libraries/error_codes.h" + +constexpr int ORBIS_HTTP_ERROR_BEFORE_INIT = 0x80431001; +constexpr int ORBIS_HTTP_ERROR_ALREADY_INITED = 0x80431020; +constexpr int ORBIS_HTTP_ERROR_BUSY = 0x80431021; +constexpr int ORBIS_HTTP_ERROR_OUT_OF_MEMORY = 0x80431022; +constexpr int ORBIS_HTTP_ERROR_NOT_FOUND = 0x80431025; +constexpr int ORBIS_HTTP_ERROR_INVALID_VERSION = 0x8043106a; +constexpr int ORBIS_HTTP_ERROR_INVALID_ID = 0x80431100; +constexpr int ORBIS_HTTP_ERROR_OUT_OF_SIZE = 0x80431104; +constexpr int ORBIS_HTTP_ERROR_INVALID_VALUE = 0x804311fe; + +constexpr int ORBIS_HTTP_ERROR_INVALID_URL = 0x80433060; +constexpr int ORBIS_HTTP_ERROR_UNKNOWN_SCHEME = 0x80431061; +constexpr int ORBIS_HTTP_ERROR_NETWORK = 0x80431063; +constexpr int ORBIS_HTTP_ERROR_BAD_RESPONSE = 0x80431064; +constexpr int ORBIS_HTTP_ERROR_BEFORE_SEND = 0x80431065; +constexpr int ORBIS_HTTP_ERROR_AFTER_SEND = 0x80431066; +constexpr int ORBIS_HTTP_ERROR_TIMEOUT = 0x80431068; +constexpr int ORBIS_HTTP_ERROR_UNKNOWN_AUTH_TYPE = 0x80431069; +constexpr int ORBIS_HTTP_ERROR_UNKNOWN_METHOD = 0x8043106b; +constexpr int ORBIS_HTTP_ERROR_READ_BY_HEAD_METHOD = 0x8043106f; +constexpr int ORBIS_HTTP_ERROR_NOT_IN_COM = 0x80431070; +constexpr int ORBIS_HTTP_ERROR_NO_CONTENT_LENGTH = 0x80431071; +constexpr int ORBIS_HTTP_ERROR_CHUNK_ENC = 0x80431072; +constexpr int ORBIS_HTTP_ERROR_TOO_LARGE_RESPONSE_HEADER = 0x80431073; +constexpr int ORBIS_HTTP_ERROR_SSL = 0x80431075; +constexpr int ORBIS_HTTP_ERROR_INSUFFICIENT_STACKSIZE = 0x80431076; +constexpr int ORBIS_HTTP_ERROR_ABORTED = 0x80431080; +constexpr int ORBIS_HTTP_ERROR_UNKNOWN = 0x80431081; +constexpr int ORBIS_HTTP_ERROR_EAGAIN = 0x80431082; +constexpr int ORBIS_HTTP_ERROR_PROXY = 0x80431084; +constexpr int ORBIS_HTTP_ERROR_BROKEN = 0x80431085; + +constexpr int ORBIS_HTTP_ERROR_PARSE_HTTP_NOT_FOUND = 0x80432025; +constexpr int ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE = 0x80432060; +constexpr int ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_VALUE = 0x804321fe; + +constexpr int ORBIS_HTTP_ERROR_RESOLVER_EPACKET = 0x80436001; +constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENODNS = 0x80436002; +constexpr int ORBIS_HTTP_ERROR_RESOLVER_ETIMEDOUT = 0x80436003; +constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENOSUPPORT = 0x80436004; +constexpr int ORBIS_HTTP_ERROR_RESOLVER_EFORMAT = 0x80436005; +constexpr int ORBIS_HTTP_ERROR_RESOLVER_ESERVERFAILURE = 0x80436006; +constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENOHOST = 0x80436007; +constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENOTIMPLEMENTED = 0x80436008; +constexpr int ORBIS_HTTP_ERROR_RESOLVER_ESERVERREFUSED = 0x80436009; +constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENORECORD = 0x8043600a; + +constexpr int ORBIS_HTTPS_ERROR_CERT = 0x80435060; +constexpr int ORBIS_HTTPS_ERROR_HANDSHAKE = 0x80435061; +constexpr int ORBIS_HTTPS_ERROR_IO = 0x80435062; +constexpr int ORBIS_HTTPS_ERROR_INTERNAL = 0x80435063; +constexpr int ORBIS_HTTPS_ERROR_PROXY = 0x80435064; + +constexpr int ORBIS_HTTPS_ERROR_SSL_INTERNAL = 0x01; +constexpr int ORBIS_HTTPS_ERROR_SSL_INVALID_CERT = 0x02; +constexpr int ORBIS_HTTPS_ERROR_SSL_CN_CHECK = 0x04; +constexpr int ORBIS_HTTPS_ERROR_SSL_NOT_AFTER_CHECK = 0x08; +constexpr int ORBIS_HTTPS_ERROR_SSL_NOT_BEFORE_CHECK = 0x10; +constexpr int ORBIS_HTTPS_ERROR_SSL_UNKNOWN_CA = 0x20; From c59d5eef45634c08ec1999b8fdb1cc6b6cca3566 Mon Sep 17 00:00:00 2001 From: baggins183 Date: Sun, 2 Mar 2025 19:17:11 -0800 Subject: [PATCH 012/194] Specialize vertex attributes on dst_sel (#2580) * Specialize vertex attributes on dst_sel * compare vs attrib specs by default, ignore NumberFmt when vertex input dynamic state is supported * specialize data_format when attribute uses step rates * use num_components in data fmt instead of fmt itself --- src/shader_recompiler/specialization.h | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/shader_recompiler/specialization.h b/src/shader_recompiler/specialization.h index 1c3bfc60a..e40309aaf 100644 --- a/src/shader_recompiler/specialization.h +++ b/src/shader_recompiler/specialization.h @@ -13,7 +13,9 @@ namespace Shader { struct VsAttribSpecialization { + s32 num_components{}; AmdGpu::NumberClass num_class{}; + AmdGpu::CompMapping dst_select{}; auto operator<=>(const VsAttribSpecialization&) const = default; }; @@ -89,12 +91,17 @@ struct StageSpecialization { Backend::Bindings start_) : info{&info_}, runtime_info{runtime_info_}, start{start_} { fetch_shader_data = Gcn::ParseFetchShader(info_); - if (info_.stage == Stage::Vertex && fetch_shader_data && - !profile_.support_legacy_vertex_attributes) { + if (info_.stage == Stage::Vertex && fetch_shader_data) { // Specialize shader on VS input number types to follow spec. ForEachSharp(vs_attribs, fetch_shader_data->attributes, - [](auto& spec, const auto& desc, AmdGpu::Buffer sharp) { - spec.num_class = AmdGpu::GetNumberClass(sharp.GetNumberFmt()); + [&profile_](auto& spec, const auto& desc, AmdGpu::Buffer sharp) { + spec.num_components = desc.UsesStepRates() + ? AmdGpu::NumComponents(sharp.GetDataFmt()) + : 0; + spec.num_class = profile_.support_legacy_vertex_attributes + ? AmdGpu::NumberClass{} + : AmdGpu::GetNumberClass(sharp.GetNumberFmt()); + spec.dst_select = sharp.DstSelect(); }); } u32 binding{}; From 517d7f04c660c9b0562a01430cd1759109397667 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Mon, 3 Mar 2025 04:29:39 -0300 Subject: [PATCH 013/194] Grammatical error: thophy to trophy :) (#2585) * Update settings_dialog.cpp * Update en_US.ts --- src/qt_gui/settings_dialog.cpp | 2 +- src/qt_gui/translations/en_US.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index a3890b548..8bd72a237 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -618,7 +618,7 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { //User if (elementName == "OpenCustomTrophyLocationButton") { - text = tr("Open the custom trophy images/sounds folder:\\nYou can add custom images to the trophies and an audio.\\nAdd the files to custom_trophy with the following names:\\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png\\nNote: The sound will only work in QT versions."); + text = tr("Open the custom trophy images/sounds folder:\\nYou can add custom images to the trophies and an audio.\\nAdd the files to custom_trophy with the following names:\\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\\nNote: The sound will only work in QT versions."); } // Input diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 29ce27f07..df4abdbf0 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never From c2adaf41c0c5e792796dbe2b5c63990f47913bc7 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:52:13 +0800 Subject: [PATCH 014/194] Qt: Add Initial KBM remapping GUI (#2544) * Initial KBM remapping GUI * Added Mousewheel mapping * Make window wider so for mousewheel + modifier string * Fix alt + mousewheel vertical being changed to horizontal for qwidgets --------- Co-authored-by: rainmakerv2 <30595646+jpau02@users.noreply.github.com> --- CMakeLists.txt | 3 + REUSE.toml | 1 + src/images/KBM.png | Bin 0 -> 241859 bytes src/qt_gui/kbm_gui.cpp | 1047 ++++++++++++++++++++++ src/qt_gui/kbm_gui.h | 56 ++ src/qt_gui/kbm_gui.ui | 1708 ++++++++++++++++++++++++++++++++++++ src/qt_gui/main_window.cpp | 6 +- src/shadps4.qrc | 1 + 8 files changed, 2818 insertions(+), 4 deletions(-) create mode 100644 src/images/KBM.png create mode 100644 src/qt_gui/kbm_gui.cpp create mode 100644 src/qt_gui/kbm_gui.h create mode 100644 src/qt_gui/kbm_gui.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 88d874af0..b8d82a11c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -917,6 +917,9 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/control_settings.cpp src/qt_gui/control_settings.h src/qt_gui/control_settings.ui + src/qt_gui/kbm_gui.cpp + src/qt_gui/kbm_gui.h + src/qt_gui/kbm_gui.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 d8f31c2f2..5cf7b01bf 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -41,6 +41,7 @@ path = [ "src/images/grid_icon.png", "src/images/keyboard_icon.png", "src/images/iconsize_icon.png", + "src/images/KBM.png", "src/images/ko-fi.png", "src/images/list_icon.png", "src/images/list_mode_icon.png", diff --git a/src/images/KBM.png b/src/images/KBM.png new file mode 100644 index 0000000000000000000000000000000000000000..37f52d549da567eb88eae45a60b174def40691b2 GIT binary patch literal 241859 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 +#include +#include +#include +#include +#include + +#include "common/path_util.h" +#include "kbm_config_dialog.h" +#include "kbm_gui.h" +#include "kbm_help_dialog.h" +#include "ui_kbm_gui.h" + +HelpDialog* HelpWindow; + +KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* parent) + : QDialog(parent), m_game_info(game_info_get), ui(new Ui::KBMSettings) { + + ui->setupUi(this); + ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); + ui->TextEditorButton->setFocus(); + this->setFocusPolicy(Qt::StrongFocus); + + ui->MouseJoystickBox->addItem("none"); + ui->MouseJoystickBox->addItem("right"); + ui->MouseJoystickBox->addItem("left"); + + 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)); + } + + ButtonsList = { + ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton, + ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button, + ui->L3Button, ui->R3Button, ui->TouchpadButton, ui->OptionsButton, + ui->TouchpadButton, ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, + ui->DpadRightButton, ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, + ui->LStickRightButton, ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, + ui->RStickRightButton, ui->LHalfButton, ui->RHalfButton}; + + ButtonConnects(); + SetUIValuestoMappings("default"); + installEventFilter(this); + + ui->ProfileComboBox->setCurrentText("Common Config"); + ui->TitleLabel->setText("Common Config"); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) { + if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { + if (HelpWindowOpen) { + HelpWindow->close(); + HelpWindowOpen = false; + } + SaveKBMConfig(true); + } else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { + SetDefault(); + } else if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) { + SaveKBMConfig(false); + } + }); + + connect(ui->HelpButton, &QPushButton::clicked, this, &KBMSettings::onHelpClicked); + connect(ui->TextEditorButton, &QPushButton::clicked, this, [this]() { + auto kbmWindow = new EditorDialog(this); + kbmWindow->exec(); + }); + + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] { + QWidget::close(); + if (HelpWindowOpen) { + HelpWindow->close(); + HelpWindowOpen = false; + } + }); + + connect(ui->ProfileComboBox, &QComboBox::currentTextChanged, this, [this] { + GetGameTitle(); + std::string config_id = (ui->ProfileComboBox->currentText() == "Common Config") + ? "default" + : ui->ProfileComboBox->currentText().toStdString(); + SetUIValuestoMappings(config_id); + }); + + connect(ui->CopyCommonButton, &QPushButton::clicked, this, [this] { + if (ui->ProfileComboBox->currentText() == "Common Config") { + QMessageBox::information(this, "Common Config Selected", + "This button copies mappings from the Common Config to the " + "currently selected profile, and cannot be used when the " + "currently selected profile is the Common Config."); + } else { + QMessageBox::StandardButton reply = + QMessageBox::question(this, "Copy values from Common Config", + "Do you want to overwrite existing mappings with the " + "mappings from the Common Config?", + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) { + SetUIValuestoMappings("default"); + } + } + }); + + connect(ui->DeadzoneOffsetSlider, &QSlider::valueChanged, this, [this](int value) { + QString DOSValue = QString::number(value / 100.0, 'f', 2); + QString DOSString = tr("Deadzone Offset (def 0.50): ") + DOSValue; + ui->DeadzoneOffsetLabel->setText(DOSString); + }); + + connect(ui->SpeedMultiplierSlider, &QSlider::valueChanged, this, [this](int value) { + QString SMSValue = QString::number(value / 10.0, 'f', 1); + QString SMSString = tr("Speed Multiplier (def 1.0): ") + SMSValue; + ui->SpeedMultiplierLabel->setText(SMSString); + }); + + connect(ui->SpeedOffsetSlider, &QSlider::valueChanged, this, [this](int value) { + QString SOSValue = QString::number(value / 1000.0, 'f', 3); + QString SOSString = tr("Speed Offset (def 0.125):") + " " + SOSValue; + ui->SpeedOffsetLabel->setText(SOSString); + }); +} + +void KBMSettings::ButtonConnects() { + connect(ui->CrossButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->CrossButton); }); + connect(ui->CircleButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->CircleButton); }); + connect(ui->TriangleButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->TriangleButton); }); + connect(ui->SquareButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->SquareButton); }); + + connect(ui->L1Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->L1Button); }); + connect(ui->L2Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->L2Button); }); + connect(ui->L3Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->L3Button); }); + connect(ui->R1Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->R1Button); }); + connect(ui->R2Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->R2Button); }); + connect(ui->R3Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->R3Button); }); + + connect(ui->TouchpadButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->TouchpadButton); }); + connect(ui->OptionsButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->OptionsButton); }); + + connect(ui->DpadUpButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->DpadUpButton); }); + connect(ui->DpadDownButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->DpadDownButton); }); + connect(ui->DpadLeftButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->DpadLeftButton); }); + connect(ui->DpadRightButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->DpadRightButton); }); + + connect(ui->LStickUpButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->LStickUpButton); }); + connect(ui->LStickDownButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->LStickDownButton); }); + connect(ui->LStickLeftButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->LStickLeftButton); }); + connect(ui->LStickRightButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->LStickRightButton); }); + + connect(ui->RStickUpButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->RStickUpButton); }); + connect(ui->RStickDownButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->RStickDownButton); }); + connect(ui->RStickLeftButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->RStickLeftButton); }); + connect(ui->RStickRightButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->RStickRightButton); }); + + connect(ui->LHalfButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->LHalfButton); }); + connect(ui->RHalfButton, &QPushButton::clicked, this, + [this]() { StartTimer(ui->RHalfButton); }); +} + +void KBMSettings::DisableMappingButtons() { + for (const auto& i : ButtonsList) { + i->setEnabled(false); + } +} + +void KBMSettings::EnableMappingButtons() { + for (const auto& i : ButtonsList) { + i->setEnabled(true); + } +} + +void KBMSettings::SaveKBMConfig(bool CloseOnSave) { + std::string output_string = "", input_string = ""; + std::vector lines, inputs; + + lines.push_back("#Feeling lost? Check out the Help section!"); + lines.push_back(""); + lines.push_back("#Keyboard bindings"); + lines.push_back(""); + + input_string = ui->CrossButton->text().toStdString(); + output_string = "cross"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->CircleButton->text().toStdString(); + output_string = "circle"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->TriangleButton->text().toStdString(); + output_string = "triangle"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->SquareButton->text().toStdString(); + output_string = "square"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + lines.push_back(""); + + input_string = ui->DpadUpButton->text().toStdString(); + output_string = "pad_up"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->DpadDownButton->text().toStdString(); + output_string = "pad_down"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->DpadLeftButton->text().toStdString(); + output_string = "pad_left"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->DpadRightButton->text().toStdString(); + output_string = "pad_right"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + lines.push_back(""); + + input_string = ui->L1Button->text().toStdString(); + output_string = "l1"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->R1Button->text().toStdString(); + output_string = "r1"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->L2Button->text().toStdString(); + output_string = "l2"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->R2Button->text().toStdString(); + output_string = "r2"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->L3Button->text().toStdString(); + output_string = "l3"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->R3Button->text().toStdString(); + output_string = "r3"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + lines.push_back(""); + + input_string = ui->OptionsButton->text().toStdString(); + output_string = "options"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->TouchpadButton->text().toStdString(); + output_string = "touchpad"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + lines.push_back(""); + + input_string = ui->LStickUpButton->text().toStdString(); + output_string = "axis_left_y_minus"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->LStickDownButton->text().toStdString(); + output_string = "axis_left_y_plus"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->LStickLeftButton->text().toStdString(); + output_string = "axis_left_x_minus"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->LStickRightButton->text().toStdString(); + output_string = "axis_left_x_plus"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + lines.push_back(""); + + input_string = ui->RStickUpButton->text().toStdString(); + output_string = "axis_right_y_minus"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->RStickDownButton->text().toStdString(); + output_string = "axis_right_y_plus"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->RStickLeftButton->text().toStdString(); + output_string = "axis_right_x_minus"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->RStickRightButton->text().toStdString(); + output_string = "axis_right_x_plus"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + lines.push_back(""); + + input_string = ui->MouseJoystickBox->currentText().toStdString(); + output_string = "mouse_to_joystick"; + if (input_string != "unmapped") + lines.push_back(output_string + " = " + input_string); + + input_string = ui->LHalfButton->text().toStdString(); + output_string = "leftjoystick_halfmode"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + input_string = ui->RHalfButton->text().toStdString(); + output_string = "rightjoystick_halfmode"; + lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") + inputs.push_back(input_string); + + std::string DOString = std::format("{:.2f}", (ui->DeadzoneOffsetSlider->value() / 100.f)); + std::string SMString = std::format("{:.1f}", (ui->SpeedMultiplierSlider->value() / 10.f)); + std::string SOString = std::format("{:.3f}", (ui->SpeedOffsetSlider->value() / 1000.f)); + input_string = DOString + ", " + SMString + ", " + SOString; + output_string = "mouse_movement_params"; + lines.push_back(output_string + " = " + input_string); + + lines.push_back(""); + + std::string config_id = (ui->ProfileComboBox->currentText() == "Common Config") + ? "default" + : ui->ProfileComboBox->currentText().toStdString(); + const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); + std::fstream file(config_file); + int lineCount = 0; + std::string line; + while (std::getline(file, line)) { + lineCount++; + + if (line.empty()) { + lines.push_back(line); + continue; + } + + std::size_t comment_pos = line.find('#'); + if (comment_pos != std::string::npos) { + if (!line.contains("Keyboard bindings") && !line.contains("Feeling lost") && + !line.contains("Alternatives for users")) + 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" || output_string == "override_controller_color") { + lines.push_back(line); + } + } + file.close(); + + // Prevent duplicate inputs for KBM as this breaks the engine + for (auto it = inputs.begin(); it != inputs.end(); ++it) { + if (std::find(it + 1, inputs.end(), *it) != inputs.end()) { + QMessageBox::information(this, "Unable to Save", + "Cannot bind any unique input more than once"); + return; + } + } + + 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 KBMSettings::SetDefault() { + ui->CrossButton->setText("kp2"); + ui->CircleButton->setText("kp6"); + ui->TriangleButton->setText("kp8"); + ui->SquareButton->setText("kp4"); + + ui->L1Button->setText("q"); + ui->L2Button->setText("e"); + ui->L3Button->setText("x"); + ui->R1Button->setText("u"); + ui->R2Button->setText("o"); + ui->R3Button->setText("m"); + + ui->TouchpadButton->setText("space"); + ui->OptionsButton->setText("enter"); + + ui->DpadUpButton->setText("up"); + ui->DpadDownButton->setText("down"); + ui->DpadLeftButton->setText("left"); + ui->DpadRightButton->setText("right"); + + ui->LStickUpButton->setText("w"); + ui->LStickDownButton->setText("s"); + ui->LStickLeftButton->setText("a"); + ui->LStickRightButton->setText("d"); + + ui->RStickUpButton->setText("i"); + ui->RStickDownButton->setText("k"); + ui->RStickLeftButton->setText("j"); + ui->RStickRightButton->setText("l"); + + ui->LHalfButton->setText("unmapped"); + ui->RHalfButton->setText("unmapped"); + + ui->MouseJoystickBox->setCurrentText("none"); + ui->DeadzoneOffsetSlider->setValue(50); + ui->SpeedMultiplierSlider->setValue(10); + ui->SpeedOffsetSlider->setValue(125); +} + +void KBMSettings::SetUIValuestoMappings(std::string config_id) { + const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); + std::ifstream file(config_file); + + int lineCount = 0; + std::string line = ""; + while (std::getline(file, line)) { + lineCount++; + + 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 - 1); + std::string input_string = line.substr(equal_pos + 2); + + if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) == + ControllerInputs.end()) { + + if (output_string == "cross") { + ui->CrossButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "circle") { + ui->CircleButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "square") { + ui->SquareButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "triangle") { + ui->TriangleButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "l1") { + ui->L1Button->setText(QString::fromStdString(input_string)); + } else if (output_string == "l2") { + ui->L2Button->setText(QString::fromStdString(input_string)); + } else if (output_string == "r1") { + ui->R1Button->setText(QString::fromStdString(input_string)); + } else if (output_string == "r2") { + ui->R2Button->setText(QString::fromStdString(input_string)); + } else if (output_string == "l3") { + ui->L3Button->setText(QString::fromStdString(input_string)); + } else if (output_string == "r3") { + ui->R3Button->setText(QString::fromStdString(input_string)); + } else if (output_string == "pad_up") { + ui->DpadUpButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "pad_down") { + ui->DpadDownButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "pad_left") { + ui->DpadLeftButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "pad_right") { + ui->DpadRightButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "options") { + ui->OptionsButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "touchpad") { + ui->TouchpadButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "axis_left_x_minus") { + ui->LStickLeftButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "axis_left_x_plus") { + ui->LStickRightButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "axis_left_y_minus") { + ui->LStickUpButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "axis_left_y_plus") { + ui->LStickDownButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "axis_right_x_minus") { + ui->RStickLeftButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "axis_right_x_plus") { + ui->RStickRightButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "axis_right_y_minus") { + ui->RStickUpButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "axis_right_y_plus") { + ui->RStickDownButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "mouse_to_joystick") { + ui->MouseJoystickBox->setCurrentText(QString::fromStdString(input_string)); + } else if (output_string == "leftjoystick_halfmode") { + ui->LHalfButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "rightjoystick_halfmode") { + ui->RHalfButton->setText(QString::fromStdString(input_string)); + } else if (output_string.contains("mouse_movement_params")) { + std::size_t comma_pos = line.find(','); + if (comma_pos != std::string::npos) { + std::string DOstring = line.substr(equal_pos + 1, comma_pos); + float DOffsetValue = std::stof(DOstring) * 100.0; + int DOffsetInt = int(DOffsetValue); + ui->DeadzoneOffsetSlider->setValue(DOffsetInt); + QString LabelValue = QString::number(DOffsetInt / 100.0, 'f', 2); + QString LabelString = tr("Deadzone Offset (def 0.50): ") + LabelValue; + ui->DeadzoneOffsetLabel->setText(LabelString); + + std::string SMSOstring = line.substr(comma_pos + 1); + std::size_t comma_pos2 = SMSOstring.find(','); + if (comma_pos2 != std::string::npos) { + std::string SMstring = SMSOstring.substr(0, comma_pos2); + float SpeedMultValue = std::stof(SMstring) * 10.0; + int SpeedMultInt = int(SpeedMultValue); + if (SpeedMultInt < 1) + SpeedMultInt = 1; + if (SpeedMultInt > 50) + SpeedMultInt = 50; + ui->SpeedMultiplierSlider->setValue(SpeedMultInt); + LabelValue = QString::number(SpeedMultInt / 10.0, 'f', 1); + LabelString = tr("Speed Multiplier (def 1.0): ") + LabelValue; + ui->SpeedMultiplierLabel->setText(LabelString); + + std::string SOstring = SMSOstring.substr(comma_pos2 + 1); + float SOffsetValue = std::stof(SOstring) * 1000.0; + int SOffsetInt = int(SOffsetValue); + ui->SpeedOffsetSlider->setValue(SOffsetInt); + LabelValue = QString::number(SOffsetInt / 1000.0, 'f', 3); + LabelString = tr("Speed Offset (def 0.125): ") + LabelValue; + ui->SpeedOffsetLabel->setText(LabelString); + } + } + } + } + } + file.close(); +} + +void KBMSettings::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)); + } + } + } +} + +void KBMSettings::onHelpClicked() { + if (!HelpWindowOpen) { + HelpWindow = new HelpDialog(&HelpWindowOpen, this); + HelpWindow->setWindowTitle("Help"); + HelpWindow->setAttribute(Qt::WA_DeleteOnClose); // Clean up on close + HelpWindow->show(); + HelpWindowOpen = true; + } else { + HelpWindow->close(); + HelpWindowOpen = false; + } +} + +void KBMSettings::StartTimer(QPushButton*& button) { + MappingTimer = 3; + EnableMapping = true; + MappingCompleted = false; + modifier = ""; + mapping = button->text(); + + DisableMappingButtons(); + button->setText("Press a key [" + QString::number(MappingTimer) + "]"); + timer = new QTimer(this); + MappingButton = button; + connect(timer, &QTimer::timeout, this, [this]() { CheckMapping(MappingButton); }); + timer->start(1000); +} + +void KBMSettings::CheckMapping(QPushButton*& button) { + MappingTimer -= 1; + button->setText("Press a key [" + QString::number(MappingTimer) + "]"); + + if (MappingCompleted) { + EnableMapping = false; + EnableMappingButtons(); + timer->stop(); + + if (mapping == "lshift" || mapping == "lalt" || mapping == "lctrl" || mapping == "lmeta" || + mapping == "lwin") { + modifier = ""; + } + + if (modifier != "") { + button->setText(modifier + ", " + mapping); + } else { + button->setText(mapping); + } + } + + if (MappingTimer <= 0) { + button->setText(mapping); + EnableMapping = false; + EnableMappingButtons(); + timer->stop(); + } +} + +void KBMSettings::SetMapping(QString input) { + mapping = input; + MappingCompleted = true; +} + +bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::Close) { + if (HelpWindowOpen) { + HelpWindow->close(); + HelpWindowOpen = false; + } + } + + if (EnableMapping) { + if (Qt::ShiftModifier & QApplication::keyboardModifiers()) { + modifier = "lshift"; + } else if (Qt::AltModifier & QApplication::keyboardModifiers()) { + modifier = "lalt"; + } else if (Qt::ControlModifier & QApplication::keyboardModifiers()) { + modifier = "lctrl"; + } else if (Qt::MetaModifier & QApplication::keyboardModifiers()) { +#ifdef _WIN32 + modifier = "lwin"; +#else + modifier = "lmeta"; +#endif + } + + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + + switch (keyEvent->key()) { + case Qt::Key_Space: + SetMapping("space"); + break; + case Qt::Key_Comma: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kpcomma"); + } else { + SetMapping("comma"); + } + break; + case Qt::Key_Period: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kpperiod"); + } else { + SetMapping("period"); + } + break; + case Qt::Key_Slash: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) + SetMapping("kpdivide"); + break; + case Qt::Key_Asterisk: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) + SetMapping("kpmultiply"); + break; + case Qt::Key_Question: + SetMapping("question"); + break; + case Qt::Key_Semicolon: + SetMapping("semicolon"); + break; + case Qt::Key_Minus: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kpminus"); + } else { + SetMapping("minus"); + } + break; + case Qt::Key_Plus: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kpplus"); + } else { + SetMapping("plus"); + } + break; + case Qt::Key_ParenLeft: + SetMapping("lparenthesis"); + break; + case Qt::Key_ParenRight: + SetMapping("rparenthesis"); + break; + case Qt::Key_BracketLeft: + SetMapping("lbracket"); + break; + case Qt::Key_BracketRight: + SetMapping("rbracket"); + break; + case Qt::Key_BraceLeft: + SetMapping("lbrace"); + break; + case Qt::Key_BraceRight: + SetMapping("rbrace"); + break; + case Qt::Key_Backslash: + SetMapping("backslash"); + break; + case Qt::Key_Tab: + SetMapping("tab"); + break; + case Qt::Key_Backspace: + SetMapping("backspace"); + break; + case Qt::Key_Return: + SetMapping("enter"); + break; + case Qt::Key_Enter: + SetMapping("kpenter"); + break; + case Qt::Key_Escape: + SetMapping("unmapped"); + break; + case Qt::Key_Shift: + SetMapping("lshift"); + break; + case Qt::Key_Alt: + SetMapping("lalt"); + break; + case Qt::Key_Control: + SetMapping("lctrl"); + break; + case Qt::Key_Meta: + activateWindow(); +#ifdef _WIN32 + SetMapping("lwin"); +#else + SetMapping("lmeta"); +#endif + case Qt::Key_1: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp1"); + } else { + SetMapping("1"); + } + break; + case Qt::Key_2: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp2"); + } else { + SetMapping("2"); + } + break; + case Qt::Key_3: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp3"); + } else { + SetMapping("3"); + } + break; + case Qt::Key_4: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp4"); + } else { + SetMapping("4"); + } + break; + case Qt::Key_5: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp5"); + } else { + SetMapping("5"); + } + break; + case Qt::Key_6: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp6"); + } else { + SetMapping("6"); + } + break; + case Qt::Key_7: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp7"); + } else { + SetMapping("7"); + } + break; + case Qt::Key_8: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp8"); + } else { + SetMapping("8"); + } + break; + case Qt::Key_9: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp9"); + } else { + SetMapping("9"); + } + break; + case Qt::Key_0: + if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { + SetMapping("kp0"); + } else { + SetMapping("0"); + } + break; + case Qt::Key_Up: + activateWindow(); + SetMapping("up"); + break; + case Qt::Key_Down: + SetMapping("down"); + break; + case Qt::Key_Left: + SetMapping("left"); + break; + case Qt::Key_Right: + SetMapping("right"); + break; + case Qt::Key_A: + SetMapping("a"); + break; + case Qt::Key_B: + SetMapping("b"); + break; + case Qt::Key_C: + SetMapping("c"); + break; + case Qt::Key_D: + SetMapping("d"); + break; + case Qt::Key_E: + SetMapping("e"); + break; + case Qt::Key_F: + SetMapping("f"); + break; + case Qt::Key_G: + SetMapping("g"); + break; + case Qt::Key_H: + SetMapping("h"); + break; + case Qt::Key_I: + SetMapping("i"); + break; + case Qt::Key_J: + SetMapping("j"); + break; + case Qt::Key_K: + SetMapping("k"); + break; + case Qt::Key_L: + SetMapping("l"); + break; + case Qt::Key_M: + SetMapping("m"); + break; + case Qt::Key_N: + SetMapping("n"); + break; + case Qt::Key_O: + SetMapping("o"); + break; + case Qt::Key_P: + SetMapping("p"); + break; + case Qt::Key_Q: + SetMapping("q"); + break; + case Qt::Key_R: + SetMapping("r"); + break; + case Qt::Key_S: + SetMapping("s"); + break; + case Qt::Key_T: + SetMapping("t"); + break; + case Qt::Key_U: + SetMapping("u"); + break; + case Qt::Key_V: + SetMapping("v"); + break; + case Qt::Key_W: + SetMapping("w"); + break; + case Qt::Key_X: + SetMapping("x"); + break; + case Qt::Key_Y: + SetMapping("Y"); + break; + case Qt::Key_Z: + SetMapping("z"); + break; + default: + break; + } + return true; + } + + if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent* mouseEvent = static_cast(event); + switch (mouseEvent->button()) { + case Qt::LeftButton: + SetMapping("leftbutton"); + break; + case Qt::RightButton: + SetMapping("rightbutton"); + break; + case Qt::MiddleButton: + SetMapping("middlebutton"); + break; + default: + break; + } + return true; + } + + const QList AxisList = { + ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton, + ui->RStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->RStickRightButton}; + + if (event->type() == QEvent::Wheel) { + QWheelEvent* wheelEvent = static_cast(event); + if (wheelEvent->angleDelta().y() > 5) { + if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { + SetMapping("mousewheelup"); + } else { + QMessageBox::information(this, "Cannot set mapping", + "Mousewheel cannot be mapped to stick outputs"); + } + } else if (wheelEvent->angleDelta().y() < -5) { + if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { + SetMapping("mousewheeldown"); + } else { + QMessageBox::information(this, "Cannot set mapping", + "Mousewheel cannot be mapped to stick outputs"); + } + } + if (wheelEvent->angleDelta().x() > 5) { + if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { + // QT changes scrolling to horizontal for all widgets with the alt modifier + if (Qt::AltModifier & QApplication::keyboardModifiers()) { + SetMapping("mousewheelup"); + } else { + SetMapping("mousewheelright"); + } + } else { + QMessageBox::information(this, "Cannot set mapping", + "Mousewheel cannot be mapped to stick outputs"); + } + } else if (wheelEvent->angleDelta().x() < -5) { + if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { + if (Qt::AltModifier & QApplication::keyboardModifiers()) { + SetMapping("mousewheeldown"); + } else { + SetMapping("mousewheelleft"); + } + } else { + QMessageBox::information(this, "Cannot set mapping", + "Mousewheel cannot be mapped to stick outputs"); + } + } + return true; + } + } + return QDialog::eventFilter(obj, event); +} + +KBMSettings::~KBMSettings() {} diff --git a/src/qt_gui/kbm_gui.h b/src/qt_gui/kbm_gui.h new file mode 100644 index 000000000..e63d7bd43 --- /dev/null +++ b/src/qt_gui/kbm_gui.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "game_info.h" + +namespace Ui { +class KBMSettings; +} + +class KBMSettings : public QDialog { + Q_OBJECT +public: + explicit KBMSettings(std::shared_ptr game_info_get, QWidget* parent = nullptr); + ~KBMSettings(); + +private Q_SLOTS: + void SaveKBMConfig(bool CloseOnSave); + void SetDefault(); + void CheckMapping(QPushButton*& button); + void StartTimer(QPushButton*& button); + void onHelpClicked(); + +private: + std::unique_ptr ui; + std::shared_ptr m_game_info; + + bool eventFilter(QObject* obj, QEvent* event) override; + void ButtonConnects(); + void SetUIValuestoMappings(std::string config_id); + void GetGameTitle(); + void DisableMappingButtons(); + void EnableMappingButtons(); + void SetMapping(QString input); + + bool EnableMapping = false; + bool MappingCompleted = false; + bool HelpWindowOpen = false; + QString mapping; + QString modifier; + int MappingTimer; + QTimer* timer; + QPushButton* MappingButton; + QList ButtonsList; + + 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"}; +}; diff --git a/src/qt_gui/kbm_gui.ui b/src/qt_gui/kbm_gui.ui new file mode 100644 index 000000000..fb8e4882b --- /dev/null +++ b/src/qt_gui/kbm_gui.ui @@ -0,0 +1,1708 @@ + + + + KBMSettings + + + Qt::WindowModality::WindowModal + + + + 0 + 0 + 1193 + 754 + + + + Qt::FocusPolicy::StrongFocus + + + Configure Controls + + + true + + + + + + Qt::FocusPolicy::NoFocus + + + true + + + + + 0 + 0 + 1173 + 704 + + + + + + 0 + 0 + 1171 + 703 + + + + + + + 5 + + + + + true + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + D-Pad + + + + 6 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 160 + 0 + + + + + 0 + 16777215 + + + + Up + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + Left + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + Right + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 160 + 0 + + + + + 124 + 16777215 + + + + Down + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Maximum + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Left Analog Halfmode + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + true + + + + hold to move left stick at half-speed + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Left Stick + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 16777215 + 2121 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 160 + 0 + + + + + 124 + 16777215 + + + + Up + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + Left + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 179 + 16777215 + + + + Right + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 160 + 0 + + + + + 124 + 21212 + + + + Down + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + + + + 0 + + + + + + 0 + 0 + + + + + 12 + true + + + + Config Selection + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + 9 + false + + + + Qt::FocusPolicy::NoFocus + + + + + + -1 + + + Common Config + + + + + + + + 10 + true + + + + Common Config + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + + + + + + + + + 0 + 0 + + + + + 9 + false + + + + Qt::FocusPolicy::NoFocus + + + Use per-game configs + + + + + + + + 9 + false + + + + Qt::FocusPolicy::NoFocus + + + Copy from Common Config + + + + + + + + + + + + 0 + + + + + + + L1 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 160 + 0 + + + + L2 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + true + + + + + + + + + + Qt::FocusPolicy::NoFocus + + + Text Editor + + + + + + + Qt::FocusPolicy::NoFocus + + + Help + + + + + + + + + + + + + + + 160 + 0 + + + + R1 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + R2 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + 0 + 200 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 420 + 200 + + + + :/images/KBM.png + + + true + + + Qt::AlignmentFlag::AlignBottom|Qt::AlignmentFlag::AlignHCenter + + + + + + + + + + 0 + + + QLayout::SizeConstraint::SetDefaultConstraint + + + + + + + + 160 + 0 + + + + L3 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + Touchpad Click + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + Mouse to Joystick + + + + + + Qt::FocusPolicy::NoFocus + + + + + + + + true + + + + *press F7 ingame to activate + + + true + + + + + + + + + + + + + + + 160 + 0 + + + + R3 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 145 + 0 + + + + Options + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + + + + + false + + + + Mouse Movement Parameters + + + + + + + + + false + + + + Deadzone Offset (def 0.50): 0.50 + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::NoFocus + + + 100 + + + 50 + + + Qt::Orientation::Horizontal + + + + + + + + + + + + false + + + + Speed Multiplier (def 1.0): 1.0 + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::NoFocus + + + 1 + + + 50 + + + 5 + + + 10 + + + Qt::Orientation::Horizontal + + + + + + + + + + + + false + + + + Speed Offset (def 0.125): 0.125 + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::NoFocus + + + 1000 + + + 100 + + + 125 + + + Qt::Orientation::Horizontal + + + + + + + + + + + + + + + + + true + false + + + + note: click Help Button/Special Keybindings for more information + + + + + + + + + 5 + + + + + + 0 + 0 + + + + Face Buttons + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 160 + 0 + + + + + 0 + 16777215 + + + + Triangle + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + Square + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + Circle + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 160 + 0 + + + + + 124 + 16777215 + + + + Cross + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Maximum + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Right Analog Halfmode + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + true + + + + hold to move right stick at half-speed + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Right Stick + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 160 + 0 + + + + + 124 + 1231321 + + + + Up + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + Left + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + Right + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 160 + 0 + + + + + 124 + 2121 + + + + Down + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + + + + + + + + + + + + + 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 d9fb45fac..bde32a52d 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -21,11 +21,10 @@ #include "core/loader.h" #include "game_install_dialog.h" #include "install_dir_select.h" +#include "kbm_gui.h" #include "main_window.h" #include "settings_dialog.h" -#include "kbm_config_dialog.h" - #include "video_core/renderer_vulkan/vk_instance.h" #ifdef ENABLE_DISCORD_RPC #include "common/discord_rpc_handler.h" @@ -348,14 +347,13 @@ void MainWindow::CreateConnects() { settingsDialog->exec(); }); - // this is the editor for kbm keybinds connect(ui->controllerButton, &QPushButton::clicked, this, [this]() { auto configWindow = new ControlSettings(m_game_info, this); configWindow->exec(); }); connect(ui->keyboardButton, &QPushButton::clicked, this, [this]() { - auto kbmWindow = new EditorDialog(this); + auto kbmWindow = new KBMSettings(m_game_info, this); kbmWindow->exec(); }); diff --git a/src/shadps4.qrc b/src/shadps4.qrc index 14b50f7a5..a1ff680ed 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -32,5 +32,6 @@ images/website.png images/ps4_controller.png images/keyboard_icon.png + images/KBM.png From a4b35f275cad0555bae8ed23a8e8615dc0a849d8 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Tue, 4 Mar 2025 18:25:42 +0800 Subject: [PATCH 015/194] Add global/common user folder for Windows (#2589) * Add global windows user folder * Add button for creating portable folder * Add notice about restarting after creating the portable folder --------- Co-authored-by: rainmakerv2 <30595646+jpau02@users.noreply.github.com> --- src/common/path_util.cpp | 6 +++ src/qt_gui/settings_dialog.cpp | 19 ++++++++ src/qt_gui/settings_dialog.ui | 84 +++++++++++++++++++++++++++------- 3 files changed, 92 insertions(+), 17 deletions(-) diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 6bc73ee43..702d0fabc 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -17,6 +17,8 @@ #ifdef _WIN32 // This is the maximum number of UTF-16 code units permissible in Windows file paths #define MAX_PATH 260 +#include +#include #else // This is the maximum number of UTF-8 code units permissible in all other OSes' file paths #define MAX_PATH 1024 @@ -106,6 +108,10 @@ static auto UserPaths = [] { } else { user_dir = std::filesystem::path(getenv("HOME")) / ".local" / "share" / "shadPS4"; } +#elif _WIN32 + TCHAR appdata[MAX_PATH] = {0}; + SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, appdata); + user_dir = std::filesystem::path(appdata) / "shadPS4"; #endif } diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 8bd72a237..69f5d3c8a 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "common/config.h" @@ -234,6 +235,21 @@ SettingsDialog::SettingsDialog(std::span physical_devices, Common::FS::GetUserPath(Common::FS::PathType::CustomTrophy)); QDesktopServices::openUrl(QUrl::fromLocalFile(userPath)); }); + + connect(ui->PortableUserButton, &QPushButton::clicked, this, []() { + QString userDir; + Common::FS::PathToQString(userDir, std::filesystem::current_path() / "user"); + if (std::filesystem::exists(std::filesystem::current_path() / "user")) { + QMessageBox::information(NULL, "Cannot create portable user folder", + userDir + " already exists"); + } else { + std::filesystem::copy(Common::FS::GetUserPath(Common::FS::PathType::UserDir), + std::filesystem::current_path() / "user", + std::filesystem::copy_options::recursive); + QMessageBox::information(NULL, "Portable user folder created", + userDir + " successfully created"); + } + }); } // Input TAB @@ -344,6 +360,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, ui->saveDataGroupBox->installEventFilter(this); ui->currentSaveDataPath->installEventFilter(this); ui->browseButton->installEventFilter(this); + ui->PortableUserFolderGroupBox->installEventFilter(this); // Debug ui->debugDump->installEventFilter(this); @@ -650,6 +667,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("Add:\\nAdd a folder to the list."); } else if (elementName == "removeFolderButton") { text = tr("Remove:\\nRemove a folder from the list."); + } else if (elementName == "PortableUserFolderGroupBox") { + text = tr("Portable user folder:\\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it."); } // Save Data diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 7db0afa59..5600a0db7 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -31,7 +31,7 @@ Settings - + :/images/shadps4.ico:/images/shadps4.ico @@ -59,7 +59,7 @@ - 0 + 5 @@ -74,7 +74,7 @@ 0 0 946 - 545 + 536 @@ -130,9 +130,6 @@ 9 - - Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop - @@ -455,7 +452,7 @@ 0 0 946 - 545 + 536 @@ -904,7 +901,7 @@ 0 0 946 - 545 + 536 @@ -1199,7 +1196,7 @@ 0 0 946 - 545 + 536 @@ -1306,14 +1303,14 @@ Top - + Bottom - + @@ -1335,8 +1332,7 @@ - - + @@ -1442,7 +1438,7 @@ 0 0 946 - 545 + 536 @@ -1726,7 +1722,7 @@ 0 0 946 - 545 + 536 @@ -1800,6 +1796,58 @@ + + + + Portable User Folder + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Create Portable User Folder from Common User Folder + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + @@ -1816,7 +1864,7 @@ 0 0 946 - 545 + 536 @@ -2068,6 +2116,8 @@ - + + + From 367f08c1b53e31c2688cc839b33e9a6bc6c14028 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 4 Mar 2025 12:39:25 +0200 Subject: [PATCH 016/194] [ci skip] Qt GUI: Update Translation. (#2590) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 175 +++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index df4abdbf0..2aa65e9d1 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -903,6 +903,169 @@ + + KBMSettings + + Configure Controls + + + + D-Pad + + + + Up + + + + unmapped + + + + Left + Left + + + Right + Right + + + Down + + + + Left Analog Halfmode + + + + hold to move left stick at half-speed + + + + Left Stick + + + + Config Selection + + + + Common Config + + + + Use per-game configs + + + + Copy from Common Config + + + + L1 + + + + L2 + + + + Text Editor + + + + Help + Help + + + R1 + + + + R2 + + + + L3 + + + + Touchpad Click + + + + Mouse to Joystick + + + + *press F7 ingame to activate + + + + R3 + + + + Options + + + + Mouse Movement Parameters + + + + note: click Help Button/Special Keybindings for more information + + + + Face Buttons + + + + Triangle + + + + Square + + + + Circle + + + + Cross + + + + Right Analog Halfmode + + + + hold to move right stick at half-speed + + + + Right Stick + + + + Deadzone Offset (def 0.50): + + + + Speed Multiplier (def 1.0): + + + + Speed Offset (def 0.125): + + + + Speed Offset (def 0.125): + + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + + + + Create Portable User Folder from Common User Folder + + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + TrophyViewer From e82c8d2f70996e0f2eb042c41e89fb634e305567 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 4 Mar 2025 12:39:43 +0200 Subject: [PATCH 017/194] New Crowdin updates (#2584) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Italian) * New translations en_us.ts (Russian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Russian) * New translations en_us.ts (Portuguese) --- src/qt_gui/translations/ar_SA.ts | 12 ++++++++-- src/qt_gui/translations/da_DK.ts | 12 ++++++++-- src/qt_gui/translations/de_DE.ts | 12 ++++++++-- src/qt_gui/translations/el_GR.ts | 12 ++++++++-- src/qt_gui/translations/es_ES.ts | 12 ++++++++-- src/qt_gui/translations/fa_IR.ts | 12 ++++++++-- src/qt_gui/translations/fi_FI.ts | 12 ++++++++-- src/qt_gui/translations/fr_FR.ts | 12 ++++++++-- src/qt_gui/translations/hu_HU.ts | 12 ++++++++-- src/qt_gui/translations/id_ID.ts | 12 ++++++++-- src/qt_gui/translations/it_IT.ts | 12 ++++++++-- src/qt_gui/translations/ja_JP.ts | 12 ++++++++-- src/qt_gui/translations/ko_KR.ts | 12 ++++++++-- src/qt_gui/translations/lt_LT.ts | 12 ++++++++-- src/qt_gui/translations/nb_NO.ts | 12 ++++++++-- src/qt_gui/translations/nl_NL.ts | 12 ++++++++-- src/qt_gui/translations/pl_PL.ts | 12 ++++++++-- src/qt_gui/translations/pt_BR.ts | 18 ++++++++++---- src/qt_gui/translations/pt_PT.ts | 40 +++++++++++++++++++------------- src/qt_gui/translations/ro_RO.ts | 12 ++++++++-- src/qt_gui/translations/ru_RU.ts | 12 ++++++++-- src/qt_gui/translations/sq_AL.ts | 12 ++++++++-- src/qt_gui/translations/sv_SE.ts | 14 ++++++++--- src/qt_gui/translations/tr_TR.ts | 12 ++++++++-- src/qt_gui/translations/uk_UA.ts | 12 ++++++++-- src/qt_gui/translations/vi_VN.ts | 12 ++++++++-- src/qt_gui/translations/zh_CN.ts | 12 ++++++++-- src/qt_gui/translations/zh_TW.ts | 12 ++++++++-- 28 files changed, 298 insertions(+), 74 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 493a33f82..44d68dcb4 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 6e8c8a6ff..a9871f21d 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 64b28c179..97eb99d0d 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -1632,8 +1632,8 @@ Aktualisiere Kompatibilitätsdatenbank:\nAktualisiere sofort die Kompatibilitätsdatenbank. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index 3d0b89bb8..c409d558d 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index a9db8860e..c6caed584 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index da9424a3a..17a1ecfad 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -1632,8 +1632,8 @@ به‌روزرسانی پایگاه داده سازگاری:\nپایگاه داده سازگاری را بلافاصله به‌روزرسانی می‌کند. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index f20461015..74d9bb518 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -1632,8 +1632,8 @@ Päivitä Yhteensopivuustietokanta:\nPäivitää yhteensopivuustietokannan heti. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 41a588aee..ecbe88b1e 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -1632,8 +1632,8 @@ Mettre à jour la compatibilité au démarrage:\nMet à jour immédiatement la base de données de compatibilité. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 35ad71d59..aa123ccf2 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index 25835f925..bd8f6b11d 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index a3f06591d..032234300 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1632,8 +1632,8 @@ Aggiorna Database Compatibilità:\nAggiorna immediatamente il database di compatibilità. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Apri la cartella personalizzata delle immagini/suoni dei trofei:\nPuoi aggiungere immagini e audio personalizzato ai trofei.\nAggiungi i file in custom_trophy con i seguenti nomi:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Destra + + Top + In alto + + + Bottom + In basso + Notification Duration Durata Notifica diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index f9f4d6370..7350057dd 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -1632,8 +1632,8 @@ 互換性データベースを更新する:\n今すぐ互換性データベースを更新します。 - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 3e6c26dde..6bc8ddfb1 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index c9be4c048..c384da08b 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index d163396ee..89400aaa4 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1632,8 +1632,8 @@ Oppdater kompatibilitets-database:\nOppdater kompatibilitets-databasen nå. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Åpne mappa med tilpassede bilder og lyder for trofé:\nDu kan legge til tilpassede bilder til trofeene og en lyd.\nLegg filene til custom_trophy med følgende navn:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Åpne mappa med tilpassede bilder og lyder for trofé:\nDu kan legge til tilpassede bilder til trofeer med en lyd.\nLegg filene til custom_trophy med følgende navn:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nMerk: Avspilling av lyden vil bare fungere med Qt versjonen. Never @@ -1839,6 +1839,14 @@ Right Høyre + + Top + Øverst + + + Bottom + Nederst + Notification Duration Varslingsvarighet diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index a61ca1adf..924f59ef7 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 64134d055..8a8ac83f7 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -1632,8 +1632,8 @@ Zaktualizuj bazę danych zgodności:\nNatychmiast zaktualizuj bazę danych zgodności. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Otwórz niestandardowy folder obrazów/dźwięków:\nMożesz dodać własne obrazy do trofeów i dźwięku.\nDodaj pliki do custom_trophy o następujących nazwach:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Z prawej + + Top + Z góry + + + Bottom + Z dołu + Notification Duration Czas trwania powiadomienia diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 103f0b66e..5fe1774ba 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -733,7 +733,7 @@ Open Log Folder - Abrir Pasta de Registros + Abrir Pasta de Log Copy info... @@ -1517,7 +1517,7 @@ Update Compatibility Database On Startup - Atualizar Base de Dados de Compatibilidade ao Inicializar + Atualizar Banco de Dados de Compatibilidade ao Inicializar Game Compatibility @@ -1589,7 +1589,7 @@ Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. - Filtro de Registro:\nFiltra o registro para exibir apenas informações específicas.\nExemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNíveis: Trace, Debug, Info, Warning, Error, Critical - nesta ordem, um nível específico silencia todos os níveis anteriores na lista e registra todos os níveis após ele. + Filtro do Registro:\nFiltra o registro para exibir apenas informações específicas.\nExemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNíveis: Trace, Debug, Info, Warning, Error, Critical - nesta ordem, um nível específico silencia todos os níveis anteriores na lista e registra todos os níveis após este. 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. @@ -1632,8 +1632,8 @@ Atualizar Lista de Compatibilidade:\nAtualiza imediatamente o banco de dados de compatibilidade. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Abrir a pasta de imagens/sons de troféus personalizados:\nVocê pode adicionar imagens e sons personalizados aos troféus.\nAdicione os arquivos em custom_trophy com os seguintes nomes:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Abrir a pasta de imagens/sons de troféus personalizados:\nVocê pode adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os arquivos na pasta custom_trophy com os seguintes nomes:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas em versões Qt. Never @@ -1839,6 +1839,14 @@ Right Direita + + Top + Acima + + + Bottom + Abaixo + Notification Duration Duração da Notificação diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index 8db33ca5b..b279d1c5b 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -777,7 +777,7 @@ Delete Trophy - Delete Trophy + Eliminar Troféu Compatibility... @@ -857,7 +857,7 @@ No log file found for this game! - No log file found for this game! + Não foi encontrado nenhum ficheiro de registo para este jogo! Failed to convert icon. @@ -869,7 +869,7 @@ This game has no saved trophies to delete! - This game has no saved trophies to delete! + Este jogo não tem troféus guardados para eliminar! Save Data @@ -877,7 +877,7 @@ Trophy - Trophy + Troféus SFO Viewer for @@ -1329,7 +1329,7 @@ Open the custom trophy images/sounds folder - Open the custom trophy images/sounds folder + Abrir a pasta de imagens/sons de troféus personalizados Logger @@ -1497,7 +1497,7 @@ Disable Trophy Notification - Disable Trophy Notification + Desativar Notificações de Troféus Background Image @@ -1632,8 +1632,8 @@ Atualizar Base de Dados de Compatibilidade:\nAtualiza imediatamente a base de dados de compatibilidade. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Abrir a pasta de imagens/sons de troféus personalizados:\nPoderá adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os ficheiros na pasta custom_trophy com os seguintes nomes:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas nas versões Qt. Never @@ -1829,19 +1829,27 @@ Trophy Notification Position - Trophy Notification Position + Posição da Notificação do Troféu Left - Left + Esquerda Right - Right + Direita + + + Top + Acima + + + Bottom + Abaixo Notification Duration - Notification Duration + Duração da Notificação @@ -1852,19 +1860,19 @@ Progress - Progress + Progresso Show Earned Trophies - Show Earned Trophies + Mostrar Troféus Conquistados Show Not Earned Trophies - Show Not Earned Trophies + Mostrar Troféus Não Conquistados Show Hidden Trophies - Show Hidden Trophies + Mostrar Troféus Ocultos diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index be1613c57..e57b23150 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 313233c92..c454ead29 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1632,8 +1632,8 @@ Обновить базу совместимости:\nНемедленно обновить базу данных совместимости. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Открыть пользовательскую папку с трофеями изображений/звуков:\nВы можете добавить пользовательские изображения к трофеям и аудио.\nДобавьте файлы в custom_trophy со следующими именами:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Открыть папку с пользовательскими изображениями/звуками трофеев:\nВы можете добавить пользовательские изображения к трофеям и аудио.\nДобавьте файлы в custom_trophy со следующими именами:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nПримечание: звук будет работать только в QT-версии. Never @@ -1839,6 +1839,14 @@ Right Вправо + + Top + Сверху + + + Bottom + Снизу + Notification Duration Продолжительность уведомления diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 0783fb1a7..0b75e767d 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1632,8 +1632,8 @@ Përditëso bazën e të dhënave të përputhshmërisë:\nPërditëso menjëherë bazën e të dhënave të përputhshmërisë. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Hap dosjen e imazheve/tingujve të trofeve të personalizuar:\nMund të shtosh imazhe të personalizuara për trofetë dhe një skedar audio.\nShto skedarët në dosjen custom_trophy me emrat që vijojnë:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Hap dosjen e imazheve/tingujve të trofeve të personalizuar:\nMund të shtosh imazhe të personalizuara për trofetë dhe një audio.\nShto skedarët në dosjen custom_trophy me emrat që vijojnë:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nShënim: Tingulli do të punojë vetëm në versionet QT. Never @@ -1839,6 +1839,14 @@ Right Djathtas + + Top + Sipër + + + Bottom + Poshtë + Notification Duration Kohëzgjatja e Njoftimit diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index 6cbd33f17..f317b1974 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -1329,7 +1329,7 @@ Open the custom trophy images/sounds folder - Öppna mapp för anpassade trofébilder/ljud + Öppna mappen för anpassade trofébilder/ljud Logger @@ -1632,8 +1632,8 @@ Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Öppna mappen med anpassade trofébilder/ljud:\nDu kan lägga till anpassade bilder till troféerna och ett ljud.\nLägg till filerna i custom_trophy med följande namn:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Öppna mappen för anpassade trofébilder/ljud:\nDu kan lägga till anpassade bilder till troféerna och ett ljud.\nLägg till filerna i custom_trophy med följande namn:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservera: Ljudet fungerar endast i QT-versioner. Never @@ -1839,6 +1839,14 @@ Right Höger + + Top + Överst + + + Bottom + Nederst + Notification Duration Varaktighet för avisering diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index cd4f1cadd..7b979e653 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -1632,8 +1632,8 @@ Uyumluluk Veritabanını Güncelle:\nUyumluluk veri tabanını hemen güncelleyin. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Sağ + + Top + Top + + + Bottom + Bottom + Notification Duration Bildirim Süresi diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 63f8b012f..222cb9e1a 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -1632,8 +1632,8 @@ Оновити данні ігрової сумістності:\nНегайно оновить базу даних ігрової сумісності. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index fe9ad915f..5ab469b92 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index a9cf7ca19..dbfe61c16 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1632,8 +1632,8 @@ 更新兼容性数据库:\n立即更新兼容性数据库。 - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - 打开自定义奖杯图像/声音文件夹:\n您可以自定义奖杯图像和声音。\n将文件添加到 custom_trophy 文件夹中,文件名如下:\nthophy.mp3、bronze.png、gold.png、platinum.png、silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right 右边 + + Top + Top + + + Bottom + Bottom + Notification Duration 通知显示持续时间 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index bd3ef8f0b..cb78b7a64 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -1632,8 +1632,8 @@ Update Compatibility Database:\nImmediately update the compatibility database. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\nthophy.mp3, bronze.png, gold.png, platinum.png, silver.png + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Never @@ -1839,6 +1839,14 @@ Right Right + + Top + Top + + + Bottom + Bottom + Notification Duration Notification Duration From 7bef4a5c70a9af9128444dda0a2c9ed8f00deb7e Mon Sep 17 00:00:00 2001 From: Paris Oplopoios Date: Tue, 4 Mar 2025 12:40:21 +0200 Subject: [PATCH 018/194] Allow our BMI1 emulation to work on non-macOS CPUs - also emulate TZCNT (#2526) * Allow our BMI1 emulation to work on non-macOS CPUs * Add TZCNT * Some changes * Subtract and add to rsp --- src/core/cpu_patches.cpp | 73 +++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index 21acf1a7b..f109f0453 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -202,21 +202,39 @@ static void RestoreStack(Xbyak::CodeGenerator& c) { /// Switches to the patch stack, saves registers, and restores the original stack. static void SaveRegisters(Xbyak::CodeGenerator& c, const std::initializer_list regs) { + // Uses a more robust solution for saving registers on MacOS to avoid potential stack corruption + // if games decide to not follow the ABI and use the red zone. +#ifdef __APPLE__ SaveStack(c); +#else + c.lea(rsp, ptr[rsp - 128]); // red zone +#endif for (const auto& reg : regs) { c.push(reg.cvt64()); } +#ifdef __APPLE__ RestoreStack(c); +#else + c.lea(rsp, ptr[rsp + 128]); +#endif } /// Switches to the patch stack, restores registers, and restores the original stack. static void RestoreRegisters(Xbyak::CodeGenerator& c, const std::initializer_list regs) { +#ifdef __APPLE__ SaveStack(c); +#else + c.lea(rsp, ptr[rsp - 128]); // red zone +#endif for (const auto& reg : regs) { c.pop(reg.cvt64()); } +#ifdef __APPLE__ RestoreStack(c); +#else + c.lea(rsp, ptr[rsp + 128]); +#endif } /// Switches to the patch stack and stores all registers. @@ -257,12 +275,11 @@ static void RestoreContext(Xbyak::CodeGenerator& c, const Xbyak::Operand& dst, RestoreStack(c); } -#ifdef __APPLE__ - static void GenerateANDN(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src1 = ZydisToXbyakRegisterOperand(operands[1]); const auto src2 = ZydisToXbyakOperand(operands[2]); + ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "ANDN overwriting the stack pointer"); // Check if src2 is a memory operand or a register different to dst. // In those cases, we don't need to use a temporary register and are free to modify dst. @@ -301,6 +318,7 @@ static void GenerateBEXTR(const ZydisDecodedOperand* operands, Xbyak::CodeGenera const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src = ZydisToXbyakOperand(operands[1]); const auto start_len = ZydisToXbyakRegisterOperand(operands[2]); + ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "BEXTR overwriting the stack pointer"); const Xbyak::Reg32e shift(Xbyak::Operand::RCX, static_cast(start_len.getBit())); const auto scratch1 = @@ -338,6 +356,7 @@ static void GenerateBEXTR(const ZydisDecodedOperand* operands, Xbyak::CodeGenera static void GenerateBLSI(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src = ZydisToXbyakOperand(operands[1]); + ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "BLSI overwriting the stack pointer"); const auto scratch = AllocateScratchRegister({&dst, src.get()}, dst.getBit()); @@ -367,6 +386,7 @@ static void GenerateBLSI(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat static void GenerateBLSMSK(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src = ZydisToXbyakOperand(operands[1]); + ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "BLSMSK overwriting the stack pointer"); const auto scratch = AllocateScratchRegister({&dst, src.get()}, dst.getBit()); @@ -395,9 +415,37 @@ static void GenerateBLSMSK(const ZydisDecodedOperand* operands, Xbyak::CodeGener RestoreRegisters(c, {scratch}); } +static void GenerateTZCNT(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { + const auto dst = ZydisToXbyakRegisterOperand(operands[0]); + const auto src = ZydisToXbyakOperand(operands[1]); + ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "TZCNT overwriting the stack pointer"); + + Xbyak::Label src_zero, end; + + c.cmp(*src, 0); + c.je(src_zero); + + // If src is not zero, functions like a BSF, but also clears the CF + c.bsf(dst, *src); + c.clc(); + c.jmp(end); + + c.L(src_zero); + c.mov(dst, operands[0].size); + // Since dst is not zero, also set ZF to zero. Testing dst with itself when we know + // it isn't zero is a good way to do this. + // Use cvt32 to avoid REX/Operand size prefixes. + c.test(dst.cvt32(), dst.cvt32()); + // When source is zero, TZCNT also sets CF. + c.stc(); + + c.L(end); +} + static void GenerateBLSR(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src = ZydisToXbyakOperand(operands[1]); + ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "BLSR overwriting the stack pointer"); const auto scratch = AllocateScratchRegister({&dst, src.get()}, dst.getBit()); @@ -426,6 +474,8 @@ static void GenerateBLSR(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat RestoreRegisters(c, {scratch}); } +#ifdef __APPLE__ + static __attribute__((sysv_abi)) void PerformVCVTPH2PS(float* out, const half_float::half* in, const u32 count) { for (u32 i = 0; i < count; i++) { @@ -616,6 +666,11 @@ static bool FilterNoSSE4a(const ZydisDecodedOperand*) { return !cpu.has(Cpu::tSSE4a); } +static bool FilterNoBMI1(const ZydisDecodedOperand*) { + Cpu cpu; + return !cpu.has(Cpu::tBMI1); +} + static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE; @@ -897,14 +952,16 @@ static const std::unordered_map Patches = { {ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}}, {ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}}, + // BMI1 + {ZYDIS_MNEMONIC_ANDN, {FilterNoBMI1, GenerateANDN, true}}, + {ZYDIS_MNEMONIC_BEXTR, {FilterNoBMI1, GenerateBEXTR, true}}, + {ZYDIS_MNEMONIC_BLSI, {FilterNoBMI1, GenerateBLSI, true}}, + {ZYDIS_MNEMONIC_BLSMSK, {FilterNoBMI1, GenerateBLSMSK, true}}, + {ZYDIS_MNEMONIC_BLSR, {FilterNoBMI1, GenerateBLSR, true}}, + {ZYDIS_MNEMONIC_TZCNT, {FilterNoBMI1, GenerateTZCNT, true}}, + #ifdef __APPLE__ // Patches for instruction sets not supported by Rosetta 2. - // BMI1 - {ZYDIS_MNEMONIC_ANDN, {FilterRosetta2Only, GenerateANDN, true}}, - {ZYDIS_MNEMONIC_BEXTR, {FilterRosetta2Only, GenerateBEXTR, true}}, - {ZYDIS_MNEMONIC_BLSI, {FilterRosetta2Only, GenerateBLSI, true}}, - {ZYDIS_MNEMONIC_BLSMSK, {FilterRosetta2Only, GenerateBLSMSK, true}}, - {ZYDIS_MNEMONIC_BLSR, {FilterRosetta2Only, GenerateBLSR, true}}, // F16C {ZYDIS_MNEMONIC_VCVTPH2PS, {FilterRosetta2Only, GenerateVCVTPH2PS, true}}, {ZYDIS_MNEMONIC_VCVTPS2PH, {FilterRosetta2Only, GenerateVCVTPS2PH, true}}, From 3a9633f5533056853bfe6460db139bd52d950b83 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 4 Mar 2025 03:25:51 -0800 Subject: [PATCH 019/194] cpu_patches: Simplify and remove some restrictions on macOS. (#2591) --- src/core/cpu_patches.cpp | 50 +++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index f109f0453..a9f6c67a8 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -180,22 +180,36 @@ static void RestoreStack(Xbyak::CodeGenerator& c) { c.mov(rsp, qword[reinterpret_cast(stack_pointer_slot * sizeof(void*))]); } +/// Validates that the dst register is supported given the SaveStack/RestoreStack implementation. +static void ValidateDst(const Xbyak::Reg& dst) { + // No restrictions. +} + #else -// These utilities are not implemented as we can't save anything to thread local storage without -// temporary registers. void InitializeThreadPatchStack() { // No-op } +// NOTE: Since stack pointer here is subtracted through safe zone and not saved anywhere, +// it must not be modified during the instruction. Otherwise, we will not be able to find +// and load registers back from where they were saved. Thus, a limitation is placed on +// instructions, that they must not use the stack pointer register as a destination. + /// Saves the stack pointer to thread local storage and loads the patch stack. static void SaveStack(Xbyak::CodeGenerator& c) { - UNIMPLEMENTED(); + c.lea(rsp, ptr[rsp - 128]); // red zone } /// Restores the stack pointer from thread local storage. static void RestoreStack(Xbyak::CodeGenerator& c) { - UNIMPLEMENTED(); + c.lea(rsp, ptr[rsp + 128]); // red zone +} + +/// Validates that the dst register is supported given the SaveStack/RestoreStack implementation. +static void ValidateDst(const Xbyak::Reg& dst) { + // Stack pointer is not preserved, so it can't be used as a dst. + ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "Stack pointer not supported as destination."); } #endif @@ -204,37 +218,21 @@ static void RestoreStack(Xbyak::CodeGenerator& c) { static void SaveRegisters(Xbyak::CodeGenerator& c, const std::initializer_list regs) { // Uses a more robust solution for saving registers on MacOS to avoid potential stack corruption // if games decide to not follow the ABI and use the red zone. -#ifdef __APPLE__ SaveStack(c); -#else - c.lea(rsp, ptr[rsp - 128]); // red zone -#endif for (const auto& reg : regs) { c.push(reg.cvt64()); } -#ifdef __APPLE__ RestoreStack(c); -#else - c.lea(rsp, ptr[rsp + 128]); -#endif } /// Switches to the patch stack, restores registers, and restores the original stack. static void RestoreRegisters(Xbyak::CodeGenerator& c, const std::initializer_list regs) { -#ifdef __APPLE__ SaveStack(c); -#else - c.lea(rsp, ptr[rsp - 128]); // red zone -#endif for (const auto& reg : regs) { c.pop(reg.cvt64()); } -#ifdef __APPLE__ RestoreStack(c); -#else - c.lea(rsp, ptr[rsp + 128]); -#endif } /// Switches to the patch stack and stores all registers. @@ -279,7 +277,7 @@ static void GenerateANDN(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src1 = ZydisToXbyakRegisterOperand(operands[1]); const auto src2 = ZydisToXbyakOperand(operands[2]); - ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "ANDN overwriting the stack pointer"); + ValidateDst(dst); // Check if src2 is a memory operand or a register different to dst. // In those cases, we don't need to use a temporary register and are free to modify dst. @@ -318,7 +316,7 @@ static void GenerateBEXTR(const ZydisDecodedOperand* operands, Xbyak::CodeGenera const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src = ZydisToXbyakOperand(operands[1]); const auto start_len = ZydisToXbyakRegisterOperand(operands[2]); - ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "BEXTR overwriting the stack pointer"); + ValidateDst(dst); const Xbyak::Reg32e shift(Xbyak::Operand::RCX, static_cast(start_len.getBit())); const auto scratch1 = @@ -356,7 +354,7 @@ static void GenerateBEXTR(const ZydisDecodedOperand* operands, Xbyak::CodeGenera static void GenerateBLSI(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src = ZydisToXbyakOperand(operands[1]); - ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "BLSI overwriting the stack pointer"); + ValidateDst(dst); const auto scratch = AllocateScratchRegister({&dst, src.get()}, dst.getBit()); @@ -386,7 +384,7 @@ static void GenerateBLSI(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat static void GenerateBLSMSK(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src = ZydisToXbyakOperand(operands[1]); - ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "BLSMSK overwriting the stack pointer"); + ValidateDst(dst); const auto scratch = AllocateScratchRegister({&dst, src.get()}, dst.getBit()); @@ -418,7 +416,7 @@ static void GenerateBLSMSK(const ZydisDecodedOperand* operands, Xbyak::CodeGener static void GenerateTZCNT(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src = ZydisToXbyakOperand(operands[1]); - ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "TZCNT overwriting the stack pointer"); + ValidateDst(dst); Xbyak::Label src_zero, end; @@ -445,7 +443,7 @@ static void GenerateTZCNT(const ZydisDecodedOperand* operands, Xbyak::CodeGenera static void GenerateBLSR(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto src = ZydisToXbyakOperand(operands[1]); - ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "BLSR overwriting the stack pointer"); + ValidateDst(dst); const auto scratch = AllocateScratchRegister({&dst, src.get()}, dst.getBit()); From ba109a4c53ac8f6905e9e62175a2d9adada2e391 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 4 Mar 2025 17:03:14 +0200 Subject: [PATCH 020/194] New Crowdin updates (#2592) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Italian) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Chinese Simplified) --- src/qt_gui/translations/ar_SA.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/da_DK.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/de_DE.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/el_GR.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/es_ES.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/fa_IR.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/fi_FI.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/fr_FR.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/hu_HU.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/id_ID.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/it_IT.ts | 177 +++++++++++++++++++++++++++++- src/qt_gui/translations/ja_JP.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/ko_KR.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/lt_LT.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/nb_NO.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/nl_NL.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/pl_PL.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/pt_BR.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/pt_PT.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/ro_RO.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/ru_RU.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/sq_AL.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/sv_SE.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/tr_TR.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/uk_UA.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/vi_VN.ts | 175 ++++++++++++++++++++++++++++++ src/qt_gui/translations/zh_CN.ts | 181 ++++++++++++++++++++++++++++++- src/qt_gui/translations/zh_TW.ts | 175 ++++++++++++++++++++++++++++++ 28 files changed, 4904 insertions(+), 4 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 44d68dcb4..7c5cf1631 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index a9871f21d..591f9b659 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 97eb99d0d..8a903070e 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index c409d558d..113d095fc 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index c6caed584..24b8a9bfe 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -903,6 +903,169 @@ Eliminar archivo PKG tras la instalación + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 17a1ecfad..8e03ec59d 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 74d9bb518..9d171b7fc 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index ecbe88b1e..e17ff70cb 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -903,6 +903,169 @@ Supprimer le fichier PKG à l'installation + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index aa123ccf2..1ee60ef19 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index bd8f6b11d..2ef26aa37 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 032234300..bb357ef04 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -903,6 +903,169 @@ Elimina file PKG dopo Installazione + + KBMSettings + + Configure Controls + Configura Comandi + + + D-Pad + Croce direzionale + + + Up + Su + + + unmapped + non mappato + + + Left + Sinistra + + + Right + Destra + + + Down + Giù + + + Left Analog Halfmode + Mezza Modalità Analogico Sinistra + + + hold to move left stick at half-speed + tieni premuto per muovere la levetta analogica sinistra a metà velocità + + + Left Stick + Levetta Sinistra + + + Config Selection + Selezione Configurazione + + + Common Config + Configurazione Comune + + + Use per-game configs + Usa configurazioni per gioco + + + Copy from Common Config + Copia da Configurazione Comune + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Editor Testuale + + + Help + Aiuto + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Click Touchpad + + + Mouse to Joystick + Mouse a Joystick + + + *press F7 ingame to activate + *premere F7 in gioco per attivare + + + R3 + R3 + + + Options + Opzioni + + + Mouse Movement Parameters + Parametri Movimento Del Mouse + + + note: click Help Button/Special Keybindings for more information + nota: cliccare sul Pulsante Aiuto/Associazioni Speciali dei Tasti per maggiori informazioni + + + Face Buttons + Pulsanti Frontali + + + Triangle + Triangolo + + + Square + Quadrato + + + Circle + Cerchio + + + Cross + Croce + + + Right Analog Halfmode + Mezza Modalità Analogico Destra + + + hold to move right stick at half-speed + tieni premuto per muovere la levetta analogica destra a metà velocità + + + Right Stick + Levetta Destra + + + Deadzone Offset (def 0.50): + Scostamento Zona Morta (def 0,50): + + + Speed Multiplier (def 1.0): + Moltiplicatore Di Velocità (def 1,0): + + + Speed Offset (def 0.125): + Scostamento Velocità (def 0,125): + + + Speed Offset (def 0.125): + Scostamento Velocità (def 0,125): + + MainWindow @@ -1633,7 +1796,7 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Apri la cartella personalizzata delle immagini/suoni trofei:\nÈ possibile aggiungere immagini personalizzate ai trofei e un audio.\nAggiungi i file a custom_trophy con i seguenti nomi:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNota: Il suono funzionerà solo nelle versioni QT. Never @@ -1851,6 +2014,18 @@ Notification Duration Durata Notifica + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Cartella utente portatile:\nMemorizza le impostazioni e i dati shadPS4 che saranno applicati solo alla build shadPS4 situata nella cartella attuale. Riavviare l'applicazione dopo aver creato la cartella utente portatile per iniziare a usarla. + TrophyViewer diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 7350057dd..70264ef12 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -903,6 +903,169 @@ インストール時にPKGファイルを削除 + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 6bc8ddfb1..096fef0a3 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index c384da08b..7d69e10b9 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 89400aaa4..a3adbcc5d 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -903,6 +903,169 @@ Slett PKG-fila ved installering + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Varslingsvarighet + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 924f59ef7..1b752649a 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 8a8ac83f7..3b22ba9a5 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -903,6 +903,169 @@ Usuń plik PKG po instalacji + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Czas trwania powiadomienia + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 5fe1774ba..9fa7c49aa 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -903,6 +903,169 @@ Excluir o PKG após a Instalação + + KBMSettings + + Configure Controls + Configurar Controles + + + D-Pad + Direcionais + + + Up + Cima + + + unmapped + não mapeado + + + Left + Esquerda + + + Right + Direita + + + Down + Baixo + + + Left Analog Halfmode + Meio Analógico Esquerdo + + + hold to move left stick at half-speed + Segure para mover o analógico esquerdo pela metade da velocidade + + + Left Stick + Analógico Esquerdo + + + Config Selection + Seleção de Configuração + + + Common Config + Configuração Comum + + + Use per-game configs + Usar configurações por jogo + + + Copy from Common Config + Copiar da Configuração Comum + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Editor de Texto + + + Help + Ajuda + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Clique do Touchpad + + + Mouse to Joystick + Mouse para Analógico + + + *press F7 ingame to activate + *Pressione F7 no jogo para ativar + + + R3 + R3 + + + Options + Opções + + + Mouse Movement Parameters + Parâmetros de Movimento do Mouse + + + note: click Help Button/Special Keybindings for more information + Nota: clique no botão de Ajuda -> Special Bindings para obter mais informações + + + Face Buttons + Botões de Ação + + + Triangle + Triângulo + + + Square + Quadrado + + + Circle + Círculo + + + Cross + Cruz + + + Right Analog Halfmode + Meio Analógico Direito + + + hold to move right stick at half-speed + Segure para mover o analógico direito pela metade da velocidade + + + Right Stick + Analógico Direito + + + Deadzone Offset (def 0.50): + Deslocamento da Zona Morta (Pad 0,50): + + + Speed Multiplier (def 1.0): + Multiplicador de Velocidade (Pad 1,0): + + + Speed Offset (def 0.125): + Deslocamento de Velocidade (Pad 0,125): + + + Speed Offset (def 0.125): + Deslocamento de Velocidade (Pad 0,125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Duração da Notificação + + Portable User Folder + Pasta de Usuário Portátil + + + Create Portable User Folder from Common User Folder + Criar Pasta de Usuário Portátil a partir da Pasta de Usuário Comum + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Pasta de usuário portátil:\nArmazena as configurações e dados do shadPS4 que serão aplicados apenas à compilação do shadPS4 localizada na pasta atual. Reinicie o aplicativo após criar a pasta de usuário portátil para começar a usá-la. + TrophyViewer diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index b279d1c5b..db7e15107 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -903,6 +903,169 @@ Eliminar Ficheiro PKG após Instalação + + KBMSettings + + Configure Controls + Configurar Comandos + + + D-Pad + Botões de Direção + + + Up + Cima + + + unmapped + não mapeado + + + Left + Esquerda + + + Right + Direita + + + Down + Baixo + + + Left Analog Halfmode + Meio Modo do Manípulo Esquerdo + + + hold to move left stick at half-speed + mantenha pressionado para mover o manípulo esquerdo à metade da velocidade + + + Left Stick + Manípulo Esquerdo + + + Config Selection + Seleção de Configuração + + + Common Config + Configuração Comum + + + Use per-game configs + Utilizar configurações por jogo + + + Copy from Common Config + Copiar da Configuração Comum + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Editor de Texto + + + Help + Ajuda + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Clique do Touchpad + + + Mouse to Joystick + Rato para Manípulo + + + *press F7 ingame to activate + *pressione F7 em jogo para ativar + + + R3 + R3 + + + Options + Opções + + + Mouse Movement Parameters + Parâmetros de Movimento do Rato + + + note: click Help Button/Special Keybindings for more information + nota: clique no Botão de Ajuda/Special Keybindings para obter mais informações + + + Face Buttons + Botões Frontais + + + Triangle + Triângulo + + + Square + Quadrado + + + Circle + Círculo + + + Cross + Cruz + + + Right Analog Halfmode + Meio Modo do Manípulo Direito + + + hold to move right stick at half-speed + mantenha pressionado para mover o manípulo direito à metade da velocidade + + + Right Stick + Manípulo Direito + + + Deadzone Offset (def 0.50): + Deslocamento da Zona Morta (def 0,50): + + + Speed Multiplier (def 1.0): + Multiplicador de Velocidade (def 1,0): + + + Speed Offset (def 0.125): + Deslocamento de Velocidade (def 0,125): + + + Speed Offset (def 0.125): + Deslocamento de Velocidade (def 0,125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Duração da Notificação + + Portable User Folder + Pasta de Utilizador Portátil + + + Create Portable User Folder from Common User Folder + Criar Pasta de Utilizador Portátil a partir da Pasta de Utilizador Comum + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Pasta de utilizador portátil:\nArmazena as definições e dados do shadPS4 que serão aplicados apenas na compilação do shadPS4 localizada na pasta atual. Reinicie a aplicação após criar a pasta de utilizador portátil para começar a usá-la. + TrophyViewer diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index e57b23150..1fc4b9e99 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index c454ead29..cf7fdccb9 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -903,6 +903,169 @@ Удалить файл PKG при установке + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Продолжительность уведомления + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 0b75e767d..c34d3c6be 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -903,6 +903,169 @@ Fshi skedarin PKG pas instalimit + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Kohëzgjatja e Njoftimit + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index f317b1974..0bea6b717 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -903,6 +903,169 @@ Ta bort PKG-fil efter installation + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Varaktighet för avisering + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 7b979e653..18362ae75 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -903,6 +903,169 @@ Yüklemede PKG Dosyasını Sil + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Bildirim Süresi + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 222cb9e1a..96c8e7c14 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -903,6 +903,169 @@ Видалити файл PKG під час встановлення + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 5ab469b92..30a69e2f3 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index dbfe61c16..2dd554753 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -903,6 +903,169 @@ 安装后删除 PKG 文件 + + KBMSettings + + Configure Controls + 配置键鼠 + + + D-Pad + D-Pad + + + Up + + + + unmapped + 未映射 + + + Left + + + + Right + + + + Down + + + + Left Analog Halfmode + 左摇杆半速模式 + + + hold to move left stick at half-speed + 按住以半速移动左摇杆 + + + Left Stick + 左摇杆 + + + Config Selection + 配置选择 + + + Common Config + 通用配置 + + + Use per-game configs + 每个游戏使用单独的配置 + + + Copy from Common Config + 从通用配置中复制 + + + L1 + L1 + + + L2 + L2 + + + Text Editor + 文本编辑器 + + + Help + 帮助 + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + 触摸板点击 + + + Mouse to Joystick + 鼠标控制摇杆 + + + *press F7 ingame to activate + * 按 F7 键激活 + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + 鼠标移动参数 + + + note: click Help Button/Special Keybindings for more information + 注意:点击帮助按钮 -> Special Bindings 获取更多信息 + + + Face Buttons + 正面按钮 + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + 右摇杆半速模式 + + + hold to move right stick at half-speed + 按住以半速移动右摇杆 + + + Right Stick + 右摇杆 + + + Deadzone Offset (def 0.50): + 死区偏移量(默认 0.50): + + + Speed Multiplier (def 1.0): + 速度系数(默认 1.0): + + + Speed Offset (def 0.125): + 速度偏移量(默认 0.125): + + + Speed Offset (def 0.125): + 速度偏移量(默认 0.125): + + MainWindow @@ -1633,7 +1796,7 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + 打开自定义奖杯图像/声音文件夹:\n您可以自定义奖杯图像和声音。\n将文件添加到 custom_trophy 文件夹中,文件名如下:\ntrophy.mp3、bronze.png、gold.png、platinum.png、silver.png。\n注意:自定义声音只能在 QT 版本中生效。 Never @@ -1841,16 +2004,28 @@ Top - Top + 顶部 Bottom - Bottom + 底部 Notification Duration 通知显示持续时间 + + Portable User Folder + 本地用户文件夹 + + + Create Portable User Folder from Common User Folder + 从公共用户文件夹创建本地用户文件夹 + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + 本地用户文件夹:\n存储 shadPS4 设置和数据,这些设置和数据仅应用于当前运行的 shadPS4。创建本地用户文件夹后,重启应用即可开始使用。 + TrophyViewer diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index cb78b7a64..913ca9aba 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -903,6 +903,169 @@ Delete PKG File on Install + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + Copy from Common Config + Copy from Common Config + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + MainWindow @@ -1851,6 +2014,18 @@ Notification Duration Notification Duration + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + TrophyViewer From f4bf53402fcaffae987d717f0424682031eebfc9 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Tue, 4 Mar 2025 23:03:30 +0800 Subject: [PATCH 021/194] Fix space (#2594) * Add missing space so only once translation is auto-generated * Use suggested format * delete an extra space --------- Co-authored-by: rainmakerv2 <30595646+jpau02@users.noreply.github.com> --- src/qt_gui/kbm_gui.cpp | 10 +++++----- src/qt_gui/kbm_gui.ui | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index e43eb282f..ced9a5b0c 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -105,13 +105,13 @@ KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* connect(ui->DeadzoneOffsetSlider, &QSlider::valueChanged, this, [this](int value) { QString DOSValue = QString::number(value / 100.0, 'f', 2); - QString DOSString = tr("Deadzone Offset (def 0.50): ") + DOSValue; + QString DOSString = tr("Deadzone Offset (def 0.50):") + " " + DOSValue; ui->DeadzoneOffsetLabel->setText(DOSString); }); connect(ui->SpeedMultiplierSlider, &QSlider::valueChanged, this, [this](int value) { QString SMSValue = QString::number(value / 10.0, 'f', 1); - QString SMSString = tr("Speed Multiplier (def 1.0): ") + SMSValue; + QString SMSString = tr("Speed Multiplier (def 1.0):") + " " + SMSValue; ui->SpeedMultiplierLabel->setText(SMSString); }); @@ -576,7 +576,7 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { int DOffsetInt = int(DOffsetValue); ui->DeadzoneOffsetSlider->setValue(DOffsetInt); QString LabelValue = QString::number(DOffsetInt / 100.0, 'f', 2); - QString LabelString = tr("Deadzone Offset (def 0.50): ") + LabelValue; + QString LabelString = tr("Deadzone Offset (def 0.50):") + " " + LabelValue; ui->DeadzoneOffsetLabel->setText(LabelString); std::string SMSOstring = line.substr(comma_pos + 1); @@ -591,7 +591,7 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { SpeedMultInt = 50; ui->SpeedMultiplierSlider->setValue(SpeedMultInt); LabelValue = QString::number(SpeedMultInt / 10.0, 'f', 1); - LabelString = tr("Speed Multiplier (def 1.0): ") + LabelValue; + LabelString = tr("Speed Multiplier (def 1.0):") + " " + LabelValue; ui->SpeedMultiplierLabel->setText(LabelString); std::string SOstring = SMSOstring.substr(comma_pos2 + 1); @@ -599,7 +599,7 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { int SOffsetInt = int(SOffsetValue); ui->SpeedOffsetSlider->setValue(SOffsetInt); LabelValue = QString::number(SOffsetInt / 1000.0, 'f', 3); - LabelString = tr("Speed Offset (def 0.125): ") + LabelValue; + LabelString = tr("Speed Offset (def 0.125):") + " " + LabelValue; ui->SpeedOffsetLabel->setText(LabelString); } } diff --git a/src/qt_gui/kbm_gui.ui b/src/qt_gui/kbm_gui.ui index fb8e4882b..0eac88105 100644 --- a/src/qt_gui/kbm_gui.ui +++ b/src/qt_gui/kbm_gui.ui @@ -634,7 +634,7 @@ Qt::FocusPolicy::NoFocus - Copy from Common Config + Copy from Common Config From f62884ffda84ac5b5984624034fd88d7d11e102e Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 4 Mar 2025 17:07:26 +0200 Subject: [PATCH 022/194] [ci skip] Qt GUI: Update Translation. (#2595) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 2aa65e9d1..8e43a9ae0 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -957,10 +957,6 @@ Use per-game configs - - Copy from Common Config - - L1 @@ -1049,20 +1045,20 @@ Right Stick - - Deadzone Offset (def 0.50): - - - - Speed Multiplier (def 1.0): - - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + + + + Deadzone Offset (def 0.50): + + + + Speed Multiplier (def 1.0): From dc52cfb9bc59b99710a827526dd5a5e4d865b3ce Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 4 Mar 2025 12:07:45 -0300 Subject: [PATCH 023/194] Clickable links for PRs in the changelog (#2588) --- src/qt_gui/check_update.cpp | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp index 5cae6c41a..e73a66a71 100644 --- a/src/qt_gui/check_update.cpp +++ b/src/qt_gui/check_update.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include @@ -247,7 +247,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate, bool latest_isWIP = latestRev.endsWith("WIP", Qt::CaseInsensitive); if (current_isWIP && !latest_isWIP) { } else { - QTextEdit* textField = new QTextEdit(this); + QTextBrowser* textField = new QTextBrowser(this); textField->setReadOnly(true); textField->setFixedWidth(500); textField->setFixedHeight(200); @@ -349,8 +349,28 @@ void CheckUpdate::requestChangelog(const QString& currentRev, const QString& lat } // Update the text field with the changelog - QTextEdit* textField = findChild(); + QTextBrowser* textField = findChild(); if (textField) { + QRegularExpression re("\\(\\#(\\d+)\\)"); + QString newChanges; + int lastIndex = 0; + QRegularExpressionMatchIterator i = re.globalMatch(changes); + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + newChanges += changes.mid(lastIndex, match.capturedStart() - lastIndex); + QString num = match.captured(1); + newChanges += + QString( + "(#%1)") + .arg(num); + lastIndex = match.capturedEnd(); + } + + newChanges += changes.mid(lastIndex); + changes = newChanges; + + textField->setOpenExternalLinks(true); textField->setHtml("

" + tr("Changes") + ":

" + changes); } From 96560ed3caa2afb171302d6e4adccd13d24cec1d Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 5 Mar 2025 18:12:35 +0200 Subject: [PATCH 024/194] New Crowdin updates (#2596) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (French) * New translations en_us.ts (Russian) * New translations en_us.ts (Russian) * New translations en_us.ts (Italian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Turkish) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Swedish) * New translations en_us.ts (Swedish) * New translations en_us.ts (Albanian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Albanian) --- src/qt_gui/translations/ar_SA.ts | 24 +++-- src/qt_gui/translations/da_DK.ts | 24 +++-- src/qt_gui/translations/de_DE.ts | 24 +++-- src/qt_gui/translations/el_GR.ts | 24 +++-- src/qt_gui/translations/es_ES.ts | 24 +++-- src/qt_gui/translations/fa_IR.ts | 24 +++-- src/qt_gui/translations/fi_FI.ts | 24 +++-- src/qt_gui/translations/fr_FR.ts | 146 +++++++++++++++---------------- src/qt_gui/translations/hu_HU.ts | 24 +++-- src/qt_gui/translations/id_ID.ts | 24 +++-- src/qt_gui/translations/it_IT.ts | 28 +++--- src/qt_gui/translations/ja_JP.ts | 24 +++-- src/qt_gui/translations/ko_KR.ts | 24 +++-- src/qt_gui/translations/lt_LT.ts | 24 +++-- src/qt_gui/translations/nb_NO.ts | 102 +++++++++++---------- src/qt_gui/translations/nl_NL.ts | 24 +++-- src/qt_gui/translations/pl_PL.ts | 24 +++-- src/qt_gui/translations/pt_BR.ts | 32 +++---- src/qt_gui/translations/pt_PT.ts | 24 +++-- src/qt_gui/translations/ro_RO.ts | 24 +++-- src/qt_gui/translations/ru_RU.ts | 110 +++++++++++------------ src/qt_gui/translations/sq_AL.ts | 54 ++++++------ src/qt_gui/translations/sv_SE.ts | 102 +++++++++++---------- src/qt_gui/translations/tr_TR.ts | 78 ++++++++--------- src/qt_gui/translations/uk_UA.ts | 24 +++-- src/qt_gui/translations/vi_VN.ts | 24 +++-- src/qt_gui/translations/zh_CN.ts | 24 +++-- src/qt_gui/translations/zh_TW.ts | 24 +++-- 28 files changed, 510 insertions(+), 622 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 7c5cf1631..87ccf0bd9 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs
- - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 591f9b659..c000a31d5 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 8a903070e..ee4ddbc15 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index 113d095fc..e85e02eab 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 24b8a9bfe..4a5977239 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 8e03ec59d..d7cd34d7e 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 9d171b7fc..5d546f91f 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index e17ff70cb..608b5d9ae 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -487,7 +487,7 @@ Face Buttons - Face Buttons + Touches d'action Triangle / Y @@ -535,7 +535,7 @@ Override Lightbar Color - Override Lightbar Color + Remplacer la couleur de la barre de lumière Override Color @@ -777,7 +777,7 @@ Delete Trophy - Delete Trophy + Supprimer Trophée Compatibility... @@ -857,7 +857,7 @@ No log file found for this game! - No log file found for this game! + Aucun fichier journal trouvé pour ce jeu! Failed to convert icon. @@ -869,7 +869,7 @@ This game has no saved trophies to delete! - This game has no saved trophies to delete! + Ce jeu n'a aucun trophée sauvegardé à supprimer! Save Data @@ -877,7 +877,7 @@ Trophy - Trophy + Trophée SFO Viewer for @@ -907,163 +907,159 @@ KBMSettings Configure Controls - Configure Controls + Configurer les Commandes D-Pad - D-Pad + Croix directionnelle Up - Up + Haut unmapped - unmapped + non mappé Left - Left + Gauche Right - Right + Droite Down - Down + Bas Left Analog Halfmode - Left Analog Halfmode + Demi-mode analogique gauche hold to move left stick at half-speed - hold to move left stick at half-speed + maintenez pour déplacer le joystick gauche à mi-vitesse Left Stick - Left Stick + Joystick gauche Config Selection - Config Selection + Sélection de la Configuration Common Config - Common Config + Configuration Commune Use per-game configs - Use per-game configs - - - Copy from Common Config - Copy from Common Config + Utiliser les configurations par jeu L1 - L1 + L1 L2 - L2 + L2 Text Editor - Text Editor + Éditeur de Texte Help - Help + Aide R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click - Touchpad Click + Clic tactile Mouse to Joystick - Mouse to Joystick + Souris vers Joystick *press F7 ingame to activate - *press F7 ingame to activate + *Appuyez sur F7 en jeu pour activer R3 - R3 + R3 Options - Options + Options Mouse Movement Parameters - Mouse Movement Parameters + Paramètres du mouvement de la souris note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + remarque: cliquez sur le bouton Aide / Raccourcis spéciaux pour plus d'informations Face Buttons - Face Buttons + Touches d'action Triangle - Triangle + Triangle Square - Square + Carré Circle - Circle + Rond Cross - Cross + Croix Right Analog Halfmode - Right Analog Halfmode + Demi-mode analogique droit hold to move right stick at half-speed - hold to move right stick at half-speed + maintenez pour déplacer le joystick droit à mi-vitesse Right Stick - Right Stick - - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Joystick Droit Speed Offset (def 0.125): - Speed Offset (def 0.125): + Décalage de vitesse (def 0,125) : - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copier à partir de la configuration commune + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0,50): + + + Speed Multiplier (def 1.0): + Multiplicateur de vitesse (def 1,0) : @@ -1492,7 +1488,7 @@ Open the custom trophy images/sounds folder - Open the custom trophy images/sounds folder + Ouvrir le dossier des images/sons de trophée personnalisé Logger @@ -1660,7 +1656,7 @@ Disable Trophy Notification - Disable Trophy Notification + Désactiver la notification de trophée Background Image @@ -1796,7 +1792,7 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Ouvrez le dossier des images/sons de trophée personnalisés:\nVous pouvez ajouter des images personnalisées aux trophées et un audio.\nAjouter les fichiers à custom_trophy avec les noms suivants :\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: Le son ne fonctionnera que dans les versions QT. Never @@ -1904,7 +1900,7 @@ Browse:\nBrowse for a folder to set as the save data path. - Browse:\nBrowse for a folder to set as the save data path. + Parcourir:\nNaviguez pour trouver un dossier pour définir le chemin des données de sauvegarde. Release @@ -1984,47 +1980,47 @@ Separate Log Files - Separate Log Files + Séparer les fichiers de log Separate Log Files:\nWrites a separate logfile for each game. - Separate Log Files:\nWrites a separate logfile for each game. + Fichiers journaux séparés :\nÉcrit un fichier journal séparé pour chaque jeu. Trophy Notification Position - Trophy Notification Position + Position de notification du trophée Left - Left + Gauche Right - Right + Droite Top - Top + Haut Bottom - Bottom + Bas Notification Duration - Notification Duration + Durée de la notification Portable User Folder - Portable User Folder + Dossier d'utilisateur portable Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Créer un dossier utilisateur portable à partir du dossier utilisateur commun Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Dossier utilisateur portable :\nStocke les paramètres et données shadPS4 qui seront appliqués uniquement à la version shadPS4 située dans le dossier actuel. Redémarrez l'application après avoir créé le dossier utilisateur portable pour commencer à l'utiliser. @@ -2035,19 +2031,19 @@ Progress - Progress + Progression Show Earned Trophies - Show Earned Trophies + Afficher les trophées gagnés Show Not Earned Trophies - Show Not Earned Trophies + Afficher les trophées non gagnés Show Hidden Trophies - Show Hidden Trophies + Afficher les trophées cachés diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 1ee60ef19..62c327c8c 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index 2ef26aa37..1551c0aac 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index bb357ef04..558b6f166 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -957,10 +957,6 @@ Use per-game configs Usa configurazioni per gioco - - Copy from Common Config - Copia da Configurazione Comune - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Levetta Destra - - Deadzone Offset (def 0.50): - Scostamento Zona Morta (def 0,50): - - - Speed Multiplier (def 1.0): - Moltiplicatore Di Velocità (def 1,0): - Speed Offset (def 0.125): Scostamento Velocità (def 0,125): - Speed Offset (def 0.125): - Scostamento Velocità (def 0,125): + Copy from Common Config + Copia da Configurazione Comune + + + Deadzone Offset (def 0.50): + Scostamento Zona Morta (def 0,50): + + + Speed Multiplier (def 1.0): + Moltiplicatore Di Velocità (def 1,0): @@ -2016,11 +2012,11 @@ Portable User Folder - Portable User Folder + Cartella Utente Portatile Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Crea una Cartella Utente Portatile dalla Cartella Comune Utente Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 70264ef12..2e1bbe394 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 096fef0a3..7f2bc531c 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 7d69e10b9..a9932cb1a 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index a3adbcc5d..4069da198 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -907,163 +907,159 @@ KBMSettings Configure Controls - Configure Controls + Tastaturoppsett D-Pad - D-Pad + Navigasjonsknapper Up - Up + Opp unmapped - unmapped + Ikke satt opp Left - Left + Venstre Right - Right + Høyre Down - Down + Ned Left Analog Halfmode - Left Analog Halfmode + Venstre analog halvmodus hold to move left stick at half-speed - hold to move left stick at half-speed + Hold for å bevege venstre analog med halv hastighet Left Stick - Left Stick + Venstre analog Config Selection - Config Selection + Valg av oppsett Common Config - Common Config + Felles oppsett Use per-game configs - Use per-game configs - - - Copy from Common Config - Copy from Common Config + Bruk oppsett per spill L1 - L1 + L1 L2 - L2 + L2 Text Editor - Text Editor + Skriveprogram Help - Help + Hjelp R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click - Touchpad Click + Berøringsplate knapp Mouse to Joystick - Mouse to Joystick + Mus til styrespak *press F7 ingame to activate - *press F7 ingame to activate + Trykk F7 i spillet for å bruke R3 - R3 + R3 Options - Options + Options Mouse Movement Parameters - Mouse Movement Parameters + Oppsett av musebevegelse note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + Merk: Trykk på hjelpeknappen for mer informasjon Face Buttons - Face Buttons + Handlingsknapper Triangle - Triangle + Triangel Square - Square + Firkant Circle - Circle + Sirkel Cross - Cross + Kryss Right Analog Halfmode - Right Analog Halfmode + Høyre analog halvmodus hold to move right stick at half-speed - hold to move right stick at half-speed + Hold for å bevege høyre analog med halv hastighet Right Stick - Right Stick - - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Høyre analog Speed Offset (def 0.125): - Speed Offset (def 0.125): + Hastighetsforskyvning (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Kopier fra felles oppsettet + + + Deadzone Offset (def 0.50): + Dødsoneforskyvning (def 0.50): + + + Speed Multiplier (def 1.0): + Hurtighetsmultiplikator (def 1.0): @@ -2016,15 +2012,15 @@ Portable User Folder - Portable User Folder + Separat brukermappe Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Lag ny separat brukermappe fra fellesbrukermappa Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Separat brukermappe:\n Lagrer shadPS4-innstillinger og data som kun brukes til shadPS4 programmet i gjeldende mappe. Start programmet på nytt etter opprettelsen av mappa for å ta den i bruk. diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 1b752649a..493468bc8 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 3b22ba9a5..f29b731be 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 9fa7c49aa..ae5567cc9 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -911,7 +911,7 @@ D-Pad - Direcionais + Direcional Up @@ -957,10 +957,6 @@ Use per-game configs Usar configurações por jogo - - Copy from Common Config - Copiar da Configuração Comum - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Analógico Direito - - Deadzone Offset (def 0.50): - Deslocamento da Zona Morta (Pad 0,50): - - - Speed Multiplier (def 1.0): - Multiplicador de Velocidade (Pad 1,0): - Speed Offset (def 0.125): Deslocamento de Velocidade (Pad 0,125): - Speed Offset (def 0.125): - Deslocamento de Velocidade (Pad 0,125): + Copy from Common Config + Copiar da Configuração Comum + + + Deadzone Offset (def 0.50): + Deslocamento da Zona Morta (Pad 0,50): + + + Speed Multiplier (def 1.0): + Multiplicador de Velocidade (Pad 1,0): @@ -2016,15 +2012,15 @@ Portable User Folder - Pasta de Usuário Portátil + Pasta Portátil do Usuário Create Portable User Folder from Common User Folder - Criar Pasta de Usuário Portátil a partir da Pasta de Usuário Comum + Criar Pasta Portátil do Usuário a partir da Pasta Comum do Usuário Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Pasta de usuário portátil:\nArmazena as configurações e dados do shadPS4 que serão aplicados apenas à compilação do shadPS4 localizada na pasta atual. Reinicie o aplicativo após criar a pasta de usuário portátil para começar a usá-la. + Pasta Portátil do Usuário:\nArmazena as configurações e dados do shadPS4 que serão aplicados apenas à compilação do shadPS4 localizada na pasta atual. Reinicie o aplicativo após criar a pasta portátil do usuário para começar a usá-la. diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index db7e15107..5a3a83bab 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -957,10 +957,6 @@ Use per-game configs Utilizar configurações por jogo - - Copy from Common Config - Copiar da Configuração Comum - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Manípulo Direito - - Deadzone Offset (def 0.50): - Deslocamento da Zona Morta (def 0,50): - - - Speed Multiplier (def 1.0): - Multiplicador de Velocidade (def 1,0): - Speed Offset (def 0.125): Deslocamento de Velocidade (def 0,125): - Speed Offset (def 0.125): - Deslocamento de Velocidade (def 0,125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 1fc4b9e99..df3eb5337 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index cf7fdccb9..f20989158 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -857,7 +857,7 @@ No log file found for this game! - Не найден файл журнала для этой игры! + Не найден файл логов для этой игры! Failed to convert icon. @@ -907,163 +907,159 @@ KBMSettings Configure Controls - Configure Controls + Настроить управление D-Pad - D-Pad + Крестовина Up - Up + Вверх unmapped - unmapped + не назначено Left - Left + Влево Right - Right + Вправо Down - Down + Вниз Left Analog Halfmode - Left Analog Halfmode + Левый стик вполовину hold to move left stick at half-speed - hold to move left stick at half-speed + удерживайте для перемещения левого стика вполовину меньше Left Stick - Left Stick + Левый стик Config Selection - Config Selection + Выбор конфига Common Config - Common Config + Общий конфиг Use per-game configs - Use per-game configs - - - Copy from Common Config - Copy from Common Config + Использовать настройки для каждой игры L1 - L1 + L1 L2 - L2 + L2 Text Editor - Text Editor + Текстовый редактор Help - Help + Помощь R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click - Touchpad Click + Нажатие на тачпад Mouse to Joystick - Mouse to Joystick + Мышь в джойстик *press F7 ingame to activate - *press F7 ingame to activate + *нажмите F7 в игре для активации R3 - R3 + R3 Options - Options + Options Mouse Movement Parameters - Mouse Movement Parameters + Параметры движения мыши note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + примечание: нажмите кнопку Help/Special Keybindings клавиш для получения дополнительной информации Face Buttons - Face Buttons + Кнопки действий Triangle - Triangle + Треугольник Square - Square + Квадрат Circle - Circle + Круг Cross - Cross + Крест Right Analog Halfmode - Right Analog Halfmode + Правый стик вполовину hold to move right stick at half-speed - hold to move right stick at half-speed + удерживайте для перемещения правого стика вполовину меньше Right Stick - Right Stick - - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Правый стик Speed Offset (def 0.125): - Speed Offset (def 0.125): + Смещение скорости (по умолч 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Копировать из общего конфига + + + Deadzone Offset (def 0.50): + Смещение мёртвой зоны (по умолч 0.50) + + + Speed Multiplier (def 1.0): + Множитель скорости (по умолч 1.0) @@ -1492,7 +1488,7 @@ Open the custom trophy images/sounds folder - Откройте папку с пользовательскими изображениями/звуками трофеев + Открыть папку с пользовательскими изображениями/звуками трофеев Logger @@ -1996,11 +1992,11 @@ Left - Влево + Слева Right - Вправо + Справа Top @@ -2016,15 +2012,15 @@ Portable User Folder - Portable User Folder + Портативная папка пользователя Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Создать портативную папку пользователя из общей папки пользователя Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Портативная папка пользователя:\nХранит настройки и данные shadPS4, которые будут применяться только к билду shadPS4, расположенному в этой папке. Перезагрузите приложение после создания портативной папки пользователя чтобы начать использовать её. diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index c34d3c6be..3bea8dc00 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -907,31 +907,31 @@ KBMSettings Configure Controls - Configure Controls + Konfiguro Kontrollet D-Pad - D-Pad + Shigjetat Up - Up + Lartë unmapped - unmapped + pacaktuar Left - Left + Majtas Right - Right + Djathtas Down - Down + Poshtë Left Analog Halfmode @@ -955,19 +955,15 @@ Use per-game configs - Use per-game configs - - - Copy from Common Config - Copy from Common Config + Përdor konfigurime për secilën lojë L1 - L1 + L1 L2 - L2 + L2 Text Editor @@ -975,19 +971,19 @@ Help - Help + Ndihmë R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click @@ -1003,7 +999,7 @@ R3 - R3 + R3 Options @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index 0bea6b717..a43b2da1c 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -907,163 +907,159 @@ KBMSettings Configure Controls - Configure Controls + Konfigurera kontroller D-Pad - D-Pad + Riktningsknappar Up - Up + Upp unmapped - unmapped + inte mappad Left - Left + Vänster Right - Right + Höger Down - Down + Ner Left Analog Halfmode - Left Analog Halfmode + Halvläge för vänster analog hold to move left stick at half-speed - hold to move left stick at half-speed + håll ner för att flytta vänster spak i halvfart Left Stick - Left Stick + Vänster spak Config Selection - Config Selection + Konfigurationsval Common Config - Common Config + Gemensam konfiguration Use per-game configs - Use per-game configs - - - Copy from Common Config - Copy from Common Config + Använd konfiguration per-spel L1 - L1 + L1 L2 - L2 + L2 Text Editor - Text Editor + Textredigerare Help - Help + Hjälp R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click - Touchpad Click + Klick på styrplatta Mouse to Joystick - Mouse to Joystick + Mus till styrspak *press F7 ingame to activate - *press F7 ingame to activate + *tryck F7 i spelet för att aktivera R3 - R3 + R3 Options - Options + Alternativ Mouse Movement Parameters - Mouse Movement Parameters + Parametrar för musrörelse note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + observera: klicka på Hjälp-knapp/Speciella tangentbindningar för mer information Face Buttons - Face Buttons + Handlingsknappar Triangle - Triangle + Triangel Square - Square + Fyrkant Circle - Circle + Cirkel Cross - Cross + Kryss Right Analog Halfmode - Right Analog Halfmode + Halvläge för höger analog hold to move right stick at half-speed - hold to move right stick at half-speed + håll ner för att flytta höger spak i halvfart Right Stick - Right Stick - - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Höger spak Speed Offset (def 0.125): - Speed Offset (def 0.125): + Offset för hastighet (standard 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Kopiera från gemensam konfiguration + + + Deadzone Offset (def 0.50): + Offset för dödläge (standard 0.50): + + + Speed Multiplier (def 1.0): + Hastighetsmultiplikator (standard 1.0): @@ -2016,15 +2012,15 @@ Portable User Folder - Portable User Folder + Portabel användarmapp Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Skapa portabel användarmapp från gemensam användarmapp Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portabel användarmapp:\nLagrar shadPS4-inställningar och data som endast tillämpas på den shadPS4-version som finns i den aktuella mappen. Starta om appen efter att du har skapat den portabla användarmappen för att börja använda den. diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 18362ae75..22fbb76c9 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -451,7 +451,7 @@ Use per-game configs - Oyuna özel yapılandırmaları kullan + Oyuna özel yapılandırma kullan L1 / LB @@ -907,15 +907,15 @@ KBMSettings Configure Controls - Configure Controls + Kontrolleri Yapılandır D-Pad - D-Pad + Yön Düğmeleri Up - Up + Yukarı unmapped @@ -923,15 +923,15 @@ Left - Left + Sol Right - Right + Sağ Down - Down + Aşağı Left Analog Halfmode @@ -943,51 +943,47 @@ Left Stick - Left Stick + Sol Analog Config Selection - Config Selection + Yapılandırma Seçimi Common Config - Common Config + Genel Yapılandırma Use per-game configs - Use per-game configs - - - Copy from Common Config - Copy from Common Config + Oyuna özel yapılandırma kullan L1 - L1 + L1 L2 - L2 + L2 Text Editor - Text Editor + Metin Düzenleyici Help - Help + Yardım R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click @@ -1003,15 +999,15 @@ R3 - R3 + R3 Options - Options + Seçenekler Mouse Movement Parameters - Mouse Movement Parameters + Mouse Hızı Değişkenleri note: click Help Button/Special Keybindings for more information @@ -1019,23 +1015,23 @@ Face Buttons - Face Buttons + Eylem Düğmeleri Triangle - Triangle + Üçgen Square - Square + Kare Circle - Circle + Daire Cross - Cross + Çarpı Right Analog Halfmode @@ -1047,23 +1043,23 @@ Right Stick - Right Stick - - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Sağ Analog Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Genel Yapılandırmadan Kopyala + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 96c8e7c14..36626fefd 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 30a69e2f3..52f63bfe6 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 2dd554753..25b394df2 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -957,10 +957,6 @@ Use per-game configs 每个游戏使用单独的配置 - - Copy from Common Config - 从通用配置中复制 - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick 右摇杆 - - Deadzone Offset (def 0.50): - 死区偏移量(默认 0.50): - - - Speed Multiplier (def 1.0): - 速度系数(默认 1.0): - Speed Offset (def 0.125): 速度偏移量(默认 0.125): - Speed Offset (def 0.125): - 速度偏移量(默认 0.125): + Copy from Common Config + 从通用配置中复制 + + + Deadzone Offset (def 0.50): + 死区偏移量(默认 0.50): + + + Speed Multiplier (def 1.0): + 速度系数(默认 1.0): diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 913ca9aba..7a3caa474 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -957,10 +957,6 @@ Use per-game configs Use per-game configs - - Copy from Common Config - Copy from Common Config - L1 L1 @@ -1049,21 +1045,21 @@ Right Stick Right Stick - - Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): - - - Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): - Speed Offset (def 0.125): Speed Offset (def 0.125): - Speed Offset (def 0.125): - Speed Offset (def 0.125): + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): From f1aea5176d00f13085080f0ea6bf0543f0083105 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 6 Mar 2025 09:09:12 +0200 Subject: [PATCH 025/194] New translations en_us.ts (Norwegian Bokmal) (#2599) --- src/qt_gui/translations/nb_NO.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 4069da198..0f9656189 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -22,11 +22,11 @@ CheatsPatches Cheats / Patches for - Juks / Programrettelser for + Juks og programrettelser for Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n - Juks/programrettelse er eksperimentelle.\nBruk med forsiktighet.\n\nLast ned juks individuelt ved å velge pakkebrønn og klikke på nedlastingsknappen.\nPå fanen programrettelse kan du laste ned alle programrettelser samtidig, velge hvilke du ønsker å bruke, og lagre valget ditt.\n\nSiden vi ikke utvikler Juks/Programrettelse,\nvær vennlig å rapportere problemer til juks/programrettelse utvikleren.\n\nHar du laget en ny juks? Besøk:\n + Juks og programrettelser er eksperimentelle.\nBruk med forsiktighet.\n\nLast ned juks individuelt ved å velge pakkebrønn og trykke på nedlastingsknappen.\nPå fanen programrettelser kan du laste ned alle programrettelser samtidig, velg hvilke du ønsker å bruke, og lagre valget ditt.\n\nSiden vi ikke utvikler juks eller programrettelser,\nmeld fra om feil til jukse eller programrettelse utvikleren.\n\nHar du utviklet en ny juks? Besøk:\n No Image Available @@ -337,7 +337,7 @@ The update has been downloaded, press OK to install. - Oppdateringen har blitt lastet ned, trykk OK for å installere. + Oppdateringen ble lastet ned, trykk OK for å installere. Failed to save the update file at @@ -407,11 +407,11 @@ ControlSettings Configure Controls - Sett opp kontroller + Kontrolleroppsett D-Pad - D-Pad + Navigasjonsknapper Up @@ -443,7 +443,7 @@ Config Selection - Utvalg av oppsett + Valg av oppsett Common Config @@ -709,7 +709,7 @@ Cheats / Patches - Juks / Programrettelse + Juks og programrettelser SFO Viewer @@ -841,7 +841,7 @@ Are you sure you want to delete %1's %2 directory? - Er du sikker på at du vil slette %1's %2 directory? + Er du sikker på at du vil slette %1's %2 mappa? Open Update Folder @@ -1154,7 +1154,7 @@ Download Cheats/Patches - Last ned juks/programrettelse + Last ned juks og programrettelser Dump Game List @@ -1436,7 +1436,7 @@ General - Generell + Generelt System @@ -1824,7 +1824,7 @@ Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. - Grafikkenhet:\nI systemer med flere GPU-er, velg GPU-en emulatoren skal bruke fra rullegardinlista,\neller "Velg automatisk&quot. + Grafikkenhet:\nSystemer med flere GPU-er, kan emulatoren velge hvilken enhet som skal brukes fra rullegardinlista,\neller velg "Velg automatisk". Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. @@ -1940,7 +1940,7 @@ Directory to install games - Mappe for å installere spill + Mappe for installering av spill Directory to save data From 0efe9a4d0f06d4dba40fbdce3c776d22159a6801 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Thu, 6 Mar 2025 15:09:27 +0800 Subject: [PATCH 026/194] Adds missing tr functions for certain GUI strings that should be translatable (#2598) * Adds missing tr functions for certain GUI strings that should be translatable * set clang format off for multi-line strings, set userDir as arg --------- Co-authored-by: rainmakerv2 <30595646+jpau02@users.noreply.github.com> --- src/qt_gui/control_settings.cpp | 4 ++-- src/qt_gui/kbm_config_dialog.cpp | 22 ++++++++--------- src/qt_gui/kbm_gui.cpp | 41 ++++++++++++++++---------------- src/qt_gui/kbm_help_dialog.cpp | 12 +++++----- src/qt_gui/settings_dialog.cpp | 30 +++++++++++------------ 5 files changed, 55 insertions(+), 54 deletions(-) diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index 820a490a0..885e36680 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -100,8 +100,8 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { 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"); + nosave = QMessageBox::information(this, tr("Unable to Save"), + tr("Cannot bind axis values more than once")); return; } diff --git a/src/qt_gui/kbm_config_dialog.cpp b/src/qt_gui/kbm_config_dialog.cpp index cfff056a0..49a6bcd89 100644 --- a/src/qt_gui/kbm_config_dialog.cpp +++ b/src/qt_gui/kbm_config_dialog.cpp @@ -28,7 +28,7 @@ HelpDialog* helpDialog; EditorDialog::EditorDialog(QWidget* parent) : QDialog(parent) { - setWindowTitle("Edit Keyboard + Mouse and Controller input bindings"); + setWindowTitle(tr("Edit Keyboard + Mouse and Controller input bindings")); resize(600, 400); // Create the editor widget @@ -42,7 +42,7 @@ EditorDialog::EditorDialog(QWidget* parent) : QDialog(parent) { // Load all installed games loadInstalledGames(); - QCheckBox* unifiedInputCheckBox = new QCheckBox("Use Per-Game configs", this); + QCheckBox* unifiedInputCheckBox = new QCheckBox(tr("Use Per-Game configs"), this); unifiedInputCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); // Connect checkbox signal @@ -94,7 +94,7 @@ void EditorDialog::loadFile(QString game) { originalConfig = editor->toPlainText(); file.close(); } else { - QMessageBox::warning(this, "Error", "Could not open the file for reading"); + QMessageBox::warning(this, tr("Error"), tr("Could not open the file for reading")); } } @@ -108,7 +108,7 @@ void EditorDialog::saveFile(QString game) { out << editor->toPlainText(); file.close(); } else { - QMessageBox::warning(this, "Error", "Could not open the file for writing"); + QMessageBox::warning(this, tr("Error"), tr("Could not open the file for writing")); } } @@ -121,7 +121,7 @@ void EditorDialog::closeEvent(QCloseEvent* event) { } if (hasUnsavedChanges()) { QMessageBox::StandardButton reply; - reply = QMessageBox::question(this, "Save Changes", "Do you want to save changes?", + reply = QMessageBox::question(this, tr("Save Changes"), tr("Do you want to save changes?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); if (reply == QMessageBox::Yes) { @@ -168,7 +168,7 @@ void EditorDialog::onCancelClicked() { void EditorDialog::onHelpClicked() { if (!isHelpOpen) { helpDialog = new HelpDialog(&isHelpOpen, this); - helpDialog->setWindowTitle("Help"); + helpDialog->setWindowTitle(tr("Help")); helpDialog->setAttribute(Qt::WA_DeleteOnClose); // Clean up on close // Get the position and size of the Config window QRect configGeometry = this->geometry(); @@ -188,10 +188,10 @@ void EditorDialog::onResetToDefaultClicked() { bool default_default = gameComboBox->currentText() == "default"; QString prompt = default_default - ? "Do you want to reset your custom default config to the original default config?" - : "Do you want to reset this config to your custom default config?"; - QMessageBox::StandardButton reply = - QMessageBox::question(this, "Reset to Default", prompt, QMessageBox::Yes | QMessageBox::No); + ? tr("Do you want to reset your custom default config to the original default config?") + : tr("Do you want to reset this config to your custom default config?"); + QMessageBox::StandardButton reply = QMessageBox::question(this, tr("Reset to Default"), prompt, + QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { if (default_default) { @@ -206,7 +206,7 @@ void EditorDialog::onResetToDefaultClicked() { editor->setPlainText(in.readAll()); file.close(); } else { - QMessageBox::warning(this, "Error", "Could not open the file for reading"); + QMessageBox::warning(this, tr("Error"), tr("Could not open the file for reading")); } // saveFile(gameComboBox->currentText()); } diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index ced9a5b0c..b78a6cf75 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -87,15 +87,16 @@ KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* connect(ui->CopyCommonButton, &QPushButton::clicked, this, [this] { if (ui->ProfileComboBox->currentText() == "Common Config") { - QMessageBox::information(this, "Common Config Selected", - "This button copies mappings from the Common Config to the " - "currently selected profile, and cannot be used when the " - "currently selected profile is the Common Config."); + QMessageBox::information(this, tr("Common Config Selected"), + // clang-format off +tr("This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config.")); + // clang-format on } else { QMessageBox::StandardButton reply = - QMessageBox::question(this, "Copy values from Common Config", - "Do you want to overwrite existing mappings with the " - "mappings from the Common Config?", + QMessageBox::question(this, tr("Copy values from Common Config"), + // clang-format off +tr("Do you want to overwrite existing mappings with the mappings from the Common Config?"), + // clang-format on QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { SetUIValuestoMappings("default"); @@ -423,8 +424,8 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) { // Prevent duplicate inputs for KBM as this breaks the engine for (auto it = inputs.begin(); it != inputs.end(); ++it) { if (std::find(it + 1, inputs.end(), *it) != inputs.end()) { - QMessageBox::information(this, "Unable to Save", - "Cannot bind any unique input more than once"); + QMessageBox::information(this, tr("Unable to Save"), + tr("Cannot bind any unique input more than once")); return; } } @@ -625,7 +626,7 @@ void KBMSettings::GetGameTitle() { void KBMSettings::onHelpClicked() { if (!HelpWindowOpen) { HelpWindow = new HelpDialog(&HelpWindowOpen, this); - HelpWindow->setWindowTitle("Help"); + HelpWindow->setWindowTitle(tr("Help")); HelpWindow->setAttribute(Qt::WA_DeleteOnClose); // Clean up on close HelpWindow->show(); HelpWindowOpen = true; @@ -643,7 +644,7 @@ void KBMSettings::StartTimer(QPushButton*& button) { mapping = button->text(); DisableMappingButtons(); - button->setText("Press a key [" + QString::number(MappingTimer) + "]"); + button->setText(tr("Press a key") + " [" + QString::number(MappingTimer) + "]"); timer = new QTimer(this); MappingButton = button; connect(timer, &QTimer::timeout, this, [this]() { CheckMapping(MappingButton); }); @@ -652,7 +653,7 @@ void KBMSettings::StartTimer(QPushButton*& button) { void KBMSettings::CheckMapping(QPushButton*& button) { MappingTimer -= 1; - button->setText("Press a key [" + QString::number(MappingTimer) + "]"); + button->setText(tr("Press a key") + " [" + QString::number(MappingTimer) + "]"); if (MappingCompleted) { EnableMapping = false; @@ -1003,15 +1004,15 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { SetMapping("mousewheelup"); } else { - QMessageBox::information(this, "Cannot set mapping", - "Mousewheel cannot be mapped to stick outputs"); + QMessageBox::information(this, tr("Cannot set mapping"), + tr("Mousewheel cannot be mapped to stick outputs")); } } else if (wheelEvent->angleDelta().y() < -5) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { SetMapping("mousewheeldown"); } else { - QMessageBox::information(this, "Cannot set mapping", - "Mousewheel cannot be mapped to stick outputs"); + QMessageBox::information(this, tr("Cannot set mapping"), + tr("Mousewheel cannot be mapped to stick outputs")); } } if (wheelEvent->angleDelta().x() > 5) { @@ -1023,8 +1024,8 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { SetMapping("mousewheelright"); } } else { - QMessageBox::information(this, "Cannot set mapping", - "Mousewheel cannot be mapped to stick outputs"); + QMessageBox::information(this, tr("Cannot set mapping"), + tr("Mousewheel cannot be mapped to stick outputs")); } } else if (wheelEvent->angleDelta().x() < -5) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { @@ -1034,8 +1035,8 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { SetMapping("mousewheelleft"); } } else { - QMessageBox::information(this, "Cannot set mapping", - "Mousewheel cannot be mapped to stick outputs"); + QMessageBox::information(this, tr("Cannot set mapping"), + tr("Mousewheel cannot be mapped to stick outputs")); } } return true; diff --git a/src/qt_gui/kbm_help_dialog.cpp b/src/qt_gui/kbm_help_dialog.cpp index 44f75f6f8..c13e18b59 100644 --- a/src/qt_gui/kbm_help_dialog.cpp +++ b/src/qt_gui/kbm_help_dialog.cpp @@ -77,11 +77,11 @@ HelpDialog::HelpDialog(bool* open_flag, QWidget* parent) : QDialog(parent) { QVBoxLayout* containerLayout = new QVBoxLayout(containerWidget); // Add expandable sections to container layout - auto* quickstartSection = new ExpandableSection("Quickstart", quickstart()); - auto* faqSection = new ExpandableSection("FAQ", faq()); - auto* syntaxSection = new ExpandableSection("Syntax", syntax()); - auto* specialSection = new ExpandableSection("Special Bindings", special()); - auto* bindingsSection = new ExpandableSection("Keybindings", bindings()); + auto* quickstartSection = new ExpandableSection(tr("Quickstart"), quickstart()); + auto* faqSection = new ExpandableSection(tr("FAQ"), faq()); + auto* syntaxSection = new ExpandableSection(tr("Syntax"), syntax()); + auto* specialSection = new ExpandableSection(tr("Special Bindings"), special()); + auto* bindingsSection = new ExpandableSection(tr("Keybindings"), bindings()); containerLayout->addWidget(quickstartSection); containerLayout->addWidget(faqSection); @@ -109,4 +109,4 @@ HelpDialog::HelpDialog(bool* open_flag, QWidget* parent) : QDialog(parent) { connect(syntaxSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize); connect(specialSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize); connect(bindingsSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize); -} \ No newline at end of file +} diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 69f5d3c8a..bff4b8221 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -235,21 +235,6 @@ SettingsDialog::SettingsDialog(std::span physical_devices, Common::FS::GetUserPath(Common::FS::PathType::CustomTrophy)); QDesktopServices::openUrl(QUrl::fromLocalFile(userPath)); }); - - connect(ui->PortableUserButton, &QPushButton::clicked, this, []() { - QString userDir; - Common::FS::PathToQString(userDir, std::filesystem::current_path() / "user"); - if (std::filesystem::exists(std::filesystem::current_path() / "user")) { - QMessageBox::information(NULL, "Cannot create portable user folder", - userDir + " already exists"); - } else { - std::filesystem::copy(Common::FS::GetUserPath(Common::FS::PathType::UserDir), - std::filesystem::current_path() / "user", - std::filesystem::copy_options::recursive); - QMessageBox::information(NULL, "Portable user folder created", - userDir + " successfully created"); - } - }); } // Input TAB @@ -300,6 +285,21 @@ SettingsDialog::SettingsDialog(std::span physical_devices, ui->currentSaveDataPath->setText(save_data_path_string); } }); + + connect(ui->PortableUserButton, &QPushButton::clicked, this, []() { + QString userDir; + Common::FS::PathToQString(userDir, std::filesystem::current_path() / "user"); + if (std::filesystem::exists(std::filesystem::current_path() / "user")) { + QMessageBox::information(NULL, tr("Cannot create portable user folder"), + tr("%1 already exists").arg(userDir)); + } else { + std::filesystem::copy(Common::FS::GetUserPath(Common::FS::PathType::UserDir), + std::filesystem::current_path() / "user", + std::filesystem::copy_options::recursive); + QMessageBox::information(NULL, tr("Portable user folder created"), + tr("%1 successfully created.").arg(userDir)); + } + }); } // DEBUG TAB From f4dc8dca8d9255f843a0f8ab0dc439c82bd79e7a Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 6 Mar 2025 16:20:22 +0200 Subject: [PATCH 027/194] [ci skip] Qt GUI: Update Translation. (#2605) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 130 +++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 8e43a9ae0..9b03ebddb 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -541,6 +541,61 @@ Override Color + + Unable to Save + + + + Cannot bind axis values more than once + + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + + + + Use Per-Game configs + + + + Error + Error + + + Could not open the file for reading + + + + Could not open the file for writing + + + + Save Changes + + + + Do you want to save changes? + + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + + + + Do you want to reset this config to your custom default config? + + + + Reset to Default + + ElfViewer @@ -884,6 +939,29 @@ + + HelpDialog + + Quickstart + + + + FAQ + + + + Syntax + + + + Special Bindings + + + + Keybindings + + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): + + Common Config Selected + + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + + Copy values from Common Config + + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + + Unable to Save + + + + Cannot bind any unique input more than once + + + + Press a key + + + + Cannot set mapping + + + + Mousewheel cannot be mapped to stick outputs + + MainWindow @@ -2022,6 +2136,22 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + + + + %1 already exists + + + + Portable user folder created + + + + %1 successfully created. + + TrophyViewer From eaa18d4e3cf70ac0e2bcf277b3ea085a209d9ef9 Mon Sep 17 00:00:00 2001 From: smiRaphi <87574679+smiRaphi@users.noreply.github.com> Date: Thu, 6 Mar 2025 15:20:55 +0100 Subject: [PATCH 028/194] load trophy from .wav (#2603) Co-authored-by: smiRaphi --- src/core/libraries/np_trophy/trophy_ui.cpp | 10 +++++++--- src/qt_gui/settings_dialog.cpp | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp index aba56f341..3c9f883b3 100644 --- a/src/core/libraries/np_trophy/trophy_ui.cpp +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -92,10 +92,14 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin AddLayer(this); #ifdef ENABLE_QT_GUI - QString musicPath = QString::fromStdString(CustomTrophy_Dir.string() + "/trophy.mp3"); - if (fs::exists(musicPath.toStdString())) { + QString musicPathWav = QString::fromStdString(CustomTrophy_Dir.string() + "/trophy.wav"); + QString musicPathMp3 = QString::fromStdString(CustomTrophy_Dir.string() + "/trophy.mp3"); + if (fs::exists(musicPathWav.toStdString())) { BackgroundMusicPlayer::getInstance().setVolume(100); - BackgroundMusicPlayer::getInstance().playMusic(musicPath, false); + BackgroundMusicPlayer::getInstance().playMusic(musicPathWav, false); + } else if (fs::exists(musicPathMp3.toStdString())) { + BackgroundMusicPlayer::getInstance().setVolume(100); + BackgroundMusicPlayer::getInstance().playMusic(musicPathMp3, false); } #endif } diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index bff4b8221..6e38bd025 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -635,7 +635,7 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { //User if (elementName == "OpenCustomTrophyLocationButton") { - text = tr("Open the custom trophy images/sounds folder:\\nYou can add custom images to the trophies and an audio.\\nAdd the files to custom_trophy with the following names:\\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\\nNote: The sound will only work in QT versions."); + text = tr("Open the custom trophy images/sounds folder:\\nYou can add custom images to the trophies and an audio.\\nAdd the files to custom_trophy with the following names:\\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\\nNote: The sound will only work in QT versions."); } // Input From 8b7eed3ffc9835886b088f6c1b0595948bce4c8d Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 6 Mar 2025 16:21:52 +0200 Subject: [PATCH 029/194] [ci skip] Qt GUI: Update Translation. (#2607) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 9b03ebddb..924f2d6e5 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1904,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Never @@ -2152,6 +2148,10 @@ %1 successfully created. + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + TrophyViewer From b5029ab940a7d489cdbe76796d8ebe6a4c2c8aff Mon Sep 17 00:00:00 2001 From: jarred wilson <20207921+jardon@users.noreply.github.com> Date: Fri, 7 Mar 2025 08:51:38 -0500 Subject: [PATCH 030/194] fix:[#2618] load HDR setting from GPU in GUI (#2619) --- src/qt_gui/settings_dialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 6e38bd025..41caccec9 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -430,7 +430,7 @@ void SettingsDialog::LoadValuesFromConfig() { ui->vblankSpinBox->setValue(toml::find_or(data, "GPU", "vblankDivider", 1)); ui->dumpShadersCheckBox->setChecked(toml::find_or(data, "GPU", "dumpShaders", false)); ui->nullGpuCheckBox->setChecked(toml::find_or(data, "GPU", "nullGpu", false)); - ui->enableHDRCheckBox->setChecked(toml::find_or(data, "General", "allowHDR", false)); + ui->enableHDRCheckBox->setChecked(toml::find_or(data, "GPU", "allowHDR", false)); ui->playBGMCheckBox->setChecked(toml::find_or(data, "General", "playBGM", false)); ui->disableTrophycheckBox->setChecked( toml::find_or(data, "General", "isTrophyPopupDisabled", false)); From bfb8b46d7da9567110d5d82b9ae90019d8e41d2a Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 7 Mar 2025 15:52:57 +0200 Subject: [PATCH 031/194] New Crowdin updates (#2608) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Italian) * New translations en_us.ts (Russian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Russian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Arabic) --- src/qt_gui/translations/ar_SA.ts | 142 +++++++++++- src/qt_gui/translations/da_DK.ts | 138 ++++++++++- src/qt_gui/translations/de_DE.ts | 138 ++++++++++- src/qt_gui/translations/el_GR.ts | 138 ++++++++++- src/qt_gui/translations/es_ES.ts | 138 ++++++++++- src/qt_gui/translations/fa_IR.ts | 138 ++++++++++- src/qt_gui/translations/fi_FI.ts | 138 ++++++++++- src/qt_gui/translations/fr_FR.ts | 138 ++++++++++- src/qt_gui/translations/hu_HU.ts | 138 ++++++++++- src/qt_gui/translations/id_ID.ts | 138 ++++++++++- src/qt_gui/translations/it_IT.ts | 138 ++++++++++- src/qt_gui/translations/ja_JP.ts | 138 ++++++++++- src/qt_gui/translations/ko_KR.ts | 138 ++++++++++- src/qt_gui/translations/lt_LT.ts | 138 ++++++++++- src/qt_gui/translations/nb_NO.ts | 150 +++++++++++- src/qt_gui/translations/nl_NL.ts | 138 ++++++++++- src/qt_gui/translations/pl_PL.ts | 138 ++++++++++- src/qt_gui/translations/pt_BR.ts | 142 +++++++++++- src/qt_gui/translations/pt_PT.ts | 138 ++++++++++- src/qt_gui/translations/ro_RO.ts | 138 ++++++++++- src/qt_gui/translations/ru_RU.ts | 140 ++++++++++- src/qt_gui/translations/sq_AL.ts | 138 ++++++++++- src/qt_gui/translations/sv_SE.ts | 138 ++++++++++- src/qt_gui/translations/tr_TR.ts | 138 ++++++++++- src/qt_gui/translations/uk_UA.ts | 384 +++++++++++++++++++++---------- src/qt_gui/translations/vi_VN.ts | 138 ++++++++++- src/qt_gui/translations/zh_CN.ts | 138 ++++++++++- src/qt_gui/translations/zh_TW.ts | 138 ++++++++++- 28 files changed, 3886 insertions(+), 246 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 87ccf0bd9..d45af76b8 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + المساعدة + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -584,7 +639,7 @@ Directory to install DLC - Directory to install DLC + مكان تثبيت حزمات DLC @@ -603,7 +658,7 @@ Compatibility - Compatibility + التوافق Region @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never أبداً @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index c000a31d5..bc9ed1d39 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Aldrig @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index ee4ddbc15..db691b11d 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Aktualisiere Kompatibilitätsdatenbank:\nAktualisiere sofort die Kompatibilitätsdatenbank. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Niemals @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index e85e02eab..e09bf7192 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Ποτέ @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 4a5977239..9f7fb3721 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ Visualizador de SFO para + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Nunca @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index d7cd34d7e..f58d691ad 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. به‌روزرسانی پایگاه داده سازگاری:\nپایگاه داده سازگاری را بلافاصله به‌روزرسانی می‌کند. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never هرگز @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 5d546f91f..5e1c20d47 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Päivitä Yhteensopivuustietokanta:\nPäivitää yhteensopivuustietokannan heti. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Ei koskaan @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 608b5d9ae..e815c2b94 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -541,6 +541,61 @@ Override Color Remplacer la couleur + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ Visionneuse SFO pour + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Multiplicateur de vitesse (def 1,0) : + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Mettre à jour la compatibilité au démarrage:\nMet à jour immédiatement la base de données de compatibilité. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Ouvrez le dossier des images/sons de trophée personnalisés:\nVous pouvez ajouter des images personnalisées aux trophées et un audio.\nAjouter les fichiers à custom_trophy avec les noms suivants :\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: Le son ne fonctionnera que dans les versions QT. - Never Jamais @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Dossier utilisateur portable :\nStocke les paramètres et données shadPS4 qui seront appliqués uniquement à la version shadPS4 située dans le dossier actuel. Redémarrez l'application après avoir créé le dossier utilisateur portable pour commencer à l'utiliser. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 62c327c8c..bb510a479 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Soha @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index 1551c0aac..e6a8aabd9 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Tidak Pernah @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 558b6f166..2e6dcf14a 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -541,6 +541,61 @@ Override Color Sostituisci Colore + + Unable to Save + Impossibile Salvare + + + Cannot bind axis values more than once + Impossibile associare i valori degli assi più di una volta + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Modifica le associazioni di input di tastiera + mouse e controller + + + Use Per-Game configs + Usa Configurazioni Per Gioco + + + Error + Errore + + + Could not open the file for reading + Impossibile aprire il file per la lettura + + + Could not open the file for writing + Impossibile aprire il file per la scrittura + + + Save Changes + Salva Modifiche + + + Do you want to save changes? + Vuoi salvare le modifiche? + + + Help + Aiuto + + + Do you want to reset your custom default config to the original default config? + Vuoi reimpostare la configurazione predefinita personalizzata alla configurazione predefinita originale? + + + Do you want to reset this config to your custom default config? + Vuoi reimpostare questa configurazione alla configurazione predefinita personalizzata? + + + Reset to Default + Ripristina a Predefinito + ElfViewer @@ -884,6 +939,29 @@ Visualizzatore SFO per + + HelpDialog + + Quickstart + Avvio rapido + + + FAQ + FAQ + + + Syntax + Sintassi + + + Special Bindings + Associazioni Speciali + + + Keybindings + Associazioni dei pulsanti + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Moltiplicatore Di Velocità (def 1,0): + + Common Config Selected + Configurazione Comune Selezionata + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Questo pulsante copia le mappature dalla Configurazione Comune al profilo attualmente selezionato, e non può essere usato quando il profilo attualmente selezionato è Configurazione Comune. + + + Copy values from Common Config + Copia valori da Configurazione Comune + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Vuoi sovrascrivere le mappature esistenti con le mappature dalla Configurazione Comune? + + + Unable to Save + Impossibile Salvare + + + Cannot bind any unique input more than once + Non è possibile associare qualsiasi input univoco più di una volta + + + Press a key + Premi un tasto + + + Cannot set mapping + Impossibile impostare la mappatura + + + Mousewheel cannot be mapped to stick outputs + La rotella del mouse non può essere associata ai comandi della levetta analogica + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Aggiorna Database Compatibilità:\nAggiorna immediatamente il database di compatibilità. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Apri la cartella personalizzata delle immagini/suoni trofei:\nÈ possibile aggiungere immagini personalizzate ai trofei e un audio.\nAggiungi i file a custom_trophy con i seguenti nomi:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNota: Il suono funzionerà solo nelle versioni QT. - Never Mai @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Cartella utente portatile:\nMemorizza le impostazioni e i dati shadPS4 che saranno applicati solo alla build shadPS4 situata nella cartella attuale. Riavviare l'applicazione dopo aver creato la cartella utente portatile per iniziare a usarla. + + Cannot create portable user folder + Impossibile creare la cartella utente portatile + + + %1 already exists + %1: esiste già + + + Portable user folder created + Cartella utente portatile creata + + + %1 successfully created. + %1 creato con successo. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Apri la cartella personalizzata delle immagini/suoni trofei:\nÈ possibile aggiungere immagini personalizzate ai trofei e un audio.\nAggiungi i file a custom_trophy con i seguenti nomi:\ntrophy.wav OPPURE trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNota: Il suono funzionerà solo nelle versioni QT. + TrophyViewer diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 2e1bbe394..981b7cf3b 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. 互換性データベースを更新する:\n今すぐ互換性データベースを更新します。 - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never 無効 @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 7f2bc531c..d76a16430 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Never @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index a9932cb1a..41b203e2b 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Niekada @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 0f9656189..9f71e0aa5 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -22,7 +22,7 @@ CheatsPatches Cheats / Patches for - Juks og programrettelser for + Juks og programrettelser for Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n @@ -102,7 +102,7 @@ Unable to open files.json for reading. - Kan ikke åpne files.json for lesing. + Klarte ikke åpne files.json for lesing. No patch file found for the current serial. @@ -110,11 +110,11 @@ Unable to open the file for reading. - Kan ikke åpne fila for lesing. + Klarte ikke åpne fila for lesing. Unable to open the file for writing. - Kan ikke åpne fila for skriving. + Klarte ikke åpne fila for skriving. Failed to parse XML: @@ -372,11 +372,11 @@ Unable to update compatibility data! Try again later. - Kan ikke oppdatere kompatibilitetsdata! Prøv igjen senere. + Klarte ikke oppdatere kompatibilitetsdata! Prøv igjen senere. Unable to open compatibility_data.json for writing. - Kan ikke åpne compatibility_data.json for skriving. + Klarte ikke åpne compatibility_data.json for skriving. Unknown @@ -541,6 +541,61 @@ Override Color Overstyr farge + + Unable to Save + Klarte ikke lagre + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Feil + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Lagre endringer + + + Do you want to save changes? + Vil du lagre endringene? + + + Help + Hjelp + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Tilbakestill + ElfViewer @@ -884,6 +939,29 @@ SFO-viser for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + Ofte stilte spørsmål + + + Syntax + Syntaks + + + Special Bindings + Special Bindings + + + Keybindings + Hurtigtast + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Hurtighetsmultiplikator (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Klarte ikke lagre + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Oppdater kompatibilitets-database:\nOppdater kompatibilitets-databasen nå. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Åpne mappa med tilpassede bilder og lyder for trofé:\nDu kan legge til tilpassede bilder til trofeer med en lyd.\nLegg filene til custom_trophy med følgende navn:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nMerk: Avspilling av lyden vil bare fungere med Qt versjonen. - Never Aldri @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Separat brukermappe:\n Lagrer shadPS4-innstillinger og data som kun brukes til shadPS4 programmet i gjeldende mappe. Start programmet på nytt etter opprettelsen av mappa for å ta den i bruk. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 finnes allerede + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 opprettet. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 493468bc8..8596c7e71 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Nooit @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index f29b731be..d179d2172 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -541,6 +541,61 @@ Override Color Zastąp kolor + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ Menedżer plików SFO dla + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Zaktualizuj bazę danych zgodności:\nNatychmiast zaktualizuj bazę danych zgodności. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Nigdy @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index ae5567cc9..6aefdb36e 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -541,6 +541,61 @@ Override Color Substituir a Cor + + Unable to Save + Não foi possível salvar + + + Cannot bind axis values more than once + Não é possível vincular os valores do eixo mais de uma vez + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Editar atalhos de entrada do Teclado + Mouse e do Controle + + + Use Per-Game configs + Usar configurações por jogo + + + Error + Erro + + + Could not open the file for reading + Não foi possível abrir o arquivo para leitura + + + Could not open the file for writing + Não foi possível abrir o arquivo para gravação + + + Save Changes + Salvar Alterações + + + Do you want to save changes? + Gostaria de salvar as alterações? + + + Help + Ajuda + + + Do you want to reset your custom default config to the original default config? + Você gostaria de redefinir sua configuração padrão personalizada de volta para a configuração padrão original? + + + Do you want to reset this config to your custom default config? + Você gostaria de redefinir esta configuração para a sua configuração padrão personalizada? + + + Reset to Default + Redefinir ao Padrão + ElfViewer @@ -651,7 +706,7 @@ Game does not initialize properly / crashes the emulator - Jogo não inicializa corretamente / trava o emulador + O jogo não inicializa corretamente ou trava o emulador Game boots, but only displays a blank screen @@ -884,6 +939,29 @@ Visualizador de SFO para + + HelpDialog + + Quickstart + Introdução + + + FAQ + Perguntas frequentes + + + Syntax + Sintaxe + + + Special Bindings + Atalhos Especiais + + + Keybindings + Teclas de atalho + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Multiplicador de Velocidade (Pad 1,0): + + Common Config Selected + Configuração Comum Selecionada + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Este botão copia os mapeamentos da Configuração Comum para o perfil atualmente selecionado, e não pode ser usado quando o perfil atualmente selecionado é a Configuração Comum. + + + Copy values from Common Config + Copiar valores da Configuração Comum + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Você deseja substituir os mapeamentos existentes com os mapeamentos da Configuração Comum? + + + Unable to Save + Não foi possível salvar + + + Cannot bind any unique input more than once + Não é possível vincular qualquer entrada única mais de uma vez + + + Press a key + Aperte uma tecla + + + Cannot set mapping + Não é possível definir o mapeamento + + + Mousewheel cannot be mapped to stick outputs + A rolagem do mouse não pode ser mapeada para saídas do analógico + MainWindow @@ -1488,7 +1602,7 @@ Open the custom trophy images/sounds folder - Abrir a pasta de imagens/sons de troféus personalizados + Abrir a pasta de imagens e sons de troféus personalizados Logger @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Atualizar Lista de Compatibilidade:\nAtualiza imediatamente o banco de dados de compatibilidade. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Abrir a pasta de imagens/sons de troféus personalizados:\nVocê pode adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os arquivos na pasta custom_trophy com os seguintes nomes:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas em versões Qt. - Never Nunca @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Pasta Portátil do Usuário:\nArmazena as configurações e dados do shadPS4 que serão aplicados apenas à compilação do shadPS4 localizada na pasta atual. Reinicie o aplicativo após criar a pasta portátil do usuário para começar a usá-la. + + Cannot create portable user folder + Não é possível criar a pasta portátil do usuário + + + %1 already exists + %1 já existe + + + Portable user folder created + Pasta portátil do usuário criada + + + %1 successfully created. + %1 criado com sucesso. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Abrir a pasta de imagens e sons de troféus personalizados:\nVocê pode adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os arquivos na pasta custom_trophy com os seguintes nomes:\ntrophy.wav OU trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas em versões Qt. + TrophyViewer diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index 5a3a83bab..0667447e1 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -541,6 +541,61 @@ Override Color Substituir Cor + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ Visualizador SFO para + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Atualizar Base de Dados de Compatibilidade:\nAtualiza imediatamente a base de dados de compatibilidade. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Abrir a pasta de imagens/sons de troféus personalizados:\nPoderá adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os ficheiros na pasta custom_trophy com os seguintes nomes:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas nas versões Qt. - Never Nunca @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Pasta de utilizador portátil:\nArmazena as definições e dados do shadPS4 que serão aplicados apenas na compilação do shadPS4 localizada na pasta atual. Reinicie a aplicação após criar a pasta de utilizador portátil para começar a usá-la. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index df3eb5337..ded417640 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Niciodată @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index f20989158..ecf155dbf 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -541,6 +541,61 @@ Override Color Заменить цвет + + Unable to Save + Не удаётся сохранить + + + Cannot bind axis values more than once + Невозможно привязать значения оси более одного раза + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Редактировать бинды клавиатуры + мыши и контроллера + + + Use Per-Game configs + Использовать настройки для каждой игры + + + Error + Ошибка + + + Could not open the file for reading + Не удалось открыть файл для чтения + + + Could not open the file for writing + Не удалось открыть файл для записи + + + Save Changes + Сохранить изменения + + + Do you want to save changes? + Хотите сохранить изменения? + + + Help + Помощь + + + Do you want to reset your custom default config to the original default config? + Хотите ли вы сбросить ваш пользовательский конфиг по умолчанию к первоначальному конфигу по умолчанию? + + + Do you want to reset this config to your custom default config? + Хотите ли вы сбросить этот конфиг к вашему пользовательскому конфигу по умолчанию? + + + Reset to Default + Сбросить по умолчанию + ElfViewer @@ -884,6 +939,29 @@ Просмотр SFO для + + HelpDialog + + Quickstart + Быстрый старт + + + FAQ + ЧАВО + + + Syntax + Синтаксис + + + Special Bindings + Специальные бинды + + + Keybindings + Бинды клавиш + + InstallDirSelect @@ -1011,7 +1089,7 @@ note: click Help Button/Special Keybindings for more information - примечание: нажмите кнопку Help/Special Keybindings клавиш для получения дополнительной информации + примечание: нажмите кнопку Помощь/Специальные бинды для получения дополнительной информации Face Buttons @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Множитель скорости (по умолч 1.0) + + Common Config Selected + Выбран общий конфиг + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Эта кнопка копирует настройки из общего конфига в текущий выбранный профиль, и не может быть использован, когда выбранный профиль это общий конфиг. + + + Copy values from Common Config + Копировать значения из общего конфига + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Вы хотите перезаписать существующие настройки настройками из общего конфига? + + + Unable to Save + Не удаётся сохранить + + + Cannot bind any unique input more than once + Невозможно привязать уникальный ввод более одного раза + + + Press a key + Нажмите кнопку + + + Cannot set mapping + Не удаётся задать настройки + + + Mousewheel cannot be mapped to stick outputs + Колесо не может быть назначено для вывода стиков + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Обновить базу совместимости:\nНемедленно обновить базу данных совместимости. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Открыть папку с пользовательскими изображениями/звуками трофеев:\nВы можете добавить пользовательские изображения к трофеям и аудио.\nДобавьте файлы в custom_trophy со следующими именами:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nПримечание: звук будет работать только в QT-версии. - Never Никогда @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Портативная папка пользователя:\nХранит настройки и данные shadPS4, которые будут применяться только к билду shadPS4, расположенному в этой папке. Перезагрузите приложение после создания портативной папки пользователя чтобы начать использовать её. + + Cannot create portable user folder + Невозможно создать папку для портативной папки пользователя + + + %1 already exists + %1 уже существует + + + Portable user folder created + Портативная папка пользователя создана + + + %1 successfully created. + %1 успешно создано. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Открыть папку с пользовательскими изображениями/звуками трофеев:\nВы можете добавить пользовательские изображения к трофеям и аудио.\nДобавьте файлы в custom_trophy со следующими именами:\ntrophy.wav ИЛИ trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nПримечание: звук будет работать только в QT-версии. + TrophyViewer diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 3bea8dc00..c371e2323 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -541,6 +541,61 @@ Override Color Zëvendëso Ngjyrën + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ Shikuesi SFO për + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Përditëso bazën e të dhënave të përputhshmërisë:\nPërditëso menjëherë bazën e të dhënave të përputhshmërisë. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Hap dosjen e imazheve/tingujve të trofeve të personalizuar:\nMund të shtosh imazhe të personalizuara për trofetë dhe një audio.\nShto skedarët në dosjen custom_trophy me emrat që vijojnë:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nShënim: Tingulli do të punojë vetëm në versionet QT. - Never Kurrë @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index a43b2da1c..8120cb403 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -541,6 +541,61 @@ Override Color Åsidosätt färg + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO-visare för + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Hastighetsmultiplikator (standard 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Öppna mappen för anpassade trofébilder/ljud:\nDu kan lägga till anpassade bilder till troféerna och ett ljud.\nLägg till filerna i custom_trophy med följande namn:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservera: Ljudet fungerar endast i QT-versioner. - Never Aldrig @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portabel användarmapp:\nLagrar shadPS4-inställningar och data som endast tillämpas på den shadPS4-version som finns i den aktuella mappen. Starta om appen efter att du har skapat den portabla användarmappen för att börja använda den. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 22fbb76c9..447cebf5f 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -541,6 +541,61 @@ Override Color Rengi Geçersiz Kıl + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Görüntüleyici: + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Uyumluluk Veritabanını Güncelle:\nUyumluluk veri tabanını hemen güncelleyin. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Asla @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 36626fefd..688cedf9d 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -407,139 +407,194 @@ ControlSettings Configure Controls - Configure Controls + Налаштування елементів керування D-Pad - D-Pad + Хрестовина Up - Up + Вгору Left - Left + Ліворуч Right - Right + Праворуч Down - Down + Вниз Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + Мертва зона лівого стику (за замов.: 2, максимум: 127) Left Deadzone - Left Deadzone + Ліва мертва зона Left Stick - Left Stick + Лівий стік Config Selection - Config Selection + Вибір конфігурації Common Config - Common Config + Загальна конфігурація Use per-game configs - Use per-game configs + Використовувати ігрові конфігурації L1 / LB - L1 / LB + L1 / Лівий Бампер L2 / LT - L2 / LT + L2 / Лівий Тригер Back - Back + Назад R1 / RB - R1 / RB + R1 / Правий Бампер R2 / RT - R2 / RT + R2 / Правий Тригер L3 - L3 + Кнопка лівого стику Options / Start - Options / Start + Опції / Старт R3 - R3 + Кнопка правого стику Face Buttons - Face Buttons + Лицьові кнопки Triangle / Y - Triangle / Y + Трикутник / Y Square / X - Square / X + Квадрат / X Circle / B - Circle / B + Коло / B Cross / A - Cross / A + Хрест / A Right Stick Deadzone (def:2, max:127) - Right Stick Deadzone (def:2, max:127) + Мертва зона правого стику (за замов.: 2, максимум: 127) Right Deadzone - Right Deadzone + Права мертва зона Right Stick - Right Stick + Правий стик Color Adjustment - Color Adjustment + Налаштування Кольору R: - R: + R: G: - G: + G: B: - B: + B: Override Lightbar Color - Override Lightbar Color + Змінити колір підсвітки Override Color - Override Color + Змінити колір + + + Unable to Save + Не вдалося зберегти + + + Cannot bind axis values more than once + Неможливо пере назначити кнопку більше одного разу + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Налаштування клавіатури, миші та перевизначення кнопок контролеру + + + Use Per-Game configs + Використовувати ігрові конфігурації + + + Error + Помилка + + + Could not open the file for reading + Не вдалося відкрити файл для читання + + + Could not open the file for writing + Не вдалось відкрити файл для запису + + + Save Changes + Зберегти зміни + + + Do you want to save changes? + Бажаєте зберегти зміни? + + + Help + Допомога + + + Do you want to reset your custom default config to the original default config? + Ви бажаєте скинути ваші налаштування за замовчуванням до початкової конфігурації? + + + Do you want to reset this config to your custom default config? + Ви бажаєте скинути ці налаштування до вашої конфігурації за замовчуванням? + + + Reset to Default + Відновити налаштування за замовчанням @@ -584,7 +639,7 @@ Directory to install DLC - Directory to install DLC + Папка для встановлення DLC @@ -777,7 +832,7 @@ Delete Trophy - Delete Trophy + Видалити трофей Compatibility... @@ -833,7 +888,7 @@ DLC - DLC + DLC Delete %1 @@ -857,11 +912,11 @@ No log file found for this game! - No log file found for this game! + Файл звіту для цієї гри не знайдено! Failed to convert icon. - Failed to convert icon. + Не вдалося конвертувати значок. This game has no save data to delete! @@ -869,7 +924,7 @@ This game has no saved trophies to delete! - This game has no saved trophies to delete! + Ця гра немає збережених трофеїв для видалення! Save Data @@ -877,11 +932,34 @@ Trophy - Trophy + Трофеї SFO Viewer for - SFO Viewer for + Перегляд SFO + + + + HelpDialog + + Quickstart + Швидкий старт + + + FAQ + ЧаПи + + + Syntax + Синтаксис + + + Special Bindings + Спеціальні прив'язки + + + Keybindings + Призначення клавіш @@ -907,159 +985,195 @@ KBMSettings Configure Controls - Configure Controls + Налаштування елементів керування D-Pad - D-Pad + Хрестовина Up - Up + Вгору unmapped - unmapped + не назначено Left - Left + Ліворуч Right - Right + Праворуч Down - Down + Вниз Left Analog Halfmode - Left Analog Halfmode + Напіврежим лівого аналогового стику hold to move left stick at half-speed - hold to move left stick at half-speed + утримуйте щоб рухати лівий стик в половину швидкості Left Stick - Left Stick + Лівий стик Config Selection - Config Selection + Вибір конфігурації Common Config - Common Config + Загальна конфігурація Use per-game configs - Use per-game configs + Використовувати ігрові конфігурації L1 - L1 + L1 / Лівий Бампер L2 - L2 + L2 / Лівий Тригер Text Editor - Text Editor + Текстовий редактор Help - Help + Довідка R1 - R1 + R1 / Правий Бампер R2 - R2 + R2 / Правий Тригер L3 - L3 + Кнопка лівого стику Touchpad Click - Touchpad Click + Натискання на сенсорну панель Mouse to Joystick - Mouse to Joystick + Миша в джойстик *press F7 ingame to activate - *press F7 ingame to activate + *натисніть F7 у грі для увімкнення R3 - R3 + Кнопка правого стику Options - Options + Опції Mouse Movement Parameters - Mouse Movement Parameters + Параметри руху миші note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + замітка: натисніть кнопку Довідки/Спеціального Призначення клавіш для отримання додаткової інформації Face Buttons - Face Buttons + Лицьові кнопки Triangle - Triangle + Трикутник Square - Square + Квадрат Circle - Circle + Коло Cross - Cross + Хрест Right Analog Halfmode - Right Analog Halfmode + Напіврежим правого аналогового стику hold to move right stick at half-speed - hold to move right stick at half-speed + утримуйте щоб рухати правий стик в половину швидкості Right Stick - Right Stick + Правий стик Speed Offset (def 0.125): - Speed Offset (def 0.125): + Зміщення швидкості (замовч 0,125): Copy from Common Config - Copy from Common Config + Копіювати з Загальної конфігурації Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Зміщення мертвої зони (замовч 0,50): Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Модифікатор швидкості (замовч 1,0): + + + Common Config Selected + Вибрані Загальні налаштування + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Ця кнопка копіює перепризначення кнопок із Загальної конфігурації до поточного вибраного профілю, і не може бути використана, якщо поточний вибраний профіль є загальною конфігурацією. + + + Copy values from Common Config + Копіювати значення з Загальної конфігурації + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Ви бажаєте перезаписати наявні перепризначення кнопок з загальної конфігурації? + + + Unable to Save + Не вдалося зберегти + + + Cannot bind any unique input more than once + Не можна прив'язати кнопку вводу більш ніж один раз + + + Press a key + Натисніть клавішу + + + Cannot set mapping + Не вдалося встановити прив'язку + + + Mousewheel cannot be mapped to stick outputs + Коліщатко миші не можна прив'язати зі значенням стиків @@ -1338,27 +1452,27 @@ Run Game - Run Game + Запустити гру Eboot.bin file not found - Eboot.bin file not found + Файл Boot.bin не знайдено PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) + Файл PKG (*.PKG *.pkg) PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! + PKG - це патч або DLC, будь ласка, спочатку встановіть гру! Game is already running! - Game is already running! + Гра вже запущена! shadPS4 - shadPS4 + shadPS4 @@ -1381,7 +1495,7 @@ Installed - Installed + Встановлені Size @@ -1389,19 +1503,19 @@ Category - Category + Категорія Type - Type + Тип App Ver - App Ver + Версія додатку FW - FW + ПЗ Region @@ -1409,7 +1523,7 @@ Flags - Flags + Мітки Path @@ -1425,7 +1539,7 @@ Package - Package + Пакет @@ -1488,7 +1602,7 @@ Open the custom trophy images/sounds folder - Open the custom trophy images/sounds folder + Відкрити папку користувацьких зображень трофеїв/звуків Logger @@ -1568,7 +1682,7 @@ Enable HDR - Enable HDR + Увімкнути HDR Paths @@ -1656,7 +1770,7 @@ Disable Trophy Notification - Disable Trophy Notification + Вимкнути сповіщення про отримання трофею Background Image @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Оновити данні ігрової сумістності:\nНегайно оновить базу даних ігрової сумісності. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Ніколи @@ -1844,7 +1954,7 @@ Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. - Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + Увімкнути HDR:\nУвімкнути HDR в іграх, які його підтримують.\nВаш монітор повинен мати колірний простір BT2020 PQ та формат swapchain RGB10A2. Game Folders:\nThe list of folders to check for installed games. @@ -1912,7 +2022,7 @@ Set the volume of the background music. - Set the volume of the background music. + Налаштування гучності фонової музики. Enable Motion Controls @@ -1944,83 +2054,103 @@ Directory to save data - Directory to save data + Папка для збереження даних Video - Video + Відео Display Mode - Display Mode + Режим відображення Windowed - Windowed + У вікні Fullscreen - Fullscreen + Повноекранний Fullscreen (Borderless) - Fullscreen (Borderless) + Повний екран (без рамок) Window Size - Window Size + Розмір вікна W: - W: + Висота: H: - H: + Ширина: Separate Log Files - Separate Log Files + Окремі файли журналу Separate Log Files:\nWrites a separate logfile for each game. - Separate Log Files:\nWrites a separate logfile for each game. + Окремі файли журналу:\nЗаписує окремий файл журналу для кожної гри. Trophy Notification Position - Trophy Notification Position + Розміщення сповіщень про трофеї Left - Left + Ліворуч Right - Right + Праворуч Top - Top + Вгорі Bottom - Bottom + Внизу Notification Duration - Notification Duration + Тривалість сповіщення Portable User Folder - Portable User Folder + Портативна папка користувача Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Створити портативну папку користувача з загальної папки Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Портативна папка користувача:\nЗберігає налаштування та дані shadPS4, які будуть застосовані лише до збірки shadPS4, розташованої у поточній теці. Перезапустіть програму після створення портативної теки користувача, щоб почати користуватися нею. + + + Cannot create portable user folder + Не вдалося створити портативну папку користувача + + + %1 already exists + %1 вже існує + + + Portable user folder created + Портативна папка користувача створена + + + %1 successfully created. + %1 успішно створено. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Відкрити папку користувацьких зображень трофеїв/звуків:\nВи можете додати користувацькі зображення до трофеїв та звук.\nДодайте файли до теки custom_trophy з такими назвами:\ntrophy.wav АБО trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nПримітка: Звук буде працювати лише у версіях ShadPS4 з графічним інтерфейсом. @@ -2031,19 +2161,19 @@ Progress - Progress + Прогрес Show Earned Trophies - Show Earned Trophies + Показати отримані трофеї Show Not Earned Trophies - Show Not Earned Trophies + Показати не отримані трофеї Show Hidden Trophies - Show Hidden Trophies + Показати приховані трофеї diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 52f63bfe6..d20c07dd9 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never Không bao giờ @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 25b394df2..fe13f4092 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -541,6 +541,61 @@ Override Color 覆盖颜色 + + Unable to Save + 无法保存 + + + Cannot bind axis values more than once + 摇杆 X/Y 轴的操作绑定不在同一直线 + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + 编辑键盘鼠标和手柄按键绑定 + + + Use Per-Game configs + 每个游戏使用单独的配置 + + + Error + 错误 + + + Could not open the file for reading + 无法打开文件进行读取 + + + Could not open the file for writing + 无法打开文件进行读取 + + + Save Changes + 保存更改 + + + Do you want to save changes? + 您要保存更改吗? + + + Help + 帮助 + + + Do you want to reset your custom default config to the original default config? + 您要将自定义默认配置重置为默认配置吗? + + + Do you want to reset this config to your custom default config? + 您想要将此配置重置为自定义默认配置吗? + + + Reset to Default + 重置为默认 + ElfViewer @@ -884,6 +939,29 @@ SFO 查看器 - + + HelpDialog + + Quickstart + 快速入门 + + + FAQ + 常见问题 + + + Syntax + 语法 + + + Special Bindings + 特殊绑定 + + + Keybindings + 按键绑定 + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): 速度系数(默认 1.0): + + Common Config Selected + 已选中通用配置 + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + 此按钮用于将通用配置中的映射复制到当前选定的配置文件,当前选定的配置文件为通用配置时无法使用。 + + + Copy values from Common Config + 从通用配置中复制配置 + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + 您想要用通用配置的映射覆盖现有映射吗? + + + Unable to Save + 无法保存 + + + Cannot bind any unique input more than once + 不能绑定重复的按键 + + + Press a key + 按下按键 + + + Cannot set mapping + 无法设置映射 + + + Mousewheel cannot be mapped to stick outputs + 鼠标滚轮无法映射到摇杆 + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. 更新兼容性数据库:\n立即更新兼容性数据库。 - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - 打开自定义奖杯图像/声音文件夹:\n您可以自定义奖杯图像和声音。\n将文件添加到 custom_trophy 文件夹中,文件名如下:\ntrophy.mp3、bronze.png、gold.png、platinum.png、silver.png。\n注意:自定义声音只能在 QT 版本中生效。 - Never 从不 @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. 本地用户文件夹:\n存储 shadPS4 设置和数据,这些设置和数据仅应用于当前运行的 shadPS4。创建本地用户文件夹后,重启应用即可开始使用。 + + Cannot create portable user folder + 无法创建本地用户文件夹 + + + %1 already exists + %1 已存在 + + + Portable user folder created + 本地用户文件夹已创建 + + + %1 successfully created. + %1 创建成功。 + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + 打开自定义奖杯图像/声音文件夹:\n您可以自定义奖杯图像和声音。\n将文件添加到 custom_trophy 文件夹中,文件名如下:\ntrophy.wav 或 trophy.mp3、bronze.png、gold.png、platinum.png、silver.png。\n注意:自定义声音只能在 QT 版本中生效。 + TrophyViewer diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 7a3caa474..311ce3ab8 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -541,6 +541,61 @@ Override Color Override Color + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + ElfViewer @@ -884,6 +939,29 @@ SFO Viewer for + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + InstallDirSelect @@ -1061,6 +1139,42 @@ Speed Multiplier (def 1.0): Speed Multiplier (def 1.0): + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + MainWindow @@ -1790,10 +1904,6 @@ Update Compatibility Database:\nImmediately update the compatibility database. Update Compatibility Database:\nImmediately update the compatibility database. - - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Never 從不 @@ -2022,6 +2132,26 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + TrophyViewer From 6b3746f3a688cd4c7c2acfbbe8238ebd97235d37 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Fri, 7 Mar 2025 21:42:25 +0300 Subject: [PATCH 032/194] kernel: re-implement clock_gettime (#2615) --- src/core/libraries/kernel/time.cpp | 192 +++++++++++++++++++++++++---- 1 file changed, 168 insertions(+), 24 deletions(-) diff --git a/src/core/libraries/kernel/time.cpp b/src/core/libraries/kernel/time.cpp index 508e54089..42d959885 100644 --- a/src/core/libraries/kernel/time.cpp +++ b/src/core/libraries/kernel/time.cpp @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/native_clock.h" +#include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/orbis_error.h" #include "core/libraries/kernel/time.h" #include "core/libraries/libs.h" @@ -19,6 +20,7 @@ #if __APPLE__ #include #endif +#include #include #include #include @@ -93,46 +95,188 @@ u32 PS4_SYSV_ABI sceKernelSleep(u32 seconds) { return 0; } -int PS4_SYSV_ABI sceKernelClockGettime(s32 clock_id, OrbisKernelTimespec* tp) { - if (tp == nullptr) { +#ifdef _WIN64 +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 0 +#endif +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 1 +#endif +#ifndef CLOCK_PROCESS_CPUTIME_ID +#define CLOCK_PROCESS_CPUTIME_ID 2 +#endif +#ifndef CLOCK_THREAD_CPUTIME_ID +#define CLOCK_THREAD_CPUTIME_ID 3 +#endif +#ifndef CLOCK_REALTIME_COARSE +#define CLOCK_REALTIME_COARSE 5 +#endif +#ifndef CLOCK_MONOTONIC_COARSE +#define CLOCK_MONOTONIC_COARSE 6 +#endif + +#define DELTA_EPOCH_IN_100NS 116444736000000000ULL + +static u64 FileTimeTo100Ns(FILETIME& ft) { + return *reinterpret_cast(&ft); +} + +static s32 clock_gettime(u32 clock_id, struct timespec* ts) { + switch (clock_id) { + case CLOCK_REALTIME: + case CLOCK_REALTIME_COARSE: { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + const u64 ns = FileTimeTo100Ns(ft) - DELTA_EPOCH_IN_100NS; + ts->tv_sec = ns / 10'000'000; + ts->tv_nsec = (ns % 10'000'000) * 100; + return 0; + } + case CLOCK_MONOTONIC: + case CLOCK_MONOTONIC_COARSE: { + static LARGE_INTEGER pf = [] { + LARGE_INTEGER res{}; + QueryPerformanceFrequency(&pf); + return res; + }(); + + LARGE_INTEGER pc{}; + QueryPerformanceCounter(&pc); + ts->tv_sec = pc.QuadPart / pf.QuadPart; + ts->tv_nsec = ((pc.QuadPart % pf.QuadPart) * 1000'000'000) / pf.QuadPart; + return 0; + } + case CLOCK_PROCESS_CPUTIME_ID: { + FILETIME ct, et, kt, ut; + if (!GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) { + return EFAULT; + } + const u64 ns = FileTimeTo100Ns(ut) + FileTimeTo100Ns(kt); + ts->tv_sec = ns / 10'000'000; + ts->tv_nsec = (ns % 10'000'000) * 100; + return 0; + } + case CLOCK_THREAD_CPUTIME_ID: { + FILETIME ct, et, kt, ut; + if (!GetThreadTimes(GetCurrentThread(), &ct, &et, &kt, &ut)) { + return EFAULT; + } + const u64 ns = FileTimeTo100Ns(ut) + FileTimeTo100Ns(kt); + ts->tv_sec = ns / 10'000'000; + ts->tv_nsec = (ns % 10'000'000) * 100; + return 0; + } + default: + return EINVAL; + } +} +#endif + +int PS4_SYSV_ABI orbis_clock_gettime(s32 clock_id, struct timespec* ts) { + if (ts == nullptr) { return ORBIS_KERNEL_ERROR_EFAULT; } - clockid_t pclock_id = CLOCK_REALTIME; + + clockid_t pclock_id = CLOCK_MONOTONIC; switch (clock_id) { case ORBIS_CLOCK_REALTIME: case ORBIS_CLOCK_REALTIME_PRECISE: - case ORBIS_CLOCK_REALTIME_FAST: pclock_id = CLOCK_REALTIME; break; case ORBIS_CLOCK_SECOND: + case ORBIS_CLOCK_REALTIME_FAST: +#ifndef __APPLE__ + pclock_id = CLOCK_REALTIME_COARSE; +#else + pclock_id = CLOCK_REALTIME; +#endif + break; + case ORBIS_CLOCK_UPTIME: + case ORBIS_CLOCK_UPTIME_PRECISE: case ORBIS_CLOCK_MONOTONIC: case ORBIS_CLOCK_MONOTONIC_PRECISE: - case ORBIS_CLOCK_MONOTONIC_FAST: pclock_id = CLOCK_MONOTONIC; break; - default: - LOG_ERROR(Lib_Kernel, "unsupported = {} using CLOCK_REALTIME", clock_id); + case ORBIS_CLOCK_UPTIME_FAST: + case ORBIS_CLOCK_MONOTONIC_FAST: +#ifndef __APPLE__ + pclock_id = CLOCK_MONOTONIC_COARSE; +#else + pclock_id = CLOCK_MONOTONIC; +#endif break; + case ORBIS_CLOCK_THREAD_CPUTIME_ID: + pclock_id = CLOCK_THREAD_CPUTIME_ID; + break; + case ORBIS_CLOCK_PROCTIME: { + const auto us = sceKernelGetProcessTime(); + ts->tv_sec = us / 1'000'000; + ts->tv_nsec = (us % 1'000'000) * 1000; + return 0; + } + case ORBIS_CLOCK_VIRTUAL: { +#ifdef _WIN64 + FILETIME ct, et, kt, ut; + if (!GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) { + return EFAULT; + } + const u64 ns = FileTimeTo100Ns(ut); + ts->tv_sec = ns / 10'000'000; + ts->tv_nsec = (ns % 10'000'000) * 100; +#else + struct rusage ru; + const auto res = getrusage(RUSAGE_SELF, &ru); + if (res < 0) { + return res; + } + ts->tv_sec = ru.ru_utime.tv_sec; + ts->tv_nsec = ru.ru_utime.tv_usec * 1000; +#endif + return 0; + } + case ORBIS_CLOCK_PROF: { +#ifdef _WIN64 + FILETIME ct, et, kt, ut; + if (!GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) { + return EFAULT; + } + const u64 ns = FileTimeTo100Ns(kt); + ts->tv_sec = ns / 10'000'000; + ts->tv_nsec = (ns % 10'000'000) * 100; +#else + struct rusage ru; + const auto res = getrusage(RUSAGE_SELF, &ru); + if (res < 0) { + return res; + } + ts->tv_sec = ru.ru_stime.tv_sec; + ts->tv_nsec = ru.ru_stime.tv_usec * 1000; +#endif + return 0; + } + case ORBIS_CLOCK_EXT_NETWORK: + case ORBIS_CLOCK_EXT_DEBUG_NETWORK: + case ORBIS_CLOCK_EXT_AD_NETWORK: + case ORBIS_CLOCK_EXT_RAW_NETWORK: + pclock_id = CLOCK_MONOTONIC; + LOG_ERROR(Lib_Kernel, "unsupported = {} using CLOCK_MONOTONIC", clock_id); + break; + default: + return EINVAL; } - time_t raw_time = time(nullptr); - - if (raw_time == (time_t)(-1)) { - return ORBIS_KERNEL_ERROR_EINVAL; - } - - tp->tv_sec = static_cast(raw_time); - tp->tv_nsec = 0; - - return ORBIS_OK; + return clock_gettime(pclock_id, ts); } -int PS4_SYSV_ABI posix_clock_gettime(s32 clock_id, OrbisKernelTimespec* time) { - int result = sceKernelClockGettime(clock_id, time); - if (result < 0) { - UNREACHABLE(); // TODO return posix error code +int PS4_SYSV_ABI sceKernelClockGettime(s32 clock_id, OrbisKernelTimespec* tp) { + struct timespec ts; + const auto res = orbis_clock_gettime(clock_id, &ts); + if (res < 0) { + return ErrnoToSceKernelError(res); } - return result; + tp->tv_sec = ts.tv_sec; + tp->tv_nsec = ts.tv_nsec; + return ORBIS_OK; } int PS4_SYSV_ABI posix_nanosleep(const OrbisKernelTimespec* rqtp, OrbisKernelTimespec* rmtp) { @@ -318,8 +462,8 @@ void RegisterTime(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("yS8U2TGCe1A", "libScePosix", 1, "libkernel", 1, 1, posix_nanosleep); LIB_FUNCTION("QBi7HCK03hw", "libkernel", 1, "libkernel", 1, 1, sceKernelClockGettime); LIB_FUNCTION("kOcnerypnQA", "libkernel", 1, "libkernel", 1, 1, sceKernelGettimezone); - LIB_FUNCTION("lLMT9vJAck0", "libkernel", 1, "libkernel", 1, 1, posix_clock_gettime); - LIB_FUNCTION("lLMT9vJAck0", "libScePosix", 1, "libkernel", 1, 1, posix_clock_gettime); + LIB_FUNCTION("lLMT9vJAck0", "libkernel", 1, "libkernel", 1, 1, orbis_clock_gettime); + LIB_FUNCTION("lLMT9vJAck0", "libScePosix", 1, "libkernel", 1, 1, orbis_clock_gettime); LIB_FUNCTION("smIj7eqzZE8", "libScePosix", 1, "libkernel", 1, 1, posix_clock_getres); LIB_FUNCTION("0NTHN1NKONI", "libkernel", 1, "libkernel", 1, 1, sceKernelConvertLocaltimeToUtc); LIB_FUNCTION("-o5uEDpN+oY", "libkernel", 1, "libkernel", 1, 1, sceKernelConvertUtcToLocaltime); From 0e315cbd8fe869e98b94f8862143ee5546cb4864 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 9 Mar 2025 08:12:05 -0500 Subject: [PATCH 033/194] sceKernelReleaseDirectMemory fix (#2623) * Fix error return on sceKernelMunmap FreeBSD docs state that len <= 0 is a EINVAL return. * Early return on ReleaseDirectMemory with len = 0 Calls to these two functions with len = 0 cause an assert in CarveDmemArea. Since there's no memory to release, an early return should be safe here. * Remove check for negative length in munmap Addresses review comment --- src/core/libraries/kernel/memory.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 82c5115f1..7b3ac5646 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -79,6 +79,9 @@ s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, } s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len) { + if (len == 0) { + return ORBIS_OK; + } LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len); auto* memory = Core::Memory::Instance(); memory->Free(start, len); @@ -86,6 +89,9 @@ s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len) { } s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) { + if (len == 0) { + return ORBIS_OK; + } auto* memory = Core::Memory::Instance(); memory->Free(start, len); return ORBIS_OK; @@ -507,7 +513,7 @@ s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) { int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) { LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}", fmt::ptr(addr), len); if (len == 0) { - return ORBIS_OK; + return ORBIS_KERNEL_ERROR_EINVAL; } auto* memory = Core::Memory::Instance(); return memory->UnmapMemory(std::bit_cast(addr), len); From aa22d80c3fb65e89d4eadbbb98a948e5cb6c361b Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 9 Mar 2025 15:12:37 +0200 Subject: [PATCH 034/194] New Crowdin updates (#2622) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Swedish) * New translations en_us.ts (Albanian) * New translations en_us.ts (Albanian) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Albanian) * New translations en_us.ts (Albanian) * New translations en_us.ts (Turkish) * New translations en_us.ts (German) * New translations en_us.ts (Turkish) * New translations en_us.ts (German) * New translations en_us.ts (French) * New translations en_us.ts (French) * New translations en_us.ts (French) --- src/qt_gui/translations/de_DE.ts | 38 ++++----- src/qt_gui/translations/fr_FR.ts | 30 +++---- src/qt_gui/translations/pt_BR.ts | 4 +- src/qt_gui/translations/sq_AL.ts | 136 +++++++++++++++---------------- src/qt_gui/translations/sv_SE.ts | 64 +++++++-------- src/qt_gui/translations/tr_TR.ts | 130 ++++++++++++++--------------- src/qt_gui/translations/zh_CN.ts | 26 +++--- 7 files changed, 214 insertions(+), 214 deletions(-) diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index db691b11d..931f93905 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -234,7 +234,7 @@ Name: - Name: + Name: Can't apply cheats before the game is started @@ -463,7 +463,7 @@ Back - Back + Zurück R1 / RB @@ -523,15 +523,15 @@ R: - R: + R: G: - G: + G: B: - B: + B: Override Lightbar Color @@ -562,7 +562,7 @@ Error - Error + Fehler Could not open the file for reading @@ -582,7 +582,7 @@ Help - Help + Hilfe Do you want to reset your custom default config to the original default config? @@ -932,7 +932,7 @@ Trophy - Trophy + Trophäe SFO Viewer for @@ -943,7 +943,7 @@ HelpDialog Quickstart - Quickstart + Schnellstart FAQ @@ -1049,7 +1049,7 @@ Help - Help + Hilfe R1 @@ -1487,7 +1487,7 @@ Name - Name + Name Serial @@ -1503,11 +1503,11 @@ Category - Category + Kategorie Type - Type + Typ App Ver @@ -1519,7 +1519,7 @@ Region - Region + Region Flags @@ -1554,7 +1554,7 @@ System - System + System Console Language @@ -2046,7 +2046,7 @@ Auto Select - Auto Select + Auto-Wählen Directory to install games @@ -2058,7 +2058,7 @@ Video - Video + Video Display Mode @@ -2102,11 +2102,11 @@ Left - Left + Links Right - Right + Rechts Top diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index e815c2b94..46ba1cfc5 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -543,50 +543,50 @@ Unable to Save - Unable to Save + Impossible de sauvegarder Cannot bind axis values more than once - Cannot bind axis values more than once + Impossible de lier les valeurs de l'axe plusieurs fois EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Modifier les raccourcis d'entrée clavier + souris et contrôleur Use Per-Game configs - Use Per-Game configs + Utiliser les configurations pour chaque jeu Error - Error + Erreur Could not open the file for reading - Could not open the file for reading + Impossible d'ouvrir le fichier pour la lecture Could not open the file for writing - Could not open the file for writing + Impossible d'ouvrir le fichier pour l'écriture Save Changes - Save Changes + Enregistrer les Modifications Do you want to save changes? - Do you want to save changes? + Voulez-vous sauvegarder les modifications? Help - Help + Aide Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Voulez-vous réinitialiser votre configuration par défaut personnalisée à la configuration par défaut d'origine ? Do you want to reset this config to your custom default config? @@ -947,7 +947,7 @@ FAQ - FAQ + FAQ Syntax @@ -1157,7 +1157,7 @@ Unable to Save - Unable to Save + Impossible de sauvegarder Cannot bind any unique input more than once @@ -1165,7 +1165,7 @@ Press a key - Press a key + Appuyez sur un bouton Cannot set mapping @@ -2150,7 +2150,7 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Ouvrez le dossier des images/sons des trophées personnalisés:\nVous pouvez ajouter des images personnalisées aux trophées et aux sons.\nAjoutez les fichiers à custom_trophy avec les noms suivants:\ntrophy.wav OU trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote : Le son ne fonctionnera que dans les versions QT. diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 6aefdb36e..8f3975cb1 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -682,7 +682,7 @@ Play Time - Tempo Jogado + Tempo de Jogo Never Played @@ -1089,7 +1089,7 @@ note: click Help Button/Special Keybindings for more information - Nota: clique no botão de Ajuda -> Special Bindings para obter mais informações + Nota: clique no botão de Ajuda e Atalhos Especiais para obter mais informações Face Buttons diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index c371e2323..412735209 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -74,7 +74,7 @@ Select Patch File: - Përzgjidh Skedarin e Arnës: + Përzgjidh skedarin e arnës: Download Patches @@ -138,7 +138,7 @@ File Exists - Skedari Ekziston + Skedari ekziston File already exists. Do you want to replace it? @@ -451,7 +451,7 @@ Use per-game configs - Përdor konfigurime për secilën lojë + Përdor konfigurime të veçanta për secilën lojë L1 / LB @@ -543,58 +543,58 @@ Unable to Save - Unable to Save + Ruajtja Dështoi Cannot bind axis values more than once - Cannot bind axis values more than once + Nuk mund të caktohen vlerat e boshtit më shumë se një herë EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Redakto caktimet e hyrjeve për Tastierën + Miun dhe Dorezën Use Per-Game configs - Use Per-Game configs + Përdor konfigurime për secilën lojë Error - Error + Gabim Could not open the file for reading - Could not open the file for reading + Skedari nuk mund të hapet për lexim Could not open the file for writing - Could not open the file for writing + Skedari nuk mund të hapet për shkrim Save Changes - Save Changes + Ruaj Ndryshimet Do you want to save changes? - Do you want to save changes? + Do të ruash ndryshimet? Help - Help + Ndihmë Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + A do ta rivendosësh konfigurimin tënd të paracaktuar të personalizuar te konfigurimi i paracaktuar origjinal? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + A do ta rivendosësh këtë konfigurim në konfigurimin tënd të paracaktuar të personalizuar? Reset to Default - Reset to Default + Rivendos në të Paracaktuarit @@ -943,23 +943,23 @@ HelpDialog Quickstart - Quickstart + Nisje e shpejtë FAQ - FAQ + Pyetje të Shpeshta Syntax - Syntax + Sintaksa Special Bindings - Special Bindings + Caktimet e Veçantë Keybindings - Keybindings + Caktimet e Tasteve @@ -1013,23 +1013,23 @@ Left Analog Halfmode - Left Analog Halfmode + Mënyra e gjysmuar për levën e majtë hold to move left stick at half-speed - hold to move left stick at half-speed + mbaj shtypur për të lëvizur levën e majtë me gjysmën e shpejtësisë Left Stick - Left Stick + Leva e Majtë Config Selection - Config Selection + Zgjedhja e Konfigurimit Common Config - Common Config + Konfigurim i Përbashkët Use per-game configs @@ -1045,7 +1045,7 @@ Text Editor - Text Editor + Redaktuesi i Tekstit Help @@ -1065,15 +1065,15 @@ Touchpad Click - Touchpad Click + Klikim i Panelit me Prekje Mouse to Joystick - Mouse to Joystick + Miu në Levë *press F7 ingame to activate - *press F7 ingame to activate + *shtyp F7 gjatë lojës për ta aktivizuar R3 @@ -1081,99 +1081,99 @@ Options - Options + Rregullime Mouse Movement Parameters - Mouse Movement Parameters + Parametrat e Lëvizjes së Miut note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + shënim: kliko Butonin e Ndihmës/Caktimet e Tasteve të Veçantë për më shumë informacion Face Buttons - Face Buttons + Butonat Kryesore Triangle - Triangle + Trekëndësh Square - Square + Katror Circle - Circle + Rreth Cross - Cross + Kryq Right Analog Halfmode - Right Analog Halfmode + Mënyra e gjysmuar për levën e djathtë hold to move right stick at half-speed - hold to move right stick at half-speed + mbaj shtypur për të lëvizur levën e djathtë me gjysmën e shpejtësisë Right Stick - Right Stick + Leva e Djathtë Speed Offset (def 0.125): - Speed Offset (def 0.125): + Ofset i Shpejtësisë (paracaktuar 0.125): Copy from Common Config - Copy from Common Config + Kopjo nga Konfigurimi i Përbashkët Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Ofseti i Zonës së Vdekur (paracaktuar 0.50): Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Shumëzuesi i Shpejtësisë (paracaktuar 1.0): Common Config Selected - Common Config Selected + Konfigurimi i Përbashkët i Zgjedhur This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Ky buton kopjon caktimet nga Konfigurimi i Përbashkët në profilin e zgjedhur aktualisht, dhe nuk mund të përdoret kur profili i zgjedhur aktualisht është Konfigurimi i Përbashkët. Copy values from Common Config - Copy values from Common Config + Kopjo vlerat nga Konfigurimi i Përbashkët Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + A dëshiron të mbishkruash caktimet ekzistuese me ato nga Konfigurimi i Përbashkët? Unable to Save - Unable to Save + Ruajtja Dështoi Cannot bind any unique input more than once - Cannot bind any unique input more than once + Asnjë hyrje unike nuk mund të caktohet më shumë se një herë Press a key - Press a key + Shtyp një tast Cannot set mapping - Cannot set mapping + Caktimi nuk u vendos dot Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + Rrota e miut nuk mund të caktohet për daljet e levës @@ -1674,7 +1674,7 @@ Enable Shaders Dumping - Aktivizo Zbrazjen e Shaders-ave + Aktivizo Zbrazjen e Shader-ave Enable NULL GPU @@ -1750,7 +1750,7 @@ Always Show Changelog - Shfaq gjithmonë regjistrin e ndryshimeve + Shfaq gjithmonë ditarin e ndryshimeve Update Channel @@ -1890,7 +1890,7 @@ Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Sjellja e butonit mbrapa:\nLejon të përcaktohet se në cilën pjesë të tastierës prekëse do të imitojë një prekje butoni mbrapa. + Sjellja e butonit mbrapa:\nLejon të përcaktohet se në cilën pjesë të panelit me prekje të dorezës do të imitojë një prekje butoni mbrapa. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1918,15 +1918,15 @@ Touchpad Left - Tastiera prekëse majtas + Paneli me Prekje Majtas Touchpad Right - Tastiera prekëse djathtas + Paneli me Prekje Djathtas Touchpad Center - Tastiera prekëse në qendër + Paneli me Prekje në Qendër None @@ -2122,35 +2122,35 @@ Portable User Folder - Portable User Folder + Dosja Portative e Përdoruesit Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Krijo Dosje Portative të Përdoruesit nga Dosja e Përbashkët e Përdoruesit Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Dosja portative e përdoruesit:\nRuan cilësimet dhe të dhënat të shadPS4 që do të zbatohen vetëm për konstruktin e shadPS4 të vendosur në dosjen aktuale. Rinis aplikacionin pasi të krijosh dosjen portative te përdoruesit për ta përdorur. Cannot create portable user folder - Cannot create portable user folder + Dosja portative e përdoruesit nuk u krijua dot %1 already exists - %1 already exists + %1 tashmë ekziston Portable user folder created - Portable user folder created + Dosja portative e përdoruesit u krijua %1 successfully created. - %1 successfully created. + %1 u krijua me sukses. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Hap dosjen e imazheve/tingujve të trofeve të personalizuar:\nMund të shtosh imazhe të personalizuara për trofetë dhe një audio.\nShto skedarët në dosjen custom_trophy me emrat që vijojnë:\ntrophy.wav ose trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nShënim: Tingulli do të punojë vetëm në versionet QT. diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index 8120cb403..eb453b4d9 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -543,58 +543,58 @@ Unable to Save - Unable to Save + Kunde inte spara Cannot bind axis values more than once - Cannot bind axis values more than once + Kan inte binda axelvärden fler än en gång EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Redigera inmatningsbindningar för tangentbord + mus och kontroller Use Per-Game configs - Use Per-Game configs + Använd konfigurationer per-spel Error - Error + Fel Could not open the file for reading - Could not open the file for reading + Kunde inte öppna filen för läsning Could not open the file for writing - Could not open the file for writing + Kunde inte öppna filen för skrivning Save Changes - Save Changes + Spara ändringar Do you want to save changes? - Do you want to save changes? + Vill du spara ändringarna? Help - Help + Hjälp Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Vill du återställa din anpassade standardkonfiguration till ursprungliga standardkonfigurationen? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Vill du återställa denna konfiguration till din anpassade standardkonfiguration? Reset to Default - Reset to Default + Återställ till standard @@ -943,23 +943,23 @@ HelpDialog Quickstart - Quickstart + Snabbstart FAQ - FAQ + Frågor och svar Syntax - Syntax + Syntax Special Bindings - Special Bindings + Speciella bindningar Keybindings - Keybindings + Tangentbindningar @@ -1141,39 +1141,39 @@ Common Config Selected - Common Config Selected + Gemensam konfiguration valdes This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Den här knappen kopierar mappningar från gemensam konfiguration till den aktuella valda profilen och kan inte användas när den aktuella valda profilen är gemensam konfiguration. Copy values from Common Config - Copy values from Common Config + Kopiera värden från gemensam konfiguration Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + Vill du skriva över befintliga mappningar med mappningarna från gemensam konfiguration? Unable to Save - Unable to Save + Kunde inte spara Cannot bind any unique input more than once - Cannot bind any unique input more than once + Kan inte binda någon unik inmatning fler än en gång Press a key - Press a key + Tryck på en tangent Cannot set mapping - Cannot set mapping + Kan inte ställa in mappning Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + Mushjulet kan inte mappas till spakutmatningar @@ -2134,23 +2134,23 @@ Cannot create portable user folder - Cannot create portable user folder + Kan inte skapa portabel användarmapp %1 already exists - %1 already exists + %1 finns redan Portable user folder created - Portable user folder created + Portabel användarmapp skapad %1 successfully created. - %1 successfully created. + %1 skapades. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Öppna mappen för anpassade trofébilder/ljud:\nDu kan lägga till egna bilder till troféerna och ett ljud.\nLägg till filerna i custom_trophy med följande namn:\ntrophy.wav ELLER trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservera: Ljudet fungerar endast i QT-versioner. diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 447cebf5f..095c73c7b 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -313,7 +313,7 @@ Update - Güncelle + Güncelleme No @@ -431,7 +431,7 @@ Left Stick Deadzone (def:2 max:127) - Sol Analog Ölü Bölgesi (şu an:2, en çok:127) + Sol Analog Ölü Bölgesi (varsayılan: 2, en çok: 127) Left Deadzone @@ -447,7 +447,7 @@ Common Config - Genel Yapılandırma + Ortak Yapılandırma Use per-game configs @@ -507,7 +507,7 @@ Right Stick Deadzone (def:2, max:127) - Sağ Analog Ölü Bölgesi (şu an:2, en çok:127) + Sağ Analog Ölü Bölgesi (varsayılan: 2, en çok: 127) Right Deadzone @@ -543,11 +543,11 @@ Unable to Save - Unable to Save + Kaydedilemedi Cannot bind axis values more than once - Cannot bind axis values more than once + Eksen değerleri birden fazla kez bağlanamaz @@ -558,43 +558,43 @@ Use Per-Game configs - Use Per-Game configs + Oyuna özel yapılandırma kullan Error - Error + Hata Could not open the file for reading - Could not open the file for reading + Dosya okumak için açılamadı Could not open the file for writing - Could not open the file for writing + Dosya yazmak için açılamadı Save Changes - Save Changes + Değişiklikleri Kaydet Do you want to save changes? - Do you want to save changes? + Değişiklikleri kaydetmek istiyor musunuz? Help - Help + Yardım Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Özel varsayılan yapılandırmanızı, orijinal varsayılan yapılandırmaya sıfırlamak istiyor musunuz? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Bu yapılandırmayı özel varsayılan yapılandırmanıza sıfırlamak istiyor musunuz? Reset to Default - Reset to Default + Varsayılanlara Sıfırla @@ -639,7 +639,7 @@ Directory to install DLC - İndirilebilir içeriğin yükleneceği dizin + DLC'lerin yükleneceği dizin @@ -828,7 +828,7 @@ Delete DLC - İndirilebilir İçeriği Sil + DLC'yi Sil Delete Trophy @@ -884,11 +884,11 @@ This game has no DLC to delete! - Bu oyunun silinecek indirilebilir içeriği yok! + Bu oyunun silinecek DLC'si yok! DLC - İndirilebilir İçerik + DLC Delete %1 @@ -912,7 +912,7 @@ No log file found for this game! - No log file found for this game! + Bu oyun için günlük dosyası bulunamadı! Failed to convert icon. @@ -943,23 +943,23 @@ HelpDialog Quickstart - Quickstart + Hızlı Başlangıç FAQ - FAQ + SSS Syntax - Syntax + Sözdizimi Special Bindings - Special Bindings + Özel Atamalar Keybindings - Keybindings + Tuş Atamaları @@ -997,7 +997,7 @@ unmapped - unmapped + atanmamış Left @@ -1013,11 +1013,11 @@ Left Analog Halfmode - Left Analog Halfmode + Sol Analog Yarı Modu hold to move left stick at half-speed - hold to move left stick at half-speed + sol analogu yarı hızda hareket ettirmek için basılı tutun Left Stick @@ -1029,7 +1029,7 @@ Common Config - Genel Yapılandırma + Ortak Yapılandırma Use per-game configs @@ -1065,15 +1065,15 @@ Touchpad Click - Touchpad Click + Dokunmatik Yüzey Tıklaması Mouse to Joystick - Mouse to Joystick + Mouse'dan Kontrolcü *press F7 ingame to activate - *press F7 ingame to activate + *Etkinleştirmek için oyundayken F7'ye basın R3 @@ -1089,7 +1089,7 @@ note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + Not: Daha fazla bilgi için Yardım ya da Özel Atamalar'a tıklayın Face Buttons @@ -1113,11 +1113,11 @@ Right Analog Halfmode - Right Analog Halfmode + Sağ Analog Yarı Modu hold to move right stick at half-speed - hold to move right stick at half-speed + sağ analogu yarı hızda hareket ettirmek için basılı tutun Right Stick @@ -1129,7 +1129,7 @@ Copy from Common Config - Genel Yapılandırmadan Kopyala + Ortak Yapılandırmadan Kopyala Deadzone Offset (def 0.50): @@ -1137,11 +1137,11 @@ Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Hız Çarpanı (varsayılan 1.0): Common Config Selected - Common Config Selected + Ortak Yapılandırma Seçildi This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. @@ -1149,38 +1149,38 @@ Copy values from Common Config - Copy values from Common Config + Ortak Yapılandırmadan Değerleri Kopyala Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + Mevcut atamaların üzerine ortak yapılandırmadaki atamaları yazmak istiyor musunuz? Unable to Save - Unable to Save + Kaydedilemedi Cannot bind any unique input more than once - Cannot bind any unique input more than once + Herhangi bir benzersiz girdi birden fazla kez bağlanamaz Press a key - Press a key + Bir tuşa basın Cannot set mapping - Cannot set mapping + Atama ayarlanamıyor Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + Mouse tekerleği analog çıkışlarına atanamaz MainWindow Open/Add Elf Folder - Elf Klasörünü Aç/Ekle + Elf Klasörü Aç/Ekle Install Packages (PKG) @@ -1236,11 +1236,11 @@ Tiny - Küçük + Minik Small - Ufak + Küçük Medium @@ -1464,7 +1464,7 @@ PKG is a patch or DLC, please install the game first! - PKG bir yama ya da indirilebilir içerik, lütfen önce oyunu yükleyin! + PKG bir yama ya da DLC, lütfen önce oyunu yükleyin! Game is already running! @@ -1678,11 +1678,11 @@ Enable NULL GPU - NULL GPU'yu Etkinleştir + NULL GPU'yu Etkinleştir Enable HDR - HDR'yi Etkinleştir + HDR Paths @@ -1826,7 +1826,7 @@ Point your mouse at an option to display its description. - Seçenek üzerinde farenizi tutarak açıklamasını görüntüleyin. + Açıklamasını görüntülemek için mouse'unuzu bir seçeneğin üzerine getirin. Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. @@ -1886,7 +1886,7 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - Hareket etmeden sonra imlecin kaybolacağı süreyi ayarlayın. + İmleç İçin Hareketsizlik Zaman Aşımı:\nBoşta kalan imlecin kendini kaç saniye sonra gizleyeceğidir. Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. @@ -1990,7 +1990,7 @@ 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. - 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. + Çökme Tanılamaları:\nÇökme anındaki Vulkan durumu hakkında bilgi içeren bir .yaml dosyası oluşturur.\n'Cihaz kayıp' hatalarını ayıklamak için kullanışlıdır. Bunu etkinleştirdiyseniz, Ana Bilgisayar ve Konuk Hata Ayıklama İşaretleyicileri'ni etkinleştirmelisiniz.\nIntel GPU'lar üzerinde çalışmaz.\nÇalışabilmesi için Vulkan Doğrulama Katmanları'nın etkinleştirilmesine ve Vulkan SDK'sine ihtiyacınız vardır. Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. @@ -2110,11 +2110,11 @@ Top - Top + Üst Bottom - Bottom + Alt Notification Duration @@ -2122,35 +2122,35 @@ Portable User Folder - Portable User Folder + Taşınabilir Kullanıcı Klasörü Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Ortak Kullanıcı Klasöründen Taşınabilir Kullanıcı Klasörü Oluştur Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Taşınabilir kullanıcı klasörü:\nYalnızca geçerli klasörde bulunan shadPS4 derlemesine uygulanacak shadPS4 ayarlarını ve verilerini depolar. Kullanmaya başlamak için taşınabilir kullanıcı klasörünü oluşturduktan sonra uygulamayı yeniden başlatın. Cannot create portable user folder - Cannot create portable user folder + Taşınabilir kullanıcı klasörü oluşturulamıyor %1 already exists - %1 already exists + %1 zaten mevcut Portable user folder created - Portable user folder created + Taşınabilir kullanıcı klasörü oluşturuldu %1 successfully created. - %1 successfully created. + %1 başarıyla oluşturuldu. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Özel kupa görüntüleri/sesleri klasörünü aç:\nKupalara özel görüntüler ve sesler ekleyebilirsiniz.\nDosyaları aşağıdaki adlarla custom_trophy'ye ekleyin:\ntrophy.wav ya da trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNot: Ses yalnızca QT sürümlerinde çalışacaktır. diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index fe13f4092..f761fe8c4 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -411,7 +411,7 @@ D-Pad - D-Pad + 十字键 Up @@ -487,23 +487,23 @@ Face Buttons - 正面按钮 + 功能键(动作键) Triangle / Y - Triangle / Y + 三角 / Y Square / X - Square / X + 方框 / X Circle / B - Circle / B + 圈 / B Cross / A - Cross / A + 叉 / A Right Stick Deadzone (def:2, max:127) @@ -989,7 +989,7 @@ D-Pad - D-Pad + 十字键 Up @@ -1081,7 +1081,7 @@ Options - Options + 选项设置 Mouse Movement Parameters @@ -1093,23 +1093,23 @@ Face Buttons - 正面按钮 + 功能键(动作键) Triangle - Triangle + 三角 Square - Square + 方框 Circle - Circle + Cross - Cross + Right Analog Halfmode From 74c2c6c74ffdacd23bfc07b323e8f7f238eeee59 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Sun, 9 Mar 2025 21:13:14 +0800 Subject: [PATCH 035/194] Make button bar translatable in controller/KBM dialog (#2625) --- src/qt_gui/control_settings.cpp | 5 +++++ src/qt_gui/kbm_gui.cpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index 885e36680..4206e45b8 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -28,6 +28,11 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, Q } }); + ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); + ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply")); + ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults")); + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); connect(ui->ProfileComboBox, &QComboBox::currentTextChanged, this, [this] { diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index b78a6cf75..c148884e9 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -63,6 +63,11 @@ KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* } }); + ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); + ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply")); + ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults")); + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + connect(ui->HelpButton, &QPushButton::clicked, this, &KBMSettings::onHelpClicked); connect(ui->TextEditorButton, &QPushButton::clicked, this, [this]() { auto kbmWindow = new EditorDialog(this); From 20ea0ee1909de7355491c562f05fb19e91d936b2 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 9 Mar 2025 16:17:33 -0500 Subject: [PATCH 036/194] Handle error behavior in sceSysmoduleGetModuleInfoForUnwind stub (#2629) * Stubby implementation of sceSysmoduleGetModuleInfoForUnwind * Update sysmodule.cpp * Minor cleanup --- src/core/libraries/kernel/process.cpp | 14 +------------- src/core/libraries/kernel/process.h | 15 +++++++++++++++ src/core/libraries/system/sysmodule.cpp | 8 ++++++-- src/core/libraries/system/sysmodule.h | 2 +- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index 58628867a..cb61bbda9 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -85,19 +85,7 @@ s32 PS4_SYSV_ABI sceKernelDlsym(s32 handle, const char* symbol, void** addrp) { return ORBIS_OK; } -static constexpr size_t ORBIS_DBG_MAX_NAME_LENGTH = 256; - -struct OrbisModuleInfoForUnwind { - u64 st_size; - std::array name; - VAddr eh_frame_hdr_addr; - VAddr eh_frame_addr; - u64 eh_frame_size; - VAddr seg0_addr; - u64 seg0_size; -}; - -s32 PS4_SYSV_ABI sceKernelGetModuleInfoForUnwind(VAddr addr, int flags, +s32 PS4_SYSV_ABI sceKernelGetModuleInfoForUnwind(VAddr addr, s32 flags, OrbisModuleInfoForUnwind* info) { if (flags >= 3) { std::memset(info, 0, sizeof(OrbisModuleInfoForUnwind)); diff --git a/src/core/libraries/kernel/process.h b/src/core/libraries/kernel/process.h index 0340a9793..09e4276fb 100644 --- a/src/core/libraries/kernel/process.h +++ b/src/core/libraries/kernel/process.h @@ -11,10 +11,25 @@ class SymbolsResolver; namespace Libraries::Kernel { +static constexpr size_t ORBIS_DBG_MAX_NAME_LENGTH = 256; + +struct OrbisModuleInfoForUnwind { + u64 st_size; + std::array name; + VAddr eh_frame_hdr_addr; + VAddr eh_frame_addr; + u64 eh_frame_size; + VAddr seg0_addr; + u64 seg0_size; +}; + int PS4_SYSV_ABI sceKernelIsNeoMode(); int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver); +s32 PS4_SYSV_ABI sceKernelGetModuleInfoForUnwind(VAddr addr, s32 flags, + OrbisModuleInfoForUnwind* info); + void RegisterProcess(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Kernel diff --git a/src/core/libraries/system/sysmodule.cpp b/src/core/libraries/system/sysmodule.cpp index 350f1317b..6c73764f2 100644 --- a/src/core/libraries/system/sysmodule.cpp +++ b/src/core/libraries/system/sysmodule.cpp @@ -7,6 +7,7 @@ #include "common/logging/log.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/process.h" #include "core/libraries/libs.h" #include "core/libraries/system/sysmodule.h" #include "core/libraries/system/system_error.h" @@ -18,9 +19,12 @@ int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind() { +s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, void* info) { LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; + Kernel::OrbisModuleInfoForUnwind module_info; + module_info.st_size = 0x130; + s32 res = Kernel::sceKernelGetModuleInfoForUnwind(addr, flags, &module_info); + return res; } int PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule() { diff --git a/src/core/libraries/system/sysmodule.h b/src/core/libraries/system/sysmodule.h index 3630fb58e..dfbdca162 100644 --- a/src/core/libraries/system/sysmodule.h +++ b/src/core/libraries/system/sysmodule.h @@ -152,7 +152,7 @@ enum class OrbisSysModuleInternal : u32 { }; int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal(); -int PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(); +s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, void* info); int PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule(); int PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded(); int PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id); From a711f4d86eb760047fa9183dc501a91d255c0e62 Mon Sep 17 00:00:00 2001 From: panzone91 <150828896+panzone91@users.noreply.github.com> Date: Sun, 9 Mar 2025 22:44:17 +0100 Subject: [PATCH 037/194] libkernel: improve module finding in sceKernelLoadStartModule (#2541) * libkernel: improve module finding in sceKernelLoadStartModule * clang-format * asserts for system module * fixes * linting * cleaning * fix linker impl --- src/core/libraries/kernel/process.cpp | 45 ++++++++++----------------- src/core/linker.cpp | 29 +++++++++++++++++ src/core/linker.h | 2 ++ src/core/module.cpp | 2 +- src/core/module.h | 2 +- 5 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index cb61bbda9..ed833068c 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -32,44 +32,33 @@ void* PS4_SYSV_ABI sceKernelGetProcParam() { return linker->GetProcParam(); } -s32 PS4_SYSV_ABI sceKernelLoadStartModule(const char* moduleFileName, size_t args, const void* argp, +s32 PS4_SYSV_ABI sceKernelLoadStartModule(const char* moduleFileName, u64 args, const void* argp, u32 flags, const void* pOpt, int* pRes) { LOG_INFO(Lib_Kernel, "called filename = {}, args = {}", moduleFileName, args); - - if (flags != 0) { - return ORBIS_KERNEL_ERROR_EINVAL; - } + ASSERT(flags == 0); auto* mnt = Common::Singleton::Instance(); - const auto path = mnt->GetHostPath(moduleFileName); - - // Load PRX module and relocate any modules that import it. auto* linker = Common::Singleton::Instance(); - u32 handle = linker->FindByName(path); - if (handle != -1) { + + std::filesystem::path path; + std::string guest_path(moduleFileName); + + const bool is_root = guest_path[0] == '/'; + if (is_root || guest_path.contains('/')) { + path = mnt->GetHostPath(guest_path); + } else if (!guest_path.contains('/')) { + path = mnt->GetHostPath("/app0/" + guest_path); + } + + if (const s32 handle = linker->LoadAndStartModule(path, args, argp, pRes); handle != -1) { return handle; } - handle = linker->LoadModule(path, true); - if (handle == -1) { - return ORBIS_KERNEL_ERROR_ESRCH; - } - auto* module = linker->GetModule(handle); - linker->RelocateAnyImports(module); - // If the new module has a TLS image, trigger its load when TlsGetAddr is called. - if (module->tls.image_size != 0) { - linker->AdvanceGenerationCounter(); + if (is_root) { + UNREACHABLE(); } - // Retrieve and verify proc param according to libkernel. - u64* param = module->GetProcParam(); - ASSERT_MSG(!param || param[0] >= 0x18, "Invalid module param size: {}", param[0]); - s32 ret = module->Start(args, argp, param); - if (pRes) { - *pRes = ret; - } - - return handle; + return ORBIS_KERNEL_ERROR_ENOENT; } s32 PS4_SYSV_ABI sceKernelDlsym(s32 handle, const char* symbol, void** addrp) { diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 2461edcb2..18ae62f4b 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -139,6 +139,35 @@ s32 Linker::LoadModule(const std::filesystem::path& elf_name, bool is_dynamic) { return m_modules.size() - 1; } +s32 Linker::LoadAndStartModule(const std::filesystem::path& path, u64 args, const void* argp, + int* pRes) { + u32 handle = FindByName(path); + if (handle != -1) { + return handle; + } + handle = LoadModule(path, true); + if (handle == -1) { + return -1; + } + auto* module = GetModule(handle); + RelocateAnyImports(module); + + // If the new module has a TLS image, trigger its load when TlsGetAddr is called. + if (module->tls.image_size != 0) { + AdvanceGenerationCounter(); + } + + // Retrieve and verify proc param according to libkernel. + u64* param = module->GetProcParam(); + ASSERT_MSG(!param || param[0] >= 0x18, "Invalid module param size: {}", param[0]); + s32 ret = module->Start(args, argp, param); + if (pRes) { + *pRes = ret; + } + + return handle; +} + Module* Linker::FindByAddress(VAddr address) { for (auto& module : m_modules) { const VAddr base = module->GetBaseAddress(); diff --git a/src/core/linker.h b/src/core/linker.h index 9c07400c4..63dfc37e8 100644 --- a/src/core/linker.h +++ b/src/core/linker.h @@ -144,6 +144,8 @@ public: void FreeTlsForNonPrimaryThread(void* pointer); s32 LoadModule(const std::filesystem::path& elf_name, bool is_dynamic = false); + s32 LoadAndStartModule(const std::filesystem::path& path, u64 args, const void* argp, + int* pRes); Module* FindByAddress(VAddr address); void Relocate(Module* module); diff --git a/src/core/module.cpp b/src/core/module.cpp index 70afb932c..a18c1141a 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -94,7 +94,7 @@ Module::Module(Core::MemoryManager* memory_, const std::filesystem::path& file_, Module::~Module() = default; -s32 Module::Start(size_t args, const void* argp, void* param) { +s32 Module::Start(u64 args, const void* argp, void* param) { LOG_INFO(Core_Linker, "Module started : {}", name); const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress(); return ExecuteGuest(reinterpret_cast(addr), args, argp, param); diff --git a/src/core/module.h b/src/core/module.h index 630c5d583..320680485 100644 --- a/src/core/module.h +++ b/src/core/module.h @@ -203,7 +203,7 @@ public: return (rela_bits[index >> 3] >> (index & 7)) & 1; } - s32 Start(size_t args, const void* argp, void* param); + s32 Start(u64 args, const void* argp, void* param); void LoadModuleToMemory(u32& max_tls_index); void LoadDynamicInfo(); void LoadSymbols(); From 46b1bafa1741d14e6c9c8aefe379c62f1d8e4477 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 10 Mar 2025 11:11:59 +0200 Subject: [PATCH 038/194] [ci skip] Qt GUI: Update Translation. (#2636) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 924f2d6e5..9c9d56076 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow From 8bcdd9c068f262f03de3e5e5d264081834af7947 Mon Sep 17 00:00:00 2001 From: Pavel <68122101+red-prig@users.noreply.github.com> Date: Mon, 10 Mar 2025 12:12:12 +0300 Subject: [PATCH 039/194] Fix sceKernelLoadStartModule (#2635) --- src/core/libraries/kernel/process.cpp | 35 ++++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index ed833068c..d61ee37ac 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -43,19 +43,30 @@ s32 PS4_SYSV_ABI sceKernelLoadStartModule(const char* moduleFileName, u64 args, std::filesystem::path path; std::string guest_path(moduleFileName); - const bool is_root = guest_path[0] == '/'; - if (is_root || guest_path.contains('/')) { + s32 handle = -1; + + if (guest_path[0] == '/') { + // try load /system/common/lib/ +path + // try load /system/priv/lib/ +path path = mnt->GetHostPath(guest_path); - } else if (!guest_path.contains('/')) { - path = mnt->GetHostPath("/app0/" + guest_path); - } - - if (const s32 handle = linker->LoadAndStartModule(path, args, argp, pRes); handle != -1) { - return handle; - } - - if (is_root) { - UNREACHABLE(); + handle = linker->LoadAndStartModule(path, args, argp, pRes); + if (handle != -1) + return handle; + } else { + if (!guest_path.contains('/')) { + path = mnt->GetHostPath("/app0/" + guest_path); + handle = linker->LoadAndStartModule(path, args, argp, pRes); + if (handle != -1) + return handle; + // if ((flags & 0x10000) != 0) + // try load /system/priv/lib/ +basename + // try load /system/common/lib/ +basename + } else { + path = mnt->GetHostPath(guest_path); + handle = linker->LoadAndStartModule(path, args, argp, pRes); + if (handle != -1) + return handle; + } } return ORBIS_KERNEL_ERROR_ENOENT; From ba1eb298dec48f88431068390232e3978ae07bda Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 10 Mar 2025 11:12:29 +0200 Subject: [PATCH 040/194] New Crowdin updates (#2631) * New translations en_us.ts (French) * New translations en_us.ts (Spanish) * New translations en_us.ts (Polish) * New translations en_us.ts (Spanish) * New translations en_us.ts (Albanian) --- src/qt_gui/translations/es_ES.ts | 16 +++++++-------- src/qt_gui/translations/fr_FR.ts | 34 ++++++++++++++++---------------- src/qt_gui/translations/pl_PL.ts | 10 +++++----- src/qt_gui/translations/sq_AL.ts | 2 +- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 9f7fb3721..a727792ba 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -594,7 +594,7 @@ Reset to Default - Reset to Default + Resetear A Valores Originales @@ -1037,11 +1037,11 @@ L1 - L1 + L1 L2 - L2 + L2 Text Editor @@ -1053,23 +1053,23 @@ R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click - Touchpad Click + Clic de pantalla táctil Mouse to Joystick - Mouse to Joystick + Ratón A Joystick *press F7 ingame to activate diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 46ba1cfc5..75a770c09 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -590,11 +590,11 @@ Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Voulez-vous réinitialiser cette configuration à votre configuration personnalisée par défaut ? Reset to Default - Reset to Default + Rétablir par défaut @@ -943,7 +943,7 @@ HelpDialog Quickstart - Quickstart + Démarrage rapide FAQ @@ -951,15 +951,15 @@ Syntax - Syntax + Syntaxe Special Bindings - Special Bindings + Special bindings Keybindings - Keybindings + Raccourcis @@ -1141,19 +1141,19 @@ Common Config Selected - Common Config Selected + Configuration courante sélectionnée This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Ce bouton copie les mappings de la configuration commune vers le profil actuellement sélectionné, et ne peut pas être utilisé lorsque le profil actuellement sélectionné est la configuration commune. Copy values from Common Config - Copy values from Common Config + Copier à partir de la configuration commune Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + Voulez-vous remplacer les mappings existants par les mappings de la configuration commune ? Unable to Save @@ -1161,7 +1161,7 @@ Cannot bind any unique input more than once - Cannot bind any unique input more than once + Impossible de lier une entrée unique plus d'une fois Press a key @@ -1169,11 +1169,11 @@ Cannot set mapping - Cannot set mapping + Impossible de définir le mapping Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + La molette de la souris ne peut pas être affectée aux sorties de la manette @@ -2134,19 +2134,19 @@ Cannot create portable user folder - Cannot create portable user folder + Impossible de créer le dossier utilisateur portable %1 already exists - %1 already exists + %1 existe déjà Portable user folder created - Portable user folder created + Dossier utilisateur portable créé %1 successfully created. - %1 successfully created. + %1 a été créé avec succès. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index d179d2172..faa464792 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -562,7 +562,7 @@ Error - Error + Błąd Could not open the file for reading @@ -574,15 +574,15 @@ Save Changes - Save Changes + Zapisać zmiany Do you want to save changes? - Do you want to save changes? + Czy chcesz zapisać zmiany? Help - Help + Pomóc Do you want to reset your custom default config to the original default config? @@ -594,7 +594,7 @@ Reset to Default - Reset to Default + Zresetować do domyślnych diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 412735209..da64729d0 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -411,7 +411,7 @@ D-Pad - D-Pad + Shigjetat Up From f663176a5d93decac9ea3dca092649a9c9c2d638 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Wed, 12 Mar 2025 15:33:30 -0300 Subject: [PATCH 041/194] FidelityFX FSR implementation (#2624) * host_shaders: support for includes * video_core: add a simpler vulkan asserts * video_core: refactored post processing pipeline to another file * renderer_vulkan: add define param to compile shader utility * video_core: fsr implementation * devtools: show resolution & fsr state --- CMakeLists.txt | 5 + REUSE.toml | 7 +- src/common/string_literal.h | 15 + src/core/debug_state.h | 4 + src/core/devtools/layer.cpp | 18 +- src/core/devtools/widget/frame_graph.cpp | 15 +- src/core/libraries/kernel/kernel.h | 10 +- src/core/libraries/videoout/video_out.cpp | 2 +- src/video_core/host_shaders/CMakeLists.txt | 1 + .../host_shaders/StringShaderHeader.cmake | 39 +- src/video_core/host_shaders/fsr.comp | 91 + src/video_core/host_shaders/fsr/ffx_a.h | 2657 +++++++++++++++++ src/video_core/host_shaders/fsr/ffx_fsr1.h | 1200 ++++++++ src/video_core/host_shaders/post_process.frag | 2 +- .../host_shaders/source_shader.h.in | 4 +- .../renderer_vulkan/host_passes/fsr_pass.cpp | 445 +++ .../renderer_vulkan/host_passes/fsr_pass.h | 56 + .../renderer_vulkan/host_passes/pp_pass.cpp | 255 ++ .../renderer_vulkan/host_passes/pp_pass.h | 34 + src/video_core/renderer_vulkan/vk_platform.h | 23 + .../renderer_vulkan/vk_presenter.cpp | 335 +-- src/video_core/renderer_vulkan/vk_presenter.h | 31 +- .../renderer_vulkan/vk_shader_util.cpp | 50 +- .../renderer_vulkan/vk_shader_util.h | 3 +- src/video_core/texture_cache/image.cpp | 5 + src/video_core/texture_cache/image.h | 1 + 26 files changed, 4984 insertions(+), 324 deletions(-) create mode 100644 src/common/string_literal.h create mode 100644 src/video_core/host_shaders/fsr.comp create mode 100644 src/video_core/host_shaders/fsr/ffx_a.h create mode 100644 src/video_core/host_shaders/fsr/ffx_fsr1.h create mode 100644 src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp create mode 100644 src/video_core/renderer_vulkan/host_passes/fsr_pass.h create mode 100644 src/video_core/renderer_vulkan/host_passes/pp_pass.cpp create mode 100644 src/video_core/renderer_vulkan/host_passes/pp_pass.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b8d82a11c..99620e7d3 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -584,6 +584,7 @@ set(COMMON src/common/logging/backend.cpp src/common/spin_lock.h src/common/stb.cpp src/common/stb.h + src/common/string_literal.h src/common/string_util.cpp src/common/string_util.h src/common/thread.cpp @@ -845,6 +846,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderer_vulkan/vk_shader_util.h src/video_core/renderer_vulkan/vk_swapchain.cpp src/video_core/renderer_vulkan/vk_swapchain.h + src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp + src/video_core/renderer_vulkan/host_passes/fsr_pass.h + src/video_core/renderer_vulkan/host_passes/pp_pass.cpp + src/video_core/renderer_vulkan/host_passes/pp_pass.h src/video_core/texture_cache/image.cpp src/video_core/texture_cache/image.h src/video_core/texture_cache/image_info.cpp diff --git a/REUSE.toml b/REUSE.toml index 5cf7b01bf..d9b307d39 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -111,4 +111,9 @@ SPDX-License-Identifier = "CC0-1.0" [[annotations]] path = "cmake/CMakeRC.cmake" SPDX-FileCopyrightText = "Copyright (c) 2017 vector-of-bool " -SPDX-License-Identifier = "MIT" \ No newline at end of file +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "src/video_core/host_shaders/fsr/*" +SPDX-FileCopyrightText = "Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved." +SPDX-License-Identifier = "MIT" diff --git a/src/common/string_literal.h b/src/common/string_literal.h new file mode 100644 index 000000000..9f64f62bd --- /dev/null +++ b/src/common/string_literal.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +template +struct StringLiteral { + static constexpr size_t len = N; + + constexpr StringLiteral(const C (&str)[N]) { + std::copy_n(str, N, value); + } + + C value[N]{}; +}; diff --git a/src/core/debug_state.h b/src/core/debug_state.h index 217efd1a9..b1b8c00d6 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -158,6 +158,10 @@ public: float Framerate = 1.0f / 60.0f; float FrameDeltaTime; + std::pair game_resolution{}; + std::pair output_resolution{}; + bool is_using_fsr{}; + void ShowDebugMessage(std::string message) { if (message.empty()) { return; diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 603d76df5..5f0fd0c95 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -81,8 +81,24 @@ void L::DrawMenuBar() { ImGui::EndMenu(); } if (BeginMenu("Display")) { + auto& pp_settings = presenter->GetPPSettingsRef(); if (BeginMenu("Brightness")) { - SliderFloat("Gamma", &presenter->GetGammaRef(), 0.1f, 2.0f); + SliderFloat("Gamma", &pp_settings.gamma, 0.1f, 2.0f); + ImGui::EndMenu(); + } + if (BeginMenu("FSR")) { + auto& fsr = presenter->GetFsrSettingsRef(); + Checkbox("FSR Enabled", &fsr.enable); + BeginDisabled(!fsr.enable); + { + Checkbox("RCAS", &fsr.use_rcas); + BeginDisabled(!fsr.use_rcas); + { + SliderFloat("RCAS Attenuation", &fsr.rcas_attenuation, 0.0, 3.0); + } + EndDisabled(); + } + EndDisabled(); ImGui::EndMenu(); } ImGui::EndMenu(); diff --git a/src/core/devtools/widget/frame_graph.cpp b/src/core/devtools/widget/frame_graph.cpp index d93de571a..8f3e133f5 100644 --- a/src/core/devtools/widget/frame_graph.cpp +++ b/src/core/devtools/widget/frame_graph.cpp @@ -74,7 +74,7 @@ void FrameGraph::Draw() { if (!is_open) { return; } - SetNextWindowSize({340.0, 185.0f}, ImGuiCond_FirstUseEver); + SetNextWindowSize({308.0, 270.0f}, ImGuiCond_FirstUseEver); if (Begin("Video debug info", &is_open)) { const auto& ctx = *GImGui; const auto& io = ctx.IO; @@ -88,13 +88,20 @@ void FrameGraph::Draw() { frameRate = 1000.0f / deltaTime; } + SeparatorText("Frame graph"); + DrawFrameGraph(); + + SeparatorText("Renderer info"); + Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate); Text("Presenter time: %.3f ms (%.1f FPS)", io.DeltaTime * 1000.0f, 1.0f / io.DeltaTime); Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(), DebugState.gnm_frame_count.load()); - - SeparatorText("Frame graph"); - DrawFrameGraph(); + Text("Game Res: %dx%d", DebugState.game_resolution.first, + DebugState.game_resolution.second); + Text("Output Res: %dx%d", DebugState.output_resolution.first, + DebugState.output_resolution.second); + Text("FSR: %s", DebugState.is_using_fsr ? "on" : "off"); } End(); } diff --git a/src/core/libraries/kernel/kernel.h b/src/core/libraries/kernel/kernel.h index 8e7f475ad..58911727d 100644 --- a/src/core/libraries/kernel/kernel.h +++ b/src/core/libraries/kernel/kernel.h @@ -5,6 +5,7 @@ #include #include +#include "common/string_literal.h" #include "common/types.h" #include "core/libraries/kernel/orbis_error.h" @@ -18,15 +19,6 @@ void ErrSceToPosix(int result); int ErrnoToSceKernelError(int e); void SetPosixErrno(int e); -template -struct StringLiteral { - constexpr StringLiteral(const char (&str)[N]) { - std::copy_n(str, N, value); - } - - char value[N]; -}; - template struct WrapperImpl; diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index 91616a5ae..4d6972d14 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -360,7 +360,7 @@ s32 PS4_SYSV_ABI sceVideoOutAdjustColor(s32 handle, const SceVideoOutColorSettin return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } - presenter->GetGammaRef() = settings->gamma; + presenter->GetPPSettingsRef().gamma = settings->gamma; return ORBIS_OK; } diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index e60cca122..3001bf773 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -12,6 +12,7 @@ set(SHADER_FILES detilers/micro_64bpp.comp detilers/micro_8bpp.comp fs_tri.vert + fsr.comp post_process.frag ) diff --git a/src/video_core/host_shaders/StringShaderHeader.cmake b/src/video_core/host_shaders/StringShaderHeader.cmake index 9f7525535..798b43e6c 100644 --- a/src/video_core/host_shaders/StringShaderHeader.cmake +++ b/src/video_core/host_shaders/StringShaderHeader.cmake @@ -9,28 +9,31 @@ get_filename_component(CONTENTS_NAME ${SOURCE_FILE} NAME) string(REPLACE "." "_" CONTENTS_NAME ${CONTENTS_NAME}) string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME) -FILE(READ ${SOURCE_FILE} line_contents) +# Function to recursively parse #include directives and replace them with file contents +function(parse_includes file_path output_content) + file(READ ${file_path} file_content) + # This regex includes \n at the begin to (hackish) avoid including comments + string(REGEX MATCHALL "\n#include +\"[^\"]+\"" includes "${file_content}") -# Replace double quotes with single quotes, -# as double quotes will be used to wrap the lines -STRING(REGEX REPLACE "\"" "'" line_contents "${line_contents}") + set(parsed_content "${file_content}") + foreach (include_match ${includes}) + string(REGEX MATCH "\"([^\"]+)\"" _ "${include_match}") + set(include_file ${CMAKE_MATCH_1}) + get_filename_component(include_full_path "${file_path}" DIRECTORY) + set(include_full_path "${include_full_path}/${include_file}") -# CMake separates list elements with semicolons, but semicolons -# are used extensively in the shader code. -# Replace with a temporary marker, to be reverted later. -STRING(REGEX REPLACE ";" "{{SEMICOLON}}" line_contents "${line_contents}") + if (NOT EXISTS "${include_full_path}") + message(FATAL_ERROR "Included file not found: ${include_full_path} from ${file_path}") + endif () -# Make every line an individual element in the CMake list. -STRING(REGEX REPLACE "\n" ";" line_contents "${line_contents}") + parse_includes("${include_full_path}" sub_content) + string(REPLACE "${include_match}" "\n${sub_content}" parsed_content "${parsed_content}") + endforeach () + set(${output_content} "${parsed_content}" PARENT_SCOPE) +endfunction() -# Build the shader string, wrapping each line in double quotes. -foreach(line IN LISTS line_contents) - string(CONCAT CONTENTS "${CONTENTS}" \"${line}\\n\"\n) -endforeach() - -# Revert the original semicolons in the source. -STRING(REGEX REPLACE "{{SEMICOLON}}" ";" CONTENTS "${CONTENTS}") +parse_includes("${SOURCE_FILE}" CONTENTS) get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY) -make_directory(${OUTPUT_DIR}) +file(MAKE_DIRECTORY ${OUTPUT_DIR}) configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY) diff --git a/src/video_core/host_shaders/fsr.comp b/src/video_core/host_shaders/fsr.comp new file mode 100644 index 000000000..105859e35 --- /dev/null +++ b/src/video_core/host_shaders/fsr.comp @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// SPDX-License-Identifier: MIT + +#version 450 +#extension GL_ARB_separate_shader_objects: enable +#extension GL_ARB_shading_language_420pack: enable + +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +layout (push_constant) uniform const_buffer +{ + uvec4 Const0; + uvec4 Const1; + uvec4 Const2; + uvec4 Const3; + uvec4 Sample; +}; + +#define A_GPU 1 +#define A_GLSL 1 + +#define A_HALF +#include "fsr/ffx_a.h" + +layout (set = 0, binding = 0) uniform texture2D InputTexture; +layout (set = 0, binding = 1, rgba16f) uniform image2D OutputTexture; +layout (set = 0, binding = 2) uniform sampler InputSampler; + +#if SAMPLE_EASU +#define FSR_EASU_H 1 +AH4 FsrEasuRH(AF2 p) { AH4 res = AH4(textureGather(sampler2D(InputTexture, InputSampler), p, 0)); return res; } +AH4 FsrEasuGH(AF2 p) { AH4 res = AH4(textureGather(sampler2D(InputTexture, InputSampler), p, 1)); return res; } +AH4 FsrEasuBH(AF2 p) { AH4 res = AH4(textureGather(sampler2D(InputTexture, InputSampler), p, 2)); return res; } +#endif// SAMPLE_EASU + +#if SAMPLE_RCAS +#define FSR_RCAS_H +AH4 FsrRcasLoadH(ASW2 p) { return AH4(texelFetch(sampler2D(InputTexture, InputSampler), ASU2(p), 0)); } +void FsrRcasInputH(inout AH1 r, inout AH1 g, inout AH1 b) { } +#endif// SAMPLE_RCAS + +#include "fsr/ffx_fsr1.h" + +void CurrFilter(AU2 pos) +{ + #if SAMPLE_EASU + AH3 c; + FsrEasuH(c, pos, Const0, Const1, Const2, Const3); + if (Sample.x == 1) + c *= c; + imageStore(OutputTexture, ASU2(pos), AH4(c, 1)); + #endif// SAMPLE_EASU +#if SAMPLE_RCAS + AH3 c; + FsrRcasH(c.r, c.g, c.b, pos, Const0); + if (Sample.x == 1) + c *= c; + imageStore(OutputTexture, ASU2(pos), AH4(c, 1)); + #endif// SAMPLE_RCAS +} + +layout (local_size_x = 64) in; +void main() +{ + // Do remapping of local xy in workgroup for a more PS-like swizzle pattern. + AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u); + CurrFilter(gxy); + gxy.x += 8u; + CurrFilter(gxy); + gxy.y += 8u; + CurrFilter(gxy); + gxy.x -= 8u; + CurrFilter(gxy); +} \ No newline at end of file diff --git a/src/video_core/host_shaders/fsr/ffx_a.h b/src/video_core/host_shaders/fsr/ffx_a.h new file mode 100644 index 000000000..882b0381c --- /dev/null +++ b/src/video_core/host_shaders/fsr/ffx_a.h @@ -0,0 +1,2657 @@ +// clang-format off +//============================================================================================================================== +// +// [A] SHADER PORTABILITY 1.20210629 +// +//============================================================================================================================== +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// MIT LICENSE +// =========== +// Copyright (c) 2014 Michal Drobot (for concepts used in "FLOAT APPROXIMATIONS"). +// ----------- +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// ----------- +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// ----------- +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// Common central point for high-level shading language and C portability for various shader headers. +//------------------------------------------------------------------------------------------------------------------------------ +// DEFINES +// ======= +// A_CPU ..... Include the CPU related code. +// A_GPU ..... Include the GPU related code. +// A_GLSL .... Using GLSL. +// A_HLSL .... Using HLSL. +// A_HLSL_6_2 Using HLSL 6.2 with new 'uint16_t' and related types (requires '-enable-16bit-types'). +// A_NO_16_BIT_CAST Don't use instructions that are not availabe in SPIR-V (needed for running A_HLSL_6_2 on Vulkan) +// A_GCC ..... Using a GCC compatible compiler (else assume MSVC compatible compiler by default). +// ======= +// A_BYTE .... Support 8-bit integer. +// A_HALF .... Support 16-bit integer and floating point. +// A_LONG .... Support 64-bit integer. +// A_DUBL .... Support 64-bit floating point. +// ======= +// A_WAVE .... Support wave-wide operations. +//------------------------------------------------------------------------------------------------------------------------------ +// To get #include "ffx_a.h" working in GLSL use '#extension GL_GOOGLE_include_directive:require'. +//------------------------------------------------------------------------------------------------------------------------------ +// SIMPLIFIED TYPE SYSTEM +// ====================== +// - All ints will be unsigned with exception of when signed is required. +// - Type naming simplified and shortened "A<#components>", +// - H = 16-bit float (half) +// - F = 32-bit float (float) +// - D = 64-bit float (double) +// - P = 1-bit integer (predicate, not using bool because 'B' is used for byte) +// - B = 8-bit integer (byte) +// - W = 16-bit integer (word) +// - U = 32-bit integer (unsigned) +// - L = 64-bit integer (long) +// - Using "AS<#components>" for signed when required. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure 'ALerp*(a,b,m)' does 'b*m+(-a*m+a)' (2 ops). +//------------------------------------------------------------------------------------------------------------------------------ +// CHANGE LOG +// ========== +// 20200914 - Expanded wave ops and prx code. +// 20200713 - Added [ZOL] section, fixed serious bugs in sRGB and Rec.709 color conversion code, etc. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COMMON +//============================================================================================================================== +#define A_2PI 6.28318530718 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// CPU +// +// +//============================================================================================================================== +#ifdef A_CPU + // Supporting user defined overrides. + #ifndef A_RESTRICT + #define A_RESTRICT __restrict + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifndef A_STATIC + #define A_STATIC static + #endif +//------------------------------------------------------------------------------------------------------------------------------ + // Same types across CPU and GPU. + // Predicate uses 32-bit integer (C friendly bool). + typedef uint32_t AP1; + typedef float AF1; + typedef double AD1; + typedef uint8_t AB1; + typedef uint16_t AW1; + typedef uint32_t AU1; + typedef uint64_t AL1; + typedef int8_t ASB1; + typedef int16_t ASW1; + typedef int32_t ASU1; + typedef int64_t ASL1; +//------------------------------------------------------------------------------------------------------------------------------ + #define AD1_(a) ((AD1)(a)) + #define AF1_(a) ((AF1)(a)) + #define AL1_(a) ((AL1)(a)) + #define AU1_(a) ((AU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1_(a) ((ASL1)(a)) + #define ASU1_(a) ((ASU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AU1 AU1_AF1(AF1 a){union{AF1 f;AU1 u;}bits;bits.f=a;return bits.u;} +//------------------------------------------------------------------------------------------------------------------------------ + #define A_TRUE 1 + #define A_FALSE 0 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// CPU/GPU PORTING +// +//------------------------------------------------------------------------------------------------------------------------------ +// Get CPU and GPU to share all setup code, without duplicate code paths. +// This uses a lower-case prefix for special vector constructs. +// - In C restrict pointers are used. +// - In the shading language, in/inout/out arguments are used. +// This depends on the ability to access a vector value in both languages via array syntax (aka color[2]). +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD1 *A_RESTRICT + #define retAD3 AD1 *A_RESTRICT + #define retAD4 AD1 *A_RESTRICT + #define retAF2 AF1 *A_RESTRICT + #define retAF3 AF1 *A_RESTRICT + #define retAF4 AF1 *A_RESTRICT + #define retAL2 AL1 *A_RESTRICT + #define retAL3 AL1 *A_RESTRICT + #define retAL4 AL1 *A_RESTRICT + #define retAU2 AU1 *A_RESTRICT + #define retAU3 AU1 *A_RESTRICT + #define retAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 AD1 *A_RESTRICT + #define inAD3 AD1 *A_RESTRICT + #define inAD4 AD1 *A_RESTRICT + #define inAF2 AF1 *A_RESTRICT + #define inAF3 AF1 *A_RESTRICT + #define inAF4 AF1 *A_RESTRICT + #define inAL2 AL1 *A_RESTRICT + #define inAL3 AL1 *A_RESTRICT + #define inAL4 AL1 *A_RESTRICT + #define inAU2 AU1 *A_RESTRICT + #define inAU3 AU1 *A_RESTRICT + #define inAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 AD1 *A_RESTRICT + #define inoutAD3 AD1 *A_RESTRICT + #define inoutAD4 AD1 *A_RESTRICT + #define inoutAF2 AF1 *A_RESTRICT + #define inoutAF3 AF1 *A_RESTRICT + #define inoutAF4 AF1 *A_RESTRICT + #define inoutAL2 AL1 *A_RESTRICT + #define inoutAL3 AL1 *A_RESTRICT + #define inoutAL4 AL1 *A_RESTRICT + #define inoutAU2 AU1 *A_RESTRICT + #define inoutAU3 AU1 *A_RESTRICT + #define inoutAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 AD1 *A_RESTRICT + #define outAD3 AD1 *A_RESTRICT + #define outAD4 AD1 *A_RESTRICT + #define outAF2 AF1 *A_RESTRICT + #define outAF3 AF1 *A_RESTRICT + #define outAF4 AF1 *A_RESTRICT + #define outAL2 AL1 *A_RESTRICT + #define outAL3 AL1 *A_RESTRICT + #define outAL4 AL1 *A_RESTRICT + #define outAU2 AU1 *A_RESTRICT + #define outAU3 AU1 *A_RESTRICT + #define outAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD1 x[2] + #define varAD3(x) AD1 x[3] + #define varAD4(x) AD1 x[4] + #define varAF2(x) AF1 x[2] + #define varAF3(x) AF1 x[3] + #define varAF4(x) AF1 x[4] + #define varAL2(x) AL1 x[2] + #define varAL3(x) AL1 x[3] + #define varAL4(x) AL1 x[4] + #define varAU2(x) AU1 x[2] + #define varAU3(x) AU1 x[3] + #define varAU4(x) AU1 x[4] +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) {x,y} + #define initAD3(x,y,z) {x,y,z} + #define initAD4(x,y,z,w) {x,y,z,w} + #define initAF2(x,y) {x,y} + #define initAF3(x,y,z) {x,y,z} + #define initAF4(x,y,z,w) {x,y,z,w} + #define initAL2(x,y) {x,y} + #define initAL3(x,y,z) {x,y,z} + #define initAL4(x,y,z,w) {x,y,z,w} + #define initAU2(x,y) {x,y} + #define initAU3(x,y,z) {x,y,z} + #define initAU4(x,y,z,w) {x,y,z,w} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Replace transcendentals with manual versions. +//============================================================================================================================== + #ifdef A_GCC + A_STATIC AD1 AAbsD1(AD1 a){return __builtin_fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return __builtin_fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(__builtin_abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(__builtin_llabs(ASL1_(a)));} + #else + A_STATIC AD1 AAbsD1(AD1 a){return fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(labs((long)ASL1_(a)));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ACosD1(AD1 a){return __builtin_cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return __builtin_cosf(a);} + #else + A_STATIC AD1 ACosD1(AD1 a){return cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return cosf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ADotD2(inAD2 a,inAD2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AD1 ADotD3(inAD3 a,inAD3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AD1 ADotD4(inAD4 a,inAD4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} + A_STATIC AF1 ADotF2(inAF2 a,inAF2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AF1 ADotF3(inAF3 a,inAF3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AF1 ADotF4(inAF4 a,inAF4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AExp2D1(AD1 a){return __builtin_exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return __builtin_exp2f(a);} + #else + A_STATIC AD1 AExp2D1(AD1 a){return exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return exp2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AFloorD1(AD1 a){return __builtin_floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return __builtin_floorf(a);} + #else + A_STATIC AD1 AFloorD1(AD1 a){return floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return floorf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ALerpD1(AD1 a,AD1 b,AD1 c){return b*c+(-a*c+a);} + A_STATIC AF1 ALerpF1(AF1 a,AF1 b,AF1 c){return b*c+(-a*c+a);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ALog2D1(AD1 a){return __builtin_log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return __builtin_log2f(a);} + #else + A_STATIC AD1 ALog2D1(AD1 a){return log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return log2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMaxD1(AD1 a,AD1 b){return a>b?a:b;} + A_STATIC AF1 AMaxF1(AF1 a,AF1 b){return a>b?a:b;} + A_STATIC AL1 AMaxL1(AL1 a,AL1 b){return a>b?a:b;} + A_STATIC AU1 AMaxU1(AU1 a,AU1 b){return a>b?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + // These follow the convention that A integer types don't have signage, until they are operated on. + A_STATIC AL1 AMaxSL1(AL1 a,AL1 b){return (ASL1_(a)>ASL1_(b))?a:b;} + A_STATIC AU1 AMaxSU1(AU1 a,AU1 b){return (ASU1_(a)>ASU1_(b))?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMinD1(AD1 a,AD1 b){return a>ASL1_(b));} + A_STATIC AU1 AShrSU1(AU1 a,AU1 b){return AU1_(ASU1_(a)>>ASU1_(b));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASinD1(AD1 a){return __builtin_sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return __builtin_sinf(a);} + #else + A_STATIC AD1 ASinD1(AD1 a){return sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return sinf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASqrtD1(AD1 a){return __builtin_sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return __builtin_sqrtf(a);} + #else + A_STATIC AD1 ASqrtD1(AD1 a){return sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return sqrtf(a);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + A_STATIC AD1 AClampD1(AD1 x,AD1 n,AD1 m){return AMaxD1(n,AMinD1(x,m));} + A_STATIC AF1 AClampF1(AF1 x,AF1 n,AF1 m){return AMaxF1(n,AMinF1(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AFractD1(AD1 a){return a-AFloorD1(a);} + A_STATIC AF1 AFractF1(AF1 a){return a-AFloorF1(a);} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 APowD1(AD1 a,AD1 b){return AExp2D1(b*ALog2D1(a));} + A_STATIC AF1 APowF1(AF1 a,AF1 b){return AExp2F1(b*ALog2F1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ARsqD1(AD1 a){return ARcpD1(ASqrtD1(a));} + A_STATIC AF1 ARsqF1(AF1 a){return ARcpF1(ASqrtF1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ASatD1(AD1 a){return AMinD1(1.0,AMaxD1(0.0,a));} + A_STATIC AF1 ASatF1(AF1 a){return AMinF1(1.0f,AMaxF1(0.0f,a));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + A_STATIC retAD2 opAAbsD2(outAD2 d,inAD2 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);return d;} + A_STATIC retAD3 opAAbsD3(outAD3 d,inAD3 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);return d;} + A_STATIC retAD4 opAAbsD4(outAD4 d,inAD4 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);d[3]=AAbsD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAbsF2(outAF2 d,inAF2 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);return d;} + A_STATIC retAF3 opAAbsF3(outAF3 d,inAF3 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);return d;} + A_STATIC retAF4 opAAbsF4(outAF4 d,inAF4 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);d[3]=AAbsF1(a[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opACpyD2(outAD2 d,inAD2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAD3 opACpyD3(outAD3 d,inAD3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAD4 opACpyD4(outAD4 d,inAD4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opACpyF2(outAF2 d,inAF2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAF3 opACpyF3(outAF3 d,inAF3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAF4 opACpyF4(outAF4 d,inAF4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);return d;} + A_STATIC retAD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);return d;} + A_STATIC retAD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);d[3]=ALerpD1(a[3],b[3],c[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);return d;} + A_STATIC retAF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);return d;} + A_STATIC retAF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);d[3]=ALerpF1(a[3],b[3],c[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);return d;} + A_STATIC retAD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);return d;} + A_STATIC retAD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);d[3]=ALerpD1(a[3],b[3],c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);return d;} + A_STATIC retAF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);return d;} + A_STATIC retAF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);d[3]=ALerpF1(a[3],b[3],c);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);d[3]=AMaxD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);d[3]=AMaxF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);d[3]=AMinD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);d[3]=AMinF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opANegD2(outAD2 d,inAD2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAD3 opANegD3(outAD3 d,inAD3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAD4 opANegD4(outAD4 d,inAD4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opANegF2(outAF2 d,inAF2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAF3 opANegF3(outAF3 d,inAF3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAF4 opANegF4(outAF4 d,inAF4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opARcpD2(outAD2 d,inAD2 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);return d;} + A_STATIC retAD3 opARcpD3(outAD3 d,inAD3 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);return d;} + A_STATIC retAD4 opARcpD4(outAD4 d,inAD4 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);d[3]=ARcpD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opARcpF2(outAF2 d,inAF2 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);return d;} + A_STATIC retAF3 opARcpF3(outAF3 d,inAF3 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);return d;} + A_STATIC retAF4 opARcpF4(outAF4 d,inAF4 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);d[3]=ARcpF1(a[3]);return d;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF FLOAT PACKING +//============================================================================================================================== + // Convert float to half (in lower 16-bits of output). + // Same fast technique as documented here: ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf + // Supports denormals. + // Conversion rules are to make computations possibly "safer" on the GPU, + // -INF & -NaN -> -65504 + // +INF & +NaN -> +65504 + A_STATIC AU1 AU1_AH1_AF1(AF1 f){ + static AW1 base[512]={ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100, + 0x0200,0x0400,0x0800,0x0c00,0x1000,0x1400,0x1800,0x1c00,0x2000,0x2400,0x2800,0x2c00,0x3000,0x3400,0x3800,0x3c00, + 0x4000,0x4400,0x4800,0x4c00,0x5000,0x5400,0x5800,0x5c00,0x6000,0x6400,0x6800,0x6c00,0x7000,0x7400,0x7800,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8001,0x8002,0x8004,0x8008,0x8010,0x8020,0x8040,0x8080,0x8100, + 0x8200,0x8400,0x8800,0x8c00,0x9000,0x9400,0x9800,0x9c00,0xa000,0xa400,0xa800,0xac00,0xb000,0xb400,0xb800,0xbc00, + 0xc000,0xc400,0xc800,0xcc00,0xd000,0xd400,0xd800,0xdc00,0xe000,0xe400,0xe800,0xec00,0xf000,0xf400,0xf800,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff}; + static AB1 shift[512]={ + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; + union{AF1 f;AU1 u;}bits;bits.f=f;AU1 u=bits.u;AU1 i=u>>23;return (AU1)(base[i])+((u&0x7fffff)>>shift[i]);} +//------------------------------------------------------------------------------------------------------------------------------ + // Used to output packed constant. + A_STATIC AU1 AU1_AH2_AF2(inAF2 a){return AU1_AH1_AF1(a[0])+(AU1_AH1_AF1(a[1])<<16);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GLSL +// +// +//============================================================================================================================== +#if defined(A_GLSL) && defined(A_GPU) + #ifndef A_SKIP_EXT + #ifdef A_HALF + #extension GL_EXT_shader_16bit_storage:require + #extension GL_EXT_shader_explicit_arithmetic_types:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_LONG + #extension GL_ARB_gpu_shader_int64:require + #extension GL_NV_shader_atomic_int64:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_WAVE + #extension GL_KHR_shader_subgroup_arithmetic:require + #extension GL_KHR_shader_subgroup_ballot:require + #extension GL_KHR_shader_subgroup_quad:require + #extension GL_KHR_shader_subgroup_shuffle:require + #endif + #endif +//============================================================================================================================== + #define AP1 bool + #define AP2 bvec2 + #define AP3 bvec3 + #define AP4 bvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 vec2 + #define AF3 vec3 + #define AF4 vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uvec2 + #define AU3 uvec3 + #define AU4 uvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 ivec2 + #define ASU3 ivec3 + #define ASU4 ivec4 +//============================================================================================================================== + #define AF1_AU1(x) uintBitsToFloat(AU1(x)) + #define AF2_AU2(x) uintBitsToFloat(AU2(x)) + #define AF3_AU3(x) uintBitsToFloat(AU3(x)) + #define AF4_AU4(x) uintBitsToFloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) floatBitsToUint(AF1(x)) + #define AU2_AF2(x) floatBitsToUint(AF2(x)) + #define AU3_AF3(x) floatBitsToUint(AF3(x)) + #define AU4_AF4(x) floatBitsToUint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return packHalf2x16(AF2(a,0.0));} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2_AF2 packHalf2x16 + #define AU1_AW2Unorm_AF2 packUnorm2x16 + #define AU1_AB4Unorm_AF4 packUnorm4x8 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF2_AH2_AU1 unpackHalf2x16 + #define AF2_AW2Unorm_AU1 unpackUnorm2x16 + #define AF4_AB4Unorm_AU1 unpackUnorm4x8 +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){return bitfieldExtract(src,ASU1(off),ASU1(bits));} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + // Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #define AB1 uint8_t + #define AB2 u8vec2 + #define AB3 u8vec3 + #define AB4 u8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASB1 int8_t + #define ASB2 i8vec2 + #define ASB3 i8vec3 + #define ASB4 i8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + AB1 AB1_x(AB1 a){return AB1(a);} + AB2 AB2_x(AB1 a){return AB2(a,a);} + AB3 AB3_x(AB1 a){return AB3(a,a,a);} + AB4 AB4_x(AB1 a){return AB4(a,a,a,a);} + #define AB1_(a) AB1_x(AB1(a)) + #define AB2_(a) AB2_x(AB1(a)) + #define AB3_(a) AB3_x(AB1(a)) + #define AB4_(a) AB4_x(AB1(a)) + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #define AH1 float16_t + #define AH2 f16vec2 + #define AH3 f16vec3 + #define AH4 f16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 u16vec2 + #define AW3 u16vec3 + #define AW4 u16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 i16vec2 + #define ASW3 i16vec3 + #define ASW4 i16vec4 +//============================================================================================================================== + #define AH2_AU1(x) unpackFloat2x16(AU1(x)) + AH4 AH4_AU2_x(AU2 x){return AH4(unpackFloat2x16(x.x),unpackFloat2x16(x.y));} + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) unpackUint2x16(AU1(x)) + #define AW4_AU2(x) unpackUint4x16(pack64(AU2(x))) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2(x) packFloat2x16(AH2(x)) + AU2 AU2_AH4_x(AH4 x){return AU2(packFloat2x16(x.xy),packFloat2x16(x.zw));} + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) packUint2x16(AW2(x)) + #define AU2_AW4(x) unpack32(packUint4x16(AW4(x))) +//============================================================================================================================== + #define AW1_AH1(x) halfBitsToUint16(AH1(x)) + #define AW2_AH2(x) halfBitsToUint16(AH2(x)) + #define AW3_AH3(x) halfBitsToUint16(AH3(x)) + #define AW4_AH4(x) halfBitsToUint16(AH4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AH1_AW1(x) uint16BitsToHalf(AW1(x)) + #define AH2_AW2(x) uint16BitsToHalf(AW2(x)) + #define AH3_AW3(x) uint16BitsToHalf(AW3(x)) + #define AH4_AW4(x) uint16BitsToHalf(AW4(x)) +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return clamp(x,n,m);} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return clamp(x,n,m);} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return clamp(x,n,m);} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return clamp(x,n,m);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFractH1(AH1 x){return fract(x);} + AH2 AFractH2(AH2 x){return fract(x);} + AH3 AFractH3(AH3 x){return fract(x);} + AH4 AFractH4(AH4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return mix(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return mix(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return mix(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of max3. + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of min3. + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return AH1_(1.0)/x;} + AH2 ARcpH2(AH2 x){return AH2_(1.0)/x;} + AH3 ARcpH3(AH3 x){return AH3_(1.0)/x;} + AH4 ARcpH4(AH4 x){return AH4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return AH1_(1.0)/sqrt(x);} + AH2 ARsqH2(AH2 x){return AH2_(1.0)/sqrt(x);} + AH3 ARsqH3(AH3 x){return AH3_(1.0)/sqrt(x);} + AH4 ARsqH4(AH4 x){return AH4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return clamp(x,AH1_(0.0),AH1_(1.0));} + AH2 ASatH2(AH2 x){return clamp(x,AH2_(0.0),AH2_(1.0));} + AH3 ASatH3(AH3 x){return clamp(x,AH3_(0.0),AH3_(1.0));} + AH4 ASatH4(AH4 x){return clamp(x,AH4_(0.0),AH4_(1.0));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #define AD1 double + #define AD2 dvec2 + #define AD3 dvec3 + #define AD4 dvec4 +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 x){return fract(x);} + AD2 AFractD2(AD2 x){return fract(x);} + AD3 AFractD3(AD3 x){return fract(x);} + AD4 AFractD4(AD4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return mix(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return mix(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return mix(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return AD1_(1.0)/x;} + AD2 ARcpD2(AD2 x){return AD2_(1.0)/x;} + AD3 ARcpD3(AD3 x){return AD3_(1.0)/x;} + AD4 ARcpD4(AD4 x){return AD4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return AD1_(1.0)/sqrt(x);} + AD2 ARsqD2(AD2 x){return AD2_(1.0)/sqrt(x);} + AD3 ARsqD3(AD3 x){return AD3_(1.0)/sqrt(x);} + AD4 ARsqD4(AD4 x){return AD4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return clamp(x,AD1_(0.0),AD1_(1.0));} + AD2 ASatD2(AD2 x){return clamp(x,AD2_(0.0),AD2_(1.0));} + AD3 ASatD3(AD3 x){return clamp(x,AD3_(0.0),AD3_(1.0));} + AD4 ASatD4(AD4 x){return clamp(x,AD4_(0.0),AD4_(1.0));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL LONG +//============================================================================================================================== + #ifdef A_LONG + #define AL1 uint64_t + #define AL2 u64vec2 + #define AL3 u64vec3 + #define AL4 u64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1 int64_t + #define ASL2 i64vec2 + #define ASL3 i64vec3 + #define ASL4 i64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AL1_AU2(x) packUint2x32(AU2(x)) + #define AU2_AL1(x) unpackUint2x32(AL1(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AL1_x(AL1 a){return AL1(a);} + AL2 AL2_x(AL1 a){return AL2(a,a);} + AL3 AL3_x(AL1 a){return AL3(a,a,a);} + AL4 AL4_x(AL1 a){return AL4(a,a,a,a);} + #define AL1_(a) AL1_x(AL1(a)) + #define AL2_(a) AL2_x(AL1(a)) + #define AL3_(a) AL3_x(AL1(a)) + #define AL4_(a) AL4_x(AL1(a)) +//============================================================================================================================== + AL1 AAbsSL1(AL1 a){return AL1(abs(ASL1(a)));} + AL2 AAbsSL2(AL2 a){return AL2(abs(ASL2(a)));} + AL3 AAbsSL3(AL3 a){return AL3(abs(ASL3(a)));} + AL4 AAbsSL4(AL4 a){return AL4(abs(ASL4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMaxSL1(AL1 a,AL1 b){return AL1(max(ASU1(a),ASU1(b)));} + AL2 AMaxSL2(AL2 a,AL2 b){return AL2(max(ASU2(a),ASU2(b)));} + AL3 AMaxSL3(AL3 a,AL3 b){return AL3(max(ASU3(a),ASU3(b)));} + AL4 AMaxSL4(AL4 a,AL4 b){return AL4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMinSL1(AL1 a,AL1 b){return AL1(min(ASU1(a),ASU1(b)));} + AL2 AMinSL2(AL2 a,AL2 b){return AL2(min(ASU2(a),ASU2(b)));} + AL3 AMinSL3(AL3 a,AL3 b){return AL3(min(ASU3(a),ASU3(b)));} + AL4 AMinSL4(AL4 a,AL4 b){return AL4(min(ASU4(a),ASU4(b)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// WAVE OPERATIONS +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return subgroupShuffleXor(v,x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return subgroupShuffleXor(v,x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return subgroupShuffleXor(v,x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return subgroupShuffleXor(v,x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return subgroupShuffleXor(v,x);} + AU2 AWaveXorU2(AU2 v,AU1 x){return subgroupShuffleXor(v,x);} + AU3 AWaveXorU3(AU3 v,AU1 x){return subgroupShuffleXor(v,x);} + AU4 AWaveXorU4(AU4 v,AU1 x){return subgroupShuffleXor(v,x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(subgroupShuffleXor(AU1_AH2(v),x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(subgroupShuffleXor(AU2_AH4(v),x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(subgroupShuffleXor(AU1_AW2(v),x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU2(subgroupShuffleXor(AU2_AW4(v),x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// HLSL +// +// +//============================================================================================================================== +#if defined(A_HLSL) && defined(A_GPU) + #ifdef A_HLSL_6_2 + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float32_t + #define AF2 float32_t2 + #define AF3 float32_t3 + #define AF4 float32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint32_t + #define AU2 uint32_t2 + #define AU3 uint32_t3 + #define AU4 uint32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int32_t + #define ASU2 int32_t2 + #define ASU3 int32_t3 + #define ASU4 int32_t4 + #else + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 float2 + #define AF3 float3 + #define AF4 float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uint2 + #define AU3 uint3 + #define AU4 uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 int2 + #define ASU3 int3 + #define ASU4 int4 + #endif +//============================================================================================================================== + #define AF1_AU1(x) asfloat(AU1(x)) + #define AF2_AU2(x) asfloat(AU2(x)) + #define AF3_AU3(x) asfloat(AU3(x)) + #define AF4_AU4(x) asfloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) asuint(AF1(x)) + #define AU2_AF2(x) asuint(AF2(x)) + #define AU3_AF3(x) asuint(AF3(x)) + #define AU4_AF4(x) asuint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return f32tof16(a);} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_AF2_x(AF2 a){return f32tof16(a.x)|(f32tof16(a.y)<<16);} + #define AU1_AH2_AF2(a) AU1_AH2_AF2_x(AF2(a)) + #define AU1_AB4Unorm_AF4(x) D3DCOLORtoUBYTE4(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AF2 AF2_AH2_AU1_x(AU1 x){return AF2(f16tof32(x&0xFFFF),f16tof32(x>>16));} + #define AF2_AH2_AU1(x) AF2_AH2_AU1_x(AU1(x)) +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1u<>off)&mask;} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1u<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define AH1 float16_t + #define AH2 float16_t2 + #define AH3 float16_t3 + #define AH4 float16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 uint16_t2 + #define AW3 uint16_t3 + #define AW4 uint16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 int16_t2 + #define ASW3 int16_t3 + #define ASW4 int16_t4 + #else + #define AH1 min16float + #define AH2 min16float2 + #define AH3 min16float3 + #define AH4 min16float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 min16uint + #define AW2 min16uint2 + #define AW3 min16uint3 + #define AW4 min16uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 min16int + #define ASW2 min16int2 + #define ASW3 min16int3 + #define ASW4 min16int4 + #endif +//============================================================================================================================== + // Need to use manual unpack to get optimal execution (don't use packed types in buffers directly). + // Unpack requires this pattern: https://gpuopen.com/first-steps-implementing-fp16/ + AH2 AH2_AU1_x(AU1 x){AF2 t=f16tof32(AU2(x&0xFFFF,x>>16));return AH2(t);} + AH4 AH4_AU2_x(AU2 x){return AH4(AH2_AU1_x(x.x),AH2_AU1_x(x.y));} + AW2 AW2_AU1_x(AU1 x){AU2 t=AU2(x&0xFFFF,x>>16);return AW2(t);} + AW4 AW4_AU2_x(AU2 x){return AW4(AW2_AU1_x(x.x),AW2_AU1_x(x.y));} + #define AH2_AU1(x) AH2_AU1_x(AU1(x)) + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) AW2_AU1_x(AU1(x)) + #define AW4_AU2(x) AW4_AU2_x(AU2(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_x(AH2 x){return f32tof16(x.x)+(f32tof16(x.y)<<16);} + AU2 AU2_AH4_x(AH4 x){return AU2(AU1_AH2_x(x.xy),AU1_AH2_x(x.zw));} + AU1 AU1_AW2_x(AW2 x){return AU1(x.x)+(AU1(x.y)<<16);} + AU2 AU2_AW4_x(AW4 x){return AU2(AU1_AW2_x(x.xy),AU1_AW2_x(x.zw));} + #define AU1_AH2(x) AU1_AH2_x(AH2(x)) + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) AU1_AW2_x(AW2(x)) + #define AU2_AW4(x) AU2_AW4_x(AW4(x)) +//============================================================================================================================== + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AW1_AH1(x) asuint16(x) + #define AW2_AH2(x) asuint16(x) + #define AW3_AH3(x) asuint16(x) + #define AW4_AH4(x) asuint16(x) + #else + #define AW1_AH1(a) AW1(f32tof16(AF1(a))) + #define AW2_AH2(a) AW2(AW1_AH1((a).x),AW1_AH1((a).y)) + #define AW3_AH3(a) AW3(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z)) + #define AW4_AH4(a) AW4(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z),AW1_AH1((a).w)) + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AH1_AW1(x) asfloat16(x) + #define AH2_AW2(x) asfloat16(x) + #define AH3_AW3(x) asfloat16(x) + #define AH4_AW4(x) asfloat16(x) + #else + #define AH1_AW1(a) AH1(f16tof32(AU1(a))) + #define AH2_AW2(a) AH2(AH1_AW1((a).x),AH1_AW1((a).y)) + #define AH3_AW3(a) AH3(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z)) + #define AH4_AW4(a) AH4(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z),AH1_AW1((a).w)) + #endif +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return max(n,min(x,m));} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return max(n,min(x,m));} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return max(n,min(x,m));} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return max(n,min(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + // V_FRACT_F16 (note DX frac() is different). + AH1 AFractH1(AH1 x){return x-floor(x);} + AH2 AFractH2(AH2 x){return x-floor(x);} + AH3 AFractH3(AH3 x){return x-floor(x);} + AH4 AFractH4(AH4 x){return x-floor(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return lerp(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return lerp(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return lerp(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return rcp(x);} + AH2 ARcpH2(AH2 x){return rcp(x);} + AH3 ARcpH3(AH3 x){return rcp(x);} + AH4 ARcpH4(AH4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return rsqrt(x);} + AH2 ARsqH2(AH2 x){return rsqrt(x);} + AH3 ARsqH3(AH3 x){return rsqrt(x);} + AH4 ARsqH4(AH4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return saturate(x);} + AH2 ASatH2(AH2 x){return saturate(x);} + AH3 ASatH3(AH3 x){return saturate(x);} + AH4 ASatH4(AH4 x){return saturate(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #ifdef A_HLSL_6_2 + #define AD1 float64_t + #define AD2 float64_t2 + #define AD3 float64_t3 + #define AD4 float64_t4 + #else + #define AD1 double + #define AD2 double2 + #define AD3 double3 + #define AD4 double4 + #endif +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 a){return a-floor(a);} + AD2 AFractD2(AD2 a){return a-floor(a);} + AD3 AFractD3(AD3 a){return a-floor(a);} + AD4 AFractD4(AD4 a){return a-floor(a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return lerp(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return lerp(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return lerp(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return rcp(x);} + AD2 ARcpD2(AD2 x){return rcp(x);} + AD3 ARcpD3(AD3 x){return rcp(x);} + AD4 ARcpD4(AD4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return rsqrt(x);} + AD2 ARsqD2(AD2 x){return rsqrt(x);} + AD3 ARsqD3(AD3 x){return rsqrt(x);} + AD4 ARsqD4(AD4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return saturate(x);} + AD2 ASatD2(AD2 x){return saturate(x);} + AD3 ASatD3(AD3 x){return saturate(x);} + AD4 ASatD4(AD4 x){return saturate(x);} + #endif +//============================================================================================================================== +// HLSL WAVE +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU2 AWaveXorU1(AU2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU3 AWaveXorU1(AU3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU4 AWaveXorU1(AU4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(WaveReadLaneAt(AU1_AH2(v),WaveGetLaneIndex()^x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(WaveReadLaneAt(AU2_AH4(v),WaveGetLaneIndex()^x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(WaveReadLaneAt(AU1_AW2(v),WaveGetLaneIndex()^x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU1(WaveReadLaneAt(AU1_AW4(v),WaveGetLaneIndex()^x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU COMMON +// +// +//============================================================================================================================== +#ifdef A_GPU + // Negative and positive infinity. + #define A_INFP_F AF1_AU1(0x7f800000u) + #define A_INFN_F AF1_AU1(0xff800000u) +//------------------------------------------------------------------------------------------------------------------------------ + // Copy sign from 's' to positive 'd'. + AF1 ACpySgnF1(AF1 d,AF1 s){return AF1_AU1(AU1_AF1(d)|(AU1_AF1(s)&AU1_(0x80000000u)));} + AF2 ACpySgnF2(AF2 d,AF2 s){return AF2_AU2(AU2_AF2(d)|(AU2_AF2(s)&AU2_(0x80000000u)));} + AF3 ACpySgnF3(AF3 d,AF3 s){return AF3_AU3(AU3_AF3(d)|(AU3_AF3(s)&AU3_(0x80000000u)));} + AF4 ACpySgnF4(AF4 d,AF4 s){return AF4_AU4(AU4_AF4(d)|(AU4_AF4(s)&AU4_(0x80000000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Single operation to return (useful to create a mask to use in lerp for branch free logic), + // m=NaN := 0 + // m>=0 := 0 + // m<0 := 1 + // Uses the following useful floating point logic, + // saturate(+a*(-INF)==-INF) := 0 + // saturate( 0*(-INF)== NaN) := 0 + // saturate(-a*(-INF)==+INF) := 1 + AF1 ASignedF1(AF1 m){return ASatF1(m*AF1_(A_INFN_F));} + AF2 ASignedF2(AF2 m){return ASatF2(m*AF2_(A_INFN_F));} + AF3 ASignedF3(AF3 m){return ASatF3(m*AF3_(A_INFN_F));} + AF4 ASignedF4(AF4 m){return ASatF4(m*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AGtZeroF1(AF1 m){return ASatF1(m*AF1_(A_INFP_F));} + AF2 AGtZeroF2(AF2 m){return ASatF2(m*AF2_(A_INFP_F));} + AF3 AGtZeroF3(AF3 m){return ASatF3(m*AF3_(A_INFP_F));} + AF4 AGtZeroF4(AF4 m){return ASatF4(m*AF4_(A_INFP_F));} +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define A_INFP_H AH1_AW1((uint16_t)0x7c00u) + #define A_INFN_H AH1_AW1((uint16_t)0xfc00u) + #else + #define A_INFP_H AH1_AW1(0x7c00u) + #define A_INFN_H AH1_AW1(0xfc00u) + #endif + +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ACpySgnH1(AH1 d,AH1 s){return AH1_AW1(AW1_AH1(d)|(AW1_AH1(s)&AW1_(0x8000u)));} + AH2 ACpySgnH2(AH2 d,AH2 s){return AH2_AW2(AW2_AH2(d)|(AW2_AH2(s)&AW2_(0x8000u)));} + AH3 ACpySgnH3(AH3 d,AH3 s){return AH3_AW3(AW3_AH3(d)|(AW3_AH3(s)&AW3_(0x8000u)));} + AH4 ACpySgnH4(AH4 d,AH4 s){return AH4_AW4(AW4_AH4(d)|(AW4_AH4(s)&AW4_(0x8000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASignedH1(AH1 m){return ASatH1(m*AH1_(A_INFN_H));} + AH2 ASignedH2(AH2 m){return ASatH2(m*AH2_(A_INFN_H));} + AH3 ASignedH3(AH3 m){return ASatH3(m*AH3_(A_INFN_H));} + AH4 ASignedH4(AH4 m){return ASatH4(m*AH4_(A_INFN_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AGtZeroH1(AH1 m){return ASatH1(m*AH1_(A_INFP_H));} + AH2 AGtZeroH2(AH2 m){return ASatH2(m*AH2_(A_INFP_H));} + AH3 AGtZeroH3(AH3 m){return ASatH3(m*AH3_(A_INFP_H));} + AH4 AGtZeroH4(AH4 m){return ASatH4(m*AH4_(A_INFP_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [FIS] FLOAT INTEGER SORTABLE +//------------------------------------------------------------------------------------------------------------------------------ +// Float to integer sortable. +// - If sign bit=0, flip the sign bit (positives). +// - If sign bit=1, flip all bits (negatives). +// Integer sortable to float. +// - If sign bit=1, flip the sign bit (positives). +// - If sign bit=0, flip all bits (negatives). +// Has nice side effects. +// - Larger integers are more positive values. +// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage). +// Burns 3 ops for conversion {shift,or,xor}. +//============================================================================================================================== + AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} + AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + // Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value). + AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} + AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AW1 AFisToW1(AW1 x){return x^(( AShrSW1(x,AW1_(15)))|AW1_(0x8000));} + AW1 AFisFromW1(AW1 x){return x^((~AShrSW1(x,AW1_(15)))|AW1_(0x8000));} +//------------------------------------------------------------------------------------------------------------------------------ + AW2 AFisToW2(AW2 x){return x^(( AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + AW2 AFisFromW2(AW2 x){return x^((~AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [PERM] V_PERM_B32 +//------------------------------------------------------------------------------------------------------------------------------ +// Support for V_PERM_B32 started in the 3rd generation of GCN. +//------------------------------------------------------------------------------------------------------------------------------ +// yyyyxxxx - The 'i' input. +// 76543210 +// ======== +// HGFEDCBA - Naming on permutation. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure compiler optimizes this. +//============================================================================================================================== + #ifdef A_HALF + AU1 APerm0E0A(AU2 i){return((i.x )&0xffu)|((i.y<<16)&0xff0000u);} + AU1 APerm0F0B(AU2 i){return((i.x>> 8)&0xffu)|((i.y<< 8)&0xff0000u);} + AU1 APerm0G0C(AU2 i){return((i.x>>16)&0xffu)|((i.y )&0xff0000u);} + AU1 APerm0H0D(AU2 i){return((i.x>>24)&0xffu)|((i.y>> 8)&0xff0000u);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermHGFA(AU2 i){return((i.x )&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGFC(AU2 i){return((i.x>>16)&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGAE(AU2 i){return((i.x<< 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHGCE(AU2 i){return((i.x>> 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHAFE(AU2 i){return((i.x<<16)&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermHCFE(AU2 i){return((i.x )&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermAGFE(AU2 i){return((i.x<<24)&0xff000000u)|(i.y&0x00ffffffu);} + AU1 APermCGFE(AU2 i){return((i.x<< 8)&0xff000000u)|(i.y&0x00ffffffu);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermGCEA(AU2 i){return((i.x)&0x00ff00ffu)|((i.y<<8)&0xff00ff00u);} + AU1 APermGECA(AU2 i){return(((i.x)&0xffu)|((i.x>>8)&0xff00u)|((i.y<<16)&0xff0000u)|((i.y<<8)&0xff000000u));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BUC] BYTE UNSIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Designed to use the optimal conversion, enables the scaling to possibly be factored into other computation. +// Works on a range of {0 to A_BUC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// OPCODE NOTES +// ============ +// GCN does not do UNORM or SNORM for bytes in opcodes. +// - V_CVT_F32_UBYTE{0,1,2,3} - Unsigned byte to float. +// - V_CVT_PKACC_U8_F32 - Float to unsigned byte (does bit-field insert into 32-bit integer). +// V_PERM_B32 does byte packing with ability to zero fill bytes as well. +// - Can pull out byte values from two sources, and zero fill upper 8-bits of packed hi and lo. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U1() - Designed for V_CVT_F32_UBYTE* and V_CVT_PKACCUM_U8_F32 ops. +// ==== ===== +// 0 : 0 +// 1 : 1 +// ... +// 255 : 255 +// : 256 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : 0 +// 1 : 1/512 +// 2 : 1/256 +// ... +// 64 : 1/8 +// 128 : 1/4 +// 255 : 255/512 +// : 1/2 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMAL IMPLEMENTATIONS ON AMD ARCHITECTURES +// ============================================ +// r=ABuc0FromU1(i) +// V_CVT_F32_UBYTE0 r,i +// -------------------------------------------- +// r=ABuc0ToU1(d,i) +// V_CVT_PKACCUM_U8_F32 r,i,0,d +// -------------------------------------------- +// d=ABuc0FromU2(i) +// Where 'k0' is an SGPR with 0x0E0A +// Where 'k1' is an SGPR with {32768.0} packed into the lower 16-bits +// V_PERM_B32 d,i.x,i.y,k0 +// V_PK_FMA_F16 d,d,k1.x,0 +// -------------------------------------------- +// r=ABuc0ToU2(d,i) +// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits +// Where 'k1' is an SGPR with 0x???? +// Where 'k2' is an SGPR with 0x???? +// V_PK_FMA_F16 i,i,k0.x,0 +// V_PERM_B32 r.x,i,i,k1 +// V_PERM_B32 r.y,i,i,k2 +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BUC_32 (255.0) + #define A_BUC_16 (255.0/512.0) +//============================================================================================================================== + #if 1 + // Designed to be one V_CVT_PKACCUM_U8_F32. + // The extra min is required to pattern match to V_CVT_PKACCUM_U8_F32. + AU1 ABuc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i),255u) )&(0x000000ffu));} + AU1 ABuc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i),255u)<< 8)&(0x0000ff00u));} + AU1 ABuc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i),255u)<<16)&(0x00ff0000u));} + AU1 ABuc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed to be one V_CVT_F32_UBYTE*. + AF1 ABuc0FromU1(AU1 i){return AF1((i )&255u);} + AF1 ABuc1FromU1(AU1 i){return AF1((i>> 8)&255u);} + AF1 ABuc2FromU1(AU1 i){return AF1((i>>16)&255u);} + AF1 ABuc3FromU1(AU1 i){return AF1((i>>24)&255u);} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABuc01ToW2(AH2 x,AH2 y){x*=AH2_(1.0/32768.0);y*=AH2_(1.0/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 3 ops to do SOA to AOS and conversion. + AU2 ABuc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABuc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABuc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABuc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 2 ops to do both AOS to SOA, and conversion. + AH2 ABuc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0);} + AH2 ABuc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0);} + AH2 ABuc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0);} + AH2 ABuc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BSC] BYTE SIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Similar to [BUC]. +// Works on a range of {-/+ A_BSC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// ENCODING (without zero-based encoding) +// ======== +// 0 = unused (can be used to mean something else) +// 1 = lowest value +// 128 = exact zero center (zero based encoding +// 255 = highest value +//------------------------------------------------------------------------------------------------------------------------------ +// Zero-based [Zb] flips the MSB bit of the byte (making 128 "exact zero" actually zero). +// This is useful if there is a desire for cleared values to decode as zero. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABsc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : -127/512 (unused) +// 1 : -126/512 +// 2 : -125/512 +// ... +// 128 : 0 +// ... +// 255 : 127/512 +// : 1/4 (just outside the encoding range) +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BSC_32 (127.0) + #define A_BSC_16 (127.0/512.0) +//============================================================================================================================== + #if 1 + AU1 ABsc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i+128.0),255u) )&(0x000000ffu));} + AU1 ABsc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i+128.0),255u)<< 8)&(0x0000ff00u));} + AU1 ABsc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i+128.0),255u)<<16)&(0x00ff0000u));} + AU1 ABsc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i+128.0),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABsc0ToZbU1(AU1 d,AF1 i){return ((d&0xffffff00u)|((min(AU1(trunc(i)+128.0),255u) )&(0x000000ffu)))^0x00000080u;} + AU1 ABsc1ToZbU1(AU1 d,AF1 i){return ((d&0xffff00ffu)|((min(AU1(trunc(i)+128.0),255u)<< 8)&(0x0000ff00u)))^0x00008000u;} + AU1 ABsc2ToZbU1(AU1 d,AF1 i){return ((d&0xff00ffffu)|((min(AU1(trunc(i)+128.0),255u)<<16)&(0x00ff0000u)))^0x00800000u;} + AU1 ABsc3ToZbU1(AU1 d,AF1 i){return ((d&0x00ffffffu)|((min(AU1(trunc(i)+128.0),255u)<<24)&(0xff000000u)))^0x80000000u;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromU1(AU1 i){return AF1((i )&255u)-128.0;} + AF1 ABsc1FromU1(AU1 i){return AF1((i>> 8)&255u)-128.0;} + AF1 ABsc2FromU1(AU1 i){return AF1((i>>16)&255u)-128.0;} + AF1 ABsc3FromU1(AU1 i){return AF1((i>>24)&255u)-128.0;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromZbU1(AU1 i){return AF1(((i )&255u)^0x80u)-128.0;} + AF1 ABsc1FromZbU1(AU1 i){return AF1(((i>> 8)&255u)^0x80u)-128.0;} + AF1 ABsc2FromZbU1(AU1 i){return AF1(((i>>16)&255u)^0x80u)-128.0;} + AF1 ABsc3FromZbU1(AU1 i){return AF1(((i>>24)&255u)^0x80u)-128.0;} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABsc01ToW2(AH2 x,AH2 y){x=x*AH2_(1.0/32768.0)+AH2_(0.25/32768.0);y=y*AH2_(1.0/32768.0)+AH2_(0.25/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0)-AH2_(0.25);} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These support only positive inputs. +// Did not see value yet in specialization for range. +// Using quick testing, ended up mostly getting the same "best" approximation for various ranges. +// With hardware that can co-execute transcendentals, the value in approximations could be less than expected. +// However from a latency perspective, if execution of a transcendental is 4 clk, with no packed support, -> 8 clk total. +// And co-execution would require a compiler interleaving a lot of independent work for packed usage. +//------------------------------------------------------------------------------------------------------------------------------ +// The one Newton Raphson iteration form of rsq() was skipped (requires 6 ops total). +// Same with sqrt(), as this could be x*rsq() (7 ops). +//============================================================================================================================== + #ifdef A_HALF + // Minimize squared error across full positive range, 2 ops. + // The 0x1de2 based approximation maps {0 to 1} input maps to < 1 output. + AH1 APrxLoSqrtH1(AH1 a){return AH1_AW1((AW1_AH1(a)>>AW1_(1))+AW1_(0x1de2));} + AH2 APrxLoSqrtH2(AH2 a){return AH2_AW2((AW2_AH2(a)>>AW2_(1))+AW2_(0x1de2));} + AH3 APrxLoSqrtH3(AH3 a){return AH3_AW3((AW3_AH3(a)>>AW3_(1))+AW3_(0x1de2));} + AH4 APrxLoSqrtH4(AH4 a){return AH4_AW4((AW4_AH4(a)>>AW4_(1))+AW4_(0x1de2));} +//------------------------------------------------------------------------------------------------------------------------------ + // Lower precision estimation, 1 op. + // Minimize squared error across {smallest normal to 16384.0}. + AH1 APrxLoRcpH1(AH1 a){return AH1_AW1(AW1_(0x7784)-AW1_AH1(a));} + AH2 APrxLoRcpH2(AH2 a){return AH2_AW2(AW2_(0x7784)-AW2_AH2(a));} + AH3 APrxLoRcpH3(AH3 a){return AH3_AW3(AW3_(0x7784)-AW3_AH3(a));} + AH4 APrxLoRcpH4(AH4 a){return AH4_AW4(AW4_(0x7784)-AW4_AH4(a));} +//------------------------------------------------------------------------------------------------------------------------------ + // Medium precision estimation, one Newton Raphson iteration, 3 ops. + AH1 APrxMedRcpH1(AH1 a){AH1 b=AH1_AW1(AW1_(0x778d)-AW1_AH1(a));return b*(-b*a+AH1_(2.0));} + AH2 APrxMedRcpH2(AH2 a){AH2 b=AH2_AW2(AW2_(0x778d)-AW2_AH2(a));return b*(-b*a+AH2_(2.0));} + AH3 APrxMedRcpH3(AH3 a){AH3 b=AH3_AW3(AW3_(0x778d)-AW3_AH3(a));return b*(-b*a+AH3_(2.0));} + AH4 APrxMedRcpH4(AH4 a){AH4 b=AH4_AW4(AW4_(0x778d)-AW4_AH4(a));return b*(-b*a+AH4_(2.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // Minimize squared error across {smallest normal to 16384.0}, 2 ops. + AH1 APrxLoRsqH1(AH1 a){return AH1_AW1(AW1_(0x59a3)-(AW1_AH1(a)>>AW1_(1)));} + AH2 APrxLoRsqH2(AH2 a){return AH2_AW2(AW2_(0x59a3)-(AW2_AH2(a)>>AW2_(1)));} + AH3 APrxLoRsqH3(AH3 a){return AH3_AW3(AW3_(0x59a3)-(AW3_AH3(a)>>AW3_(1)));} + AH4 APrxLoRsqH4(AH4 a){return AH4_AW4(AW4_(0x59a3)-(AW4_AH4(a)>>AW4_(1)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// FLOAT APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// Michal Drobot has an excellent presentation on these: "Low Level Optimizations For GCN", +// - Idea dates back to SGI, then to Quake 3, etc. +// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf +// - sqrt(x)=rsqrt(x)*x +// - rcp(x)=rsqrt(x)*rsqrt(x) for positive x +// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h +//------------------------------------------------------------------------------------------------------------------------------ +// These below are from perhaps less complete searching for optimal. +// Used FP16 normal range for testing with +4096 32-bit step size for sampling error. +// So these match up well with the half approximations. +//============================================================================================================================== + AF1 APrxLoSqrtF1(AF1 a){return AF1_AU1((AU1_AF1(a)>>AU1_(1))+AU1_(0x1fbc4639));} + AF1 APrxLoRcpF1(AF1 a){return AF1_AU1(AU1_(0x7ef07ebb)-AU1_AF1(a));} + AF1 APrxMedRcpF1(AF1 a){AF1 b=AF1_AU1(AU1_(0x7ef19fff)-AU1_AF1(a));return b*(-b*a+AF1_(2.0));} + AF1 APrxLoRsqF1(AF1 a){return AF1_AU1(AU1_(0x5f347d74)-(AU1_AF1(a)>>AU1_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxLoSqrtF2(AF2 a){return AF2_AU2((AU2_AF2(a)>>AU2_(1))+AU2_(0x1fbc4639));} + AF2 APrxLoRcpF2(AF2 a){return AF2_AU2(AU2_(0x7ef07ebb)-AU2_AF2(a));} + AF2 APrxMedRcpF2(AF2 a){AF2 b=AF2_AU2(AU2_(0x7ef19fff)-AU2_AF2(a));return b*(-b*a+AF2_(2.0));} + AF2 APrxLoRsqF2(AF2 a){return AF2_AU2(AU2_(0x5f347d74)-(AU2_AF2(a)>>AU2_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxLoSqrtF3(AF3 a){return AF3_AU3((AU3_AF3(a)>>AU3_(1))+AU3_(0x1fbc4639));} + AF3 APrxLoRcpF3(AF3 a){return AF3_AU3(AU3_(0x7ef07ebb)-AU3_AF3(a));} + AF3 APrxMedRcpF3(AF3 a){AF3 b=AF3_AU3(AU3_(0x7ef19fff)-AU3_AF3(a));return b*(-b*a+AF3_(2.0));} + AF3 APrxLoRsqF3(AF3 a){return AF3_AU3(AU3_(0x5f347d74)-(AU3_AF3(a)>>AU3_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxLoSqrtF4(AF4 a){return AF4_AU4((AU4_AF4(a)>>AU4_(1))+AU4_(0x1fbc4639));} + AF4 APrxLoRcpF4(AF4 a){return AF4_AU4(AU4_(0x7ef07ebb)-AU4_AF4(a));} + AF4 APrxMedRcpF4(AF4 a){AF4 b=AF4_AU4(AU4_(0x7ef19fff)-AU4_AF4(a));return b*(-b*a+AF4_(2.0));} + AF4 APrxLoRsqF4(AF4 a){return AF4_AU4(AU4_(0x5f347d74)-(AU4_AF4(a)>>AU4_(1)));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PQ APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// PQ is very close to x^(1/8). The functions below Use the fast float approximation method to do +// PQ<~>Gamma2 (4th power and fast 4th root) and PQ<~>Linear (8th power and fast 8th root). Maximum error is ~0.2%. +//============================================================================================================================== +// Helpers + AF1 Quart(AF1 a) { a = a * a; return a * a;} + AF1 Oct(AF1 a) { a = a * a; a = a * a; return a * a; } + AF2 Quart(AF2 a) { a = a * a; return a * a; } + AF2 Oct(AF2 a) { a = a * a; a = a * a; return a * a; } + AF3 Quart(AF3 a) { a = a * a; return a * a; } + AF3 Oct(AF3 a) { a = a * a; a = a * a; return a * a; } + AF4 Quart(AF4 a) { a = a * a; return a * a; } + AF4 Oct(AF4 a) { a = a * a; a = a * a; return a * a; } + //------------------------------------------------------------------------------------------------------------------------------ + AF1 APrxPQToGamma2(AF1 a) { return Quart(a); } + AF1 APrxPQToLinear(AF1 a) { return Oct(a); } + AF1 APrxLoGamma2ToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); } + AF1 APrxMedGamma2ToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); AF1 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF1 APrxHighGamma2ToPQ(AF1 a) { return sqrt(sqrt(a)); } + AF1 APrxLoLinearToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); } + AF1 APrxMedLinearToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); AF1 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF1 APrxHighLinearToPQ(AF1 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxPQToGamma2(AF2 a) { return Quart(a); } + AF2 APrxPQToLinear(AF2 a) { return Oct(a); } + AF2 APrxLoGamma2ToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); } + AF2 APrxMedGamma2ToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); AF2 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF2 APrxHighGamma2ToPQ(AF2 a) { return sqrt(sqrt(a)); } + AF2 APrxLoLinearToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); } + AF2 APrxMedLinearToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); AF2 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF2 APrxHighLinearToPQ(AF2 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxPQToGamma2(AF3 a) { return Quart(a); } + AF3 APrxPQToLinear(AF3 a) { return Oct(a); } + AF3 APrxLoGamma2ToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); } + AF3 APrxMedGamma2ToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); AF3 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF3 APrxHighGamma2ToPQ(AF3 a) { return sqrt(sqrt(a)); } + AF3 APrxLoLinearToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); } + AF3 APrxMedLinearToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); AF3 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF3 APrxHighLinearToPQ(AF3 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxPQToGamma2(AF4 a) { return Quart(a); } + AF4 APrxPQToLinear(AF4 a) { return Oct(a); } + AF4 APrxLoGamma2ToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); } + AF4 APrxMedGamma2ToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); AF4 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF4 APrxHighGamma2ToPQ(AF4 a) { return sqrt(sqrt(a)); } + AF4 APrxLoLinearToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); } + AF4 APrxMedLinearToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); AF4 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF4 APrxHighLinearToPQ(AF4 a) { return sqrt(sqrt(sqrt(a))); } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PARABOLIC SIN & COS +//------------------------------------------------------------------------------------------------------------------------------ +// Approximate answers to transcendental questions. +//------------------------------------------------------------------------------------------------------------------------------ +//============================================================================================================================== + #if 1 + // Valid input range is {-1 to 1} representing {0 to 2 pi}. + // Output range is {-1/4 to 1/4} representing {-1 to 1}. + AF1 APSinF1(AF1 x){return x*abs(x)-x;} // MAD. + AF2 APSinF2(AF2 x){return x*abs(x)-x;} + AF1 APCosF1(AF1 x){x=AFractF1(x*AF1_(0.5)+AF1_(0.75));x=x*AF1_(2.0)-AF1_(1.0);return APSinF1(x);} // 3x MAD, FRACT + AF2 APCosF2(AF2 x){x=AFractF2(x*AF2_(0.5)+AF2_(0.75));x=x*AF2_(2.0)-AF2_(1.0);return APSinF2(x);} + AF2 APSinCosF1(AF1 x){AF1 y=AFractF1(x*AF1_(0.5)+AF1_(0.75));y=y*AF1_(2.0)-AF1_(1.0);return APSinF2(AF2(x,y));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + // For a packed {sin,cos} pair, + // - Native takes 16 clocks and 4 issue slots (no packed transcendentals). + // - Parabolic takes 8 clocks and 8 issue slots (only fract is non-packed). + AH1 APSinH1(AH1 x){return x*abs(x)-x;} + AH2 APSinH2(AH2 x){return x*abs(x)-x;} // AND,FMA + AH1 APCosH1(AH1 x){x=AFractH1(x*AH1_(0.5)+AH1_(0.75));x=x*AH1_(2.0)-AH1_(1.0);return APSinH1(x);} + AH2 APCosH2(AH2 x){x=AFractH2(x*AH2_(0.5)+AH2_(0.75));x=x*AH2_(2.0)-AH2_(1.0);return APSinH2(x);} // 3x FMA, 2xFRACT, AND + AH2 APSinCosH1(AH1 x){AH1 y=AFractH1(x*AH1_(0.5)+AH1_(0.75));y=y*AH1_(2.0)-AH1_(1.0);return APSinH2(AH2(x,y));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [ZOL] ZERO ONE LOGIC +//------------------------------------------------------------------------------------------------------------------------------ +// Conditional free logic designed for easy 16-bit packing, and backwards porting to 32-bit. +//------------------------------------------------------------------------------------------------------------------------------ +// 0 := false +// 1 := true +//------------------------------------------------------------------------------------------------------------------------------ +// AndNot(x,y) -> !(x&y) .... One op. +// AndOr(x,y,z) -> (x&y)|z ... One op. +// GtZero(x) -> x>0.0 ..... One op. +// Sel(x,y,z) -> x?y:z ..... Two ops, has no precision loss. +// Signed(x) -> x<0.0 ..... One op. +// ZeroPass(x,y) -> x?0:y ..... Two ops, 'y' is a pass through safe for aliasing as integer. +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMIZATION NOTES +// ================== +// - On Vega to use 2 constants in a packed op, pass in as one AW2 or one AH2 'k.xy' and use as 'k.xx' and 'k.yy'. +// For example 'a.xy*k.xx+k.yy'. +//============================================================================================================================== + #if 1 + AU1 AZolAndU1(AU1 x,AU1 y){return min(x,y);} + AU2 AZolAndU2(AU2 x,AU2 y){return min(x,y);} + AU3 AZolAndU3(AU3 x,AU3 y){return min(x,y);} + AU4 AZolAndU4(AU4 x,AU4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolNotU1(AU1 x){return x^AU1_(1);} + AU2 AZolNotU2(AU2 x){return x^AU2_(1);} + AU3 AZolNotU3(AU3 x){return x^AU3_(1);} + AU4 AZolNotU4(AU4 x){return x^AU4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolOrU1(AU1 x,AU1 y){return max(x,y);} + AU2 AZolOrU2(AU2 x,AU2 y){return max(x,y);} + AU3 AZolOrU3(AU3 x,AU3 y){return max(x,y);} + AU4 AZolOrU4(AU4 x,AU4 y){return max(x,y);} +//============================================================================================================================== + AU1 AZolF1ToU1(AF1 x){return AU1(x);} + AU2 AZolF2ToU2(AF2 x){return AU2(x);} + AU3 AZolF3ToU3(AF3 x){return AU3(x);} + AU4 AZolF4ToU4(AF4 x){return AU4(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // 2 ops, denormals don't work in 32-bit on PC (and if they are enabled, OMOD is disabled). + AU1 AZolNotF1ToU1(AF1 x){return AU1(AF1_(1.0)-x);} + AU2 AZolNotF2ToU2(AF2 x){return AU2(AF2_(1.0)-x);} + AU3 AZolNotF3ToU3(AF3 x){return AU3(AF3_(1.0)-x);} + AU4 AZolNotF4ToU4(AF4 x){return AU4(AF4_(1.0)-x);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolU1ToF1(AU1 x){return AF1(x);} + AF2 AZolU2ToF2(AU2 x){return AF2(x);} + AF3 AZolU3ToF3(AU3 x){return AF3(x);} + AF4 AZolU4ToF4(AU4 x){return AF4(x);} +//============================================================================================================================== + AF1 AZolAndF1(AF1 x,AF1 y){return min(x,y);} + AF2 AZolAndF2(AF2 x,AF2 y){return min(x,y);} + AF3 AZolAndF3(AF3 x,AF3 y){return min(x,y);} + AF4 AZolAndF4(AF4 x,AF4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ASolAndNotF1(AF1 x,AF1 y){return (-x)*y+AF1_(1.0);} + AF2 ASolAndNotF2(AF2 x,AF2 y){return (-x)*y+AF2_(1.0);} + AF3 ASolAndNotF3(AF3 x,AF3 y){return (-x)*y+AF3_(1.0);} + AF4 ASolAndNotF4(AF4 x,AF4 y){return (-x)*y+AF4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolAndOrF1(AF1 x,AF1 y,AF1 z){return ASatF1(x*y+z);} + AF2 AZolAndOrF2(AF2 x,AF2 y,AF2 z){return ASatF2(x*y+z);} + AF3 AZolAndOrF3(AF3 x,AF3 y,AF3 z){return ASatF3(x*y+z);} + AF4 AZolAndOrF4(AF4 x,AF4 y,AF4 z){return ASatF4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolGtZeroF1(AF1 x){return ASatF1(x*AF1_(A_INFP_F));} + AF2 AZolGtZeroF2(AF2 x){return ASatF2(x*AF2_(A_INFP_F));} + AF3 AZolGtZeroF3(AF3 x){return ASatF3(x*AF3_(A_INFP_F));} + AF4 AZolGtZeroF4(AF4 x){return ASatF4(x*AF4_(A_INFP_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolNotF1(AF1 x){return AF1_(1.0)-x;} + AF2 AZolNotF2(AF2 x){return AF2_(1.0)-x;} + AF3 AZolNotF3(AF3 x){return AF3_(1.0)-x;} + AF4 AZolNotF4(AF4 x){return AF4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolOrF1(AF1 x,AF1 y){return max(x,y);} + AF2 AZolOrF2(AF2 x,AF2 y){return max(x,y);} + AF3 AZolOrF3(AF3 x,AF3 y){return max(x,y);} + AF4 AZolOrF4(AF4 x,AF4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSelF1(AF1 x,AF1 y,AF1 z){AF1 r=(-x)*z+z;return x*y+r;} + AF2 AZolSelF2(AF2 x,AF2 y,AF2 z){AF2 r=(-x)*z+z;return x*y+r;} + AF3 AZolSelF3(AF3 x,AF3 y,AF3 z){AF3 r=(-x)*z+z;return x*y+r;} + AF4 AZolSelF4(AF4 x,AF4 y,AF4 z){AF4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSignedF1(AF1 x){return ASatF1(x*AF1_(A_INFN_F));} + AF2 AZolSignedF2(AF2 x){return ASatF2(x*AF2_(A_INFN_F));} + AF3 AZolSignedF3(AF3 x){return ASatF3(x*AF3_(A_INFN_F));} + AF4 AZolSignedF4(AF4 x){return ASatF4(x*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolZeroPassF1(AF1 x,AF1 y){return AF1_AU1((AU1_AF1(x)!=AU1_(0))?AU1_(0):AU1_AF1(y));} + AF2 AZolZeroPassF2(AF2 x,AF2 y){return AF2_AU2((AU2_AF2(x)!=AU2_(0))?AU2_(0):AU2_AF2(y));} + AF3 AZolZeroPassF3(AF3 x,AF3 y){return AF3_AU3((AU3_AF3(x)!=AU3_(0))?AU3_(0):AU3_AF3(y));} + AF4 AZolZeroPassF4(AF4 x,AF4 y){return AF4_AU4((AU4_AF4(x)!=AU4_(0))?AU4_(0):AU4_AF4(y));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AW1 AZolAndW1(AW1 x,AW1 y){return min(x,y);} + AW2 AZolAndW2(AW2 x,AW2 y){return min(x,y);} + AW3 AZolAndW3(AW3 x,AW3 y){return min(x,y);} + AW4 AZolAndW4(AW4 x,AW4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolNotW1(AW1 x){return x^AW1_(1);} + AW2 AZolNotW2(AW2 x){return x^AW2_(1);} + AW3 AZolNotW3(AW3 x){return x^AW3_(1);} + AW4 AZolNotW4(AW4 x){return x^AW4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolOrW1(AW1 x,AW1 y){return max(x,y);} + AW2 AZolOrW2(AW2 x,AW2 y){return max(x,y);} + AW3 AZolOrW3(AW3 x,AW3 y){return max(x,y);} + AW4 AZolOrW4(AW4 x,AW4 y){return max(x,y);} +//============================================================================================================================== + // Uses denormal trick. + AW1 AZolH1ToW1(AH1 x){return AW1_AH1(x*AH1_AW1(AW1_(1)));} + AW2 AZolH2ToW2(AH2 x){return AW2_AH2(x*AH2_AW2(AW2_(1)));} + AW3 AZolH3ToW3(AH3 x){return AW3_AH3(x*AH3_AW3(AW3_(1)));} + AW4 AZolH4ToW4(AH4 x){return AW4_AH4(x*AH4_AW4(AW4_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + // AMD arch lacks a packed conversion opcode. + AH1 AZolW1ToH1(AW1 x){return AH1_AW1(x*AW1_AH1(AH1_(1.0)));} + AH2 AZolW2ToH2(AW2 x){return AH2_AW2(x*AW2_AH2(AH2_(1.0)));} + AH3 AZolW1ToH3(AW3 x){return AH3_AW3(x*AW3_AH3(AH3_(1.0)));} + AH4 AZolW2ToH4(AW4 x){return AH4_AW4(x*AW4_AH4(AH4_(1.0)));} +//============================================================================================================================== + AH1 AZolAndH1(AH1 x,AH1 y){return min(x,y);} + AH2 AZolAndH2(AH2 x,AH2 y){return min(x,y);} + AH3 AZolAndH3(AH3 x,AH3 y){return min(x,y);} + AH4 AZolAndH4(AH4 x,AH4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASolAndNotH1(AH1 x,AH1 y){return (-x)*y+AH1_(1.0);} + AH2 ASolAndNotH2(AH2 x,AH2 y){return (-x)*y+AH2_(1.0);} + AH3 ASolAndNotH3(AH3 x,AH3 y){return (-x)*y+AH3_(1.0);} + AH4 ASolAndNotH4(AH4 x,AH4 y){return (-x)*y+AH4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolAndOrH1(AH1 x,AH1 y,AH1 z){return ASatH1(x*y+z);} + AH2 AZolAndOrH2(AH2 x,AH2 y,AH2 z){return ASatH2(x*y+z);} + AH3 AZolAndOrH3(AH3 x,AH3 y,AH3 z){return ASatH3(x*y+z);} + AH4 AZolAndOrH4(AH4 x,AH4 y,AH4 z){return ASatH4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolGtZeroH1(AH1 x){return ASatH1(x*AH1_(A_INFP_H));} + AH2 AZolGtZeroH2(AH2 x){return ASatH2(x*AH2_(A_INFP_H));} + AH3 AZolGtZeroH3(AH3 x){return ASatH3(x*AH3_(A_INFP_H));} + AH4 AZolGtZeroH4(AH4 x){return ASatH4(x*AH4_(A_INFP_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolNotH1(AH1 x){return AH1_(1.0)-x;} + AH2 AZolNotH2(AH2 x){return AH2_(1.0)-x;} + AH3 AZolNotH3(AH3 x){return AH3_(1.0)-x;} + AH4 AZolNotH4(AH4 x){return AH4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolOrH1(AH1 x,AH1 y){return max(x,y);} + AH2 AZolOrH2(AH2 x,AH2 y){return max(x,y);} + AH3 AZolOrH3(AH3 x,AH3 y){return max(x,y);} + AH4 AZolOrH4(AH4 x,AH4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSelH1(AH1 x,AH1 y,AH1 z){AH1 r=(-x)*z+z;return x*y+r;} + AH2 AZolSelH2(AH2 x,AH2 y,AH2 z){AH2 r=(-x)*z+z;return x*y+r;} + AH3 AZolSelH3(AH3 x,AH3 y,AH3 z){AH3 r=(-x)*z+z;return x*y+r;} + AH4 AZolSelH4(AH4 x,AH4 y,AH4 z){AH4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSignedH1(AH1 x){return ASatH1(x*AH1_(A_INFN_H));} + AH2 AZolSignedH2(AH2 x){return ASatH2(x*AH2_(A_INFN_H));} + AH3 AZolSignedH3(AH3 x){return ASatH3(x*AH3_(A_INFN_H));} + AH4 AZolSignedH4(AH4 x){return ASatH4(x*AH4_(A_INFN_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COLOR CONVERSIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These are all linear to/from some other space (where 'linear' has been shortened out of the function name). +// So 'ToGamma' is 'LinearToGamma', and 'FromGamma' is 'LinearFromGamma'. +// These are branch free implementations. +// The AToSrgbF1() function is useful for stores for compute shaders for GPUs without hardware linear->sRGB store conversion. +//------------------------------------------------------------------------------------------------------------------------------ +// TRANSFER FUNCTIONS +// ================== +// 709 ..... Rec709 used for some HDTVs +// Gamma ... Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native +// Pq ...... PQ native for HDR10 +// Srgb .... The sRGB output, typical of PC displays, useful for 10-bit output, or storing to 8-bit UNORM without SRGB type +// Two ..... Gamma 2.0, fastest conversion (useful for intermediate pass approximations) +// Three ... Gamma 3.0, less fast, but good for HDR. +//------------------------------------------------------------------------------------------------------------------------------ +// KEEPING TO SPEC +// =============== +// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times. +// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range). +// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range). +// Also there is a slight step in the transition regions. +// Precision of the coefficients in the spec being the likely cause. +// Main usage case of the sRGB code is to do the linear->sRGB converstion in a compute shader before store. +// This is to work around lack of hardware (typically only ROP does the conversion for free). +// To "correct" the linear segment, would be to introduce error, because hardware decode of sRGB->linear is fixed (and free). +// So this header keeps with the spec. +// For linear->sRGB transforms, the linear segment in some respects reduces error, because rounding in that region is linear. +// Rounding in the curved region in hardware (and fast software code) introduces error due to rounding in non-linear. +//------------------------------------------------------------------------------------------------------------------------------ +// FOR PQ +// ====== +// Both input and output is {0.0-1.0}, and where output 1.0 represents 10000.0 cd/m^2. +// All constants are only specified to FP32 precision. +// External PQ source reference, +// - https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESlib.Utilities_Color.a1.0.1.ctl +//------------------------------------------------------------------------------------------------------------------------------ +// PACKED VERSIONS +// =============== +// These are the A*H2() functions. +// There is no PQ functions as FP16 seemed to not have enough precision for the conversion. +// The remaining functions are "good enough" for 8-bit, and maybe 10-bit if not concerned about a few 1-bit errors. +// Precision is lowest in the 709 conversion, higher in sRGB, higher still in Two and Gamma (when using 2.2 at least). +//------------------------------------------------------------------------------------------------------------------------------ +// NOTES +// ===== +// Could be faster for PQ conversions to be in ALU or a texture lookup depending on usage case. +//============================================================================================================================== + #if 1 + AF1 ATo709F1(AF1 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 ATo709F2(AF2 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 ATo709F3(AF3 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + // Note 'rcpX' is '1/x', where the 'x' is what would be used in AFromGamma(). + AF1 AToGammaF1(AF1 c,AF1 rcpX){return pow(c,AF1_(rcpX));} + AF2 AToGammaF2(AF2 c,AF1 rcpX){return pow(c,AF2_(rcpX));} + AF3 AToGammaF3(AF3 c,AF1 rcpX){return pow(c,AF3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToPqF1(AF1 x){AF1 p=pow(x,AF1_(0.159302)); + return pow((AF1_(0.835938)+AF1_(18.8516)*p)/(AF1_(1.0)+AF1_(18.6875)*p),AF1_(78.8438));} + AF2 AToPqF1(AF2 x){AF2 p=pow(x,AF2_(0.159302)); + return pow((AF2_(0.835938)+AF2_(18.8516)*p)/(AF2_(1.0)+AF2_(18.6875)*p),AF2_(78.8438));} + AF3 AToPqF1(AF3 x){AF3 p=pow(x,AF3_(0.159302)); + return pow((AF3_(0.835938)+AF3_(18.8516)*p)/(AF3_(1.0)+AF3_(18.6875)*p),AF3_(78.8438));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToSrgbF1(AF1 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 AToSrgbF2(AF2 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 AToSrgbF3(AF3 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToTwoF1(AF1 c){return sqrt(c);} + AF2 AToTwoF2(AF2 c){return sqrt(c);} + AF3 AToTwoF3(AF3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToThreeF1(AF1 c){return pow(c,AF1_(1.0/3.0));} + AF2 AToThreeF2(AF2 c){return pow(c,AF2_(1.0/3.0));} + AF3 AToThreeF3(AF3 c){return pow(c,AF3_(1.0/3.0));} + #endif +//============================================================================================================================== + #if 1 + // Unfortunately median won't work here. + AF1 AFrom709F1(AF1 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFrom709F2(AF2 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFrom709F3(AF3 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromGammaF1(AF1 c,AF1 x){return pow(c,AF1_(x));} + AF2 AFromGammaF2(AF2 c,AF1 x){return pow(c,AF2_(x));} + AF3 AFromGammaF3(AF3 c,AF1 x){return pow(c,AF3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromPqF1(AF1 x){AF1 p=pow(x,AF1_(0.0126833)); + return pow(ASatF1(p-AF1_(0.835938))/(AF1_(18.8516)-AF1_(18.6875)*p),AF1_(6.27739));} + AF2 AFromPqF1(AF2 x){AF2 p=pow(x,AF2_(0.0126833)); + return pow(ASatF2(p-AF2_(0.835938))/(AF2_(18.8516)-AF2_(18.6875)*p),AF2_(6.27739));} + AF3 AFromPqF1(AF3 x){AF3 p=pow(x,AF3_(0.0126833)); + return pow(ASatF3(p-AF3_(0.835938))/(AF3_(18.8516)-AF3_(18.6875)*p),AF3_(6.27739));} +//------------------------------------------------------------------------------------------------------------------------------ + // Unfortunately median won't work here. + AF1 AFromSrgbF1(AF1 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFromSrgbF2(AF2 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFromSrgbF3(AF3 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromTwoF1(AF1 c){return c*c;} + AF2 AFromTwoF2(AF2 c){return c*c;} + AF3 AFromTwoF3(AF3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromThreeF1(AF1 c){return c*c*c;} + AF2 AFromThreeF2(AF2 c){return c*c*c;} + AF3 AFromThreeF3(AF3 c){return c*c*c;} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 ATo709H1(AH1 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 ATo709H2(AH2 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 ATo709H3(AH3 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToGammaH1(AH1 c,AH1 rcpX){return pow(c,AH1_(rcpX));} + AH2 AToGammaH2(AH2 c,AH1 rcpX){return pow(c,AH2_(rcpX));} + AH3 AToGammaH3(AH3 c,AH1 rcpX){return pow(c,AH3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToSrgbH1(AH1 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 AToSrgbH2(AH2 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 AToSrgbH3(AH3 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToTwoH1(AH1 c){return sqrt(c);} + AH2 AToTwoH2(AH2 c){return sqrt(c);} + AH3 AToTwoH3(AH3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToThreeF1(AH1 c){return pow(c,AH1_(1.0/3.0));} + AH2 AToThreeF2(AH2 c){return pow(c,AH2_(1.0/3.0));} + AH3 AToThreeF3(AH3 c){return pow(c,AH3_(1.0/3.0));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 AFrom709H1(AH1 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AFrom709H2(AH2 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AFrom709H3(AH3 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromGammaH1(AH1 c,AH1 x){return pow(c,AH1_(x));} + AH2 AFromGammaH2(AH2 c,AH1 x){return pow(c,AH2_(x));} + AH3 AFromGammaH3(AH3 c,AH1 x){return pow(c,AH3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AHromSrgbF1(AH1 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AHromSrgbF2(AH2 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AHromSrgbF3(AH3 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromTwoH1(AH1 c){return c*c;} + AH2 AFromTwoH2(AH2 c){return c*c;} + AH3 AFromTwoH3(AH3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromThreeH1(AH1 c){return c*c*c;} + AH2 AFromThreeH2(AH2 c){return c*c*c;} + AH3 AFromThreeH3(AH3 c){return c*c*c;} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CS REMAP +//============================================================================================================================== + // Simple remap 64x1 to 8x8 with rotated 2x2 pixel quads in quad linear. + // 543210 + // ====== + // ..xxx. + // yy...y + AU2 ARmp8x8(AU1 a){return AU2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} +//============================================================================================================================== + // More complex remap 64x1 to 8x8 which is necessary for 2D wave reductions. + // 543210 + // ====== + // .xx..x + // y..yy. + // Details, + // LANE TO 8x8 MAPPING + // =================== + // 00 01 08 09 10 11 18 19 + // 02 03 0a 0b 12 13 1a 1b + // 04 05 0c 0d 14 15 1c 1d + // 06 07 0e 0f 16 17 1e 1f + // 20 21 28 29 30 31 38 39 + // 22 23 2a 2b 32 33 3a 3b + // 24 25 2c 2d 34 35 3c 3d + // 26 27 2e 2f 36 37 3e 3f + AU2 ARmpRed8x8(AU1 a){return AU2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} +//============================================================================================================================== + #ifdef A_HALF + AW2 ARmp8x8H(AU1 a){return AW2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} + AW2 ARmpRed8x8H(AU1 a){return AW2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} + #endif +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// REFERENCE +// +//------------------------------------------------------------------------------------------------------------------------------ +// IEEE FLOAT RULES +// ================ +// - saturate(NaN)=0, saturate(-INF)=0, saturate(+INF)=1 +// - {+/-}0 * {+/-}INF = NaN +// - -INF + (+INF) = NaN +// - {+/-}0 / {+/-}0 = NaN +// - {+/-}INF / {+/-}INF = NaN +// - a<(-0) := sqrt(a) = NaN (a=-0.0 won't NaN) +// - 0 == -0 +// - 4/0 = +INF +// - 4/-0 = -INF +// - 4+INF = +INF +// - 4-INF = -INF +// - 4*(+INF) = +INF +// - 4*(-INF) = -INF +// - -4*(+INF) = -INF +// - sqrt(+INF) = +INF +//------------------------------------------------------------------------------------------------------------------------------ +// FP16 ENCODING +// ============= +// fedcba9876543210 +// ---------------- +// ......mmmmmmmmmm 10-bit mantissa (encodes 11-bit 0.5 to 1.0 except for denormals) +// .eeeee.......... 5-bit exponent +// .00000.......... denormals +// .00001.......... -14 exponent +// .11110.......... 15 exponent +// .111110000000000 infinity +// .11111nnnnnnnnnn NaN with n!=0 +// s............... sign +//------------------------------------------------------------------------------------------------------------------------------ +// FP16/INT16 ALIASING DENORMAL +// ============================ +// 11-bit unsigned integers alias with half float denormal/normal values, +// 1 = 2^(-24) = 1/16777216 ....................... first denormal value +// 2 = 2^(-23) +// ... +// 1023 = 2^(-14)*(1-2^(-10)) = 2^(-14)*(1-1/1024) ... last denormal value +// 1024 = 2^(-14) = 1/16384 .......................... first normal value that still maps to integers +// 2047 .............................................. last normal value that still maps to integers +// Scaling limits, +// 2^15 = 32768 ...................................... largest power of 2 scaling +// Largest pow2 conversion mapping is at *32768, +// 1 : 2^(-9) = 1/512 +// 2 : 1/256 +// 4 : 1/128 +// 8 : 1/64 +// 16 : 1/32 +// 32 : 1/16 +// 64 : 1/8 +// 128 : 1/4 +// 256 : 1/2 +// 512 : 1 +// 1024 : 2 +// 2047 : a little less than 4 +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU/CPU PORTABILITY +// +// +//------------------------------------------------------------------------------------------------------------------------------ +// This is the GPU implementation. +// See the CPU implementation for docs. +//============================================================================================================================== +#ifdef A_GPU + #define A_TRUE true + #define A_FALSE false + #define A_STATIC +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD2 + #define retAD3 AD3 + #define retAD4 AD4 + #define retAF2 AF2 + #define retAF3 AF3 + #define retAF4 AF4 + #define retAL2 AL2 + #define retAL3 AL3 + #define retAL4 AL4 + #define retAU2 AU2 + #define retAU3 AU3 + #define retAU4 AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 in AD2 + #define inAD3 in AD3 + #define inAD4 in AD4 + #define inAF2 in AF2 + #define inAF3 in AF3 + #define inAF4 in AF4 + #define inAL2 in AL2 + #define inAL3 in AL3 + #define inAL4 in AL4 + #define inAU2 in AU2 + #define inAU3 in AU3 + #define inAU4 in AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 inout AD2 + #define inoutAD3 inout AD3 + #define inoutAD4 inout AD4 + #define inoutAF2 inout AF2 + #define inoutAF3 inout AF3 + #define inoutAF4 inout AF4 + #define inoutAL2 inout AL2 + #define inoutAL3 inout AL3 + #define inoutAL4 inout AL4 + #define inoutAU2 inout AU2 + #define inoutAU3 inout AU3 + #define inoutAU4 inout AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 out AD2 + #define outAD3 out AD3 + #define outAD4 out AD4 + #define outAF2 out AF2 + #define outAF3 out AF3 + #define outAF4 out AF4 + #define outAL2 out AL2 + #define outAL3 out AL3 + #define outAL4 out AL4 + #define outAU2 out AU2 + #define outAU3 out AU3 + #define outAU4 out AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD2 x + #define varAD3(x) AD3 x + #define varAD4(x) AD4 x + #define varAF2(x) AF2 x + #define varAF3(x) AF3 x + #define varAF4(x) AF4 x + #define varAL2(x) AL2 x + #define varAL3(x) AL3 x + #define varAL4(x) AL4 x + #define varAU2(x) AU2 x + #define varAU3(x) AU3 x + #define varAU4(x) AU4 x +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) AD2(x,y) + #define initAD3(x,y,z) AD3(x,y,z) + #define initAD4(x,y,z,w) AD4(x,y,z,w) + #define initAF2(x,y) AF2(x,y) + #define initAF3(x,y,z) AF3(x,y,z) + #define initAF4(x,y,z,w) AF4(x,y,z,w) + #define initAL2(x,y) AL2(x,y) + #define initAL3(x,y,z) AL3(x,y,z) + #define initAL4(x,y,z,w) AL4(x,y,z,w) + #define initAU2(x,y) AU2(x,y) + #define initAU3(x,y,z) AU3(x,y,z) + #define initAU4(x,y,z,w) AU4(x,y,z,w) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//============================================================================================================================== + #define AAbsD1(a) abs(AD1(a)) + #define AAbsF1(a) abs(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ACosD1(a) cos(AD1(a)) + #define ACosF1(a) cos(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ADotD2(a,b) dot(AD2(a),AD2(b)) + #define ADotD3(a,b) dot(AD3(a),AD3(b)) + #define ADotD4(a,b) dot(AD4(a),AD4(b)) + #define ADotF2(a,b) dot(AF2(a),AF2(b)) + #define ADotF3(a,b) dot(AF3(a),AF3(b)) + #define ADotF4(a,b) dot(AF4(a),AF4(b)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AExp2D1(a) exp2(AD1(a)) + #define AExp2F1(a) exp2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AFloorD1(a) floor(AD1(a)) + #define AFloorF1(a) floor(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ALog2D1(a) log2(AD1(a)) + #define ALog2F1(a) log2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMaxD1(a,b) max(a,b) + #define AMaxF1(a,b) max(a,b) + #define AMaxL1(a,b) max(a,b) + #define AMaxU1(a,b) max(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMinD1(a,b) min(a,b) + #define AMinF1(a,b) min(a,b) + #define AMinL1(a,b) min(a,b) + #define AMinU1(a,b) min(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASinD1(a) sin(AD1(a)) + #define ASinF1(a) sin(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASqrtD1(a) sqrt(AD1(a)) + #define ASqrtF1(a) sqrt(AF1(a)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + #define APowD1(a,b) pow(AD1(a),AF1(b)) + #define APowF1(a,b) pow(AF1(a),AF1(b)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + #ifdef A_DUBL + AD2 opAAbsD2(outAD2 d,inAD2 a){d=abs(a);return d;} + AD3 opAAbsD3(outAD3 d,inAD3 a){d=abs(a);return d;} + AD4 opAAbsD4(outAD4 d,inAD4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d=a+b;return d;} + AD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d=a+b;return d;} + AD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d=a+AD2_(b);return d;} + AD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d=a+AD3_(b);return d;} + AD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d=a+AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opACpyD2(outAD2 d,inAD2 a){d=a;return d;} + AD3 opACpyD3(outAD3 d,inAD3 a){d=a;return d;} + AD4 opACpyD4(outAD4 d,inAD4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d=ALerpD2(a,b,c);return d;} + AD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d=ALerpD3(a,b,c);return d;} + AD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d=ALerpD4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d=ALerpD2(a,b,AD2_(c));return d;} + AD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d=ALerpD3(a,b,AD3_(c));return d;} + AD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d=ALerpD4(a,b,AD4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d=max(a,b);return d;} + AD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d=max(a,b);return d;} + AD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d=min(a,b);return d;} + AD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d=min(a,b);return d;} + AD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d=a*b;return d;} + AD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d=a*b;return d;} + AD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d=a*AD2_(b);return d;} + AD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d=a*AD3_(b);return d;} + AD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d=a*AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opANegD2(outAD2 d,inAD2 a){d=-a;return d;} + AD3 opANegD3(outAD3 d,inAD3 a){d=-a;return d;} + AD4 opANegD4(outAD4 d,inAD4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opARcpD2(outAD2 d,inAD2 a){d=ARcpD2(a);return d;} + AD3 opARcpD3(outAD3 d,inAD3 a){d=ARcpD3(a);return d;} + AD4 opARcpD4(outAD4 d,inAD4 a){d=ARcpD4(a);return d;} + #endif +//============================================================================================================================== + AF2 opAAbsF2(outAF2 d,inAF2 a){d=abs(a);return d;} + AF3 opAAbsF3(outAF3 d,inAF3 a){d=abs(a);return d;} + AF4 opAAbsF4(outAF4 d,inAF4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d=a+b;return d;} + AF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d=a+b;return d;} + AF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d=a+AF2_(b);return d;} + AF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d=a+AF3_(b);return d;} + AF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d=a+AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opACpyF2(outAF2 d,inAF2 a){d=a;return d;} + AF3 opACpyF3(outAF3 d,inAF3 a){d=a;return d;} + AF4 opACpyF4(outAF4 d,inAF4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d=ALerpF2(a,b,c);return d;} + AF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d=ALerpF3(a,b,c);return d;} + AF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d=ALerpF4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d=ALerpF2(a,b,AF2_(c));return d;} + AF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d=ALerpF3(a,b,AF3_(c));return d;} + AF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d=ALerpF4(a,b,AF4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d=max(a,b);return d;} + AF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d=max(a,b);return d;} + AF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d=min(a,b);return d;} + AF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d=min(a,b);return d;} + AF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d=a*b;return d;} + AF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d=a*b;return d;} + AF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d=a*AF2_(b);return d;} + AF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d=a*AF3_(b);return d;} + AF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d=a*AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opANegF2(outAF2 d,inAF2 a){d=-a;return d;} + AF3 opANegF3(outAF3 d,inAF3 a){d=-a;return d;} + AF4 opANegF4(outAF4 d,inAF4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opARcpF2(outAF2 d,inAF2 a){d=ARcpF2(a);return d;} + AF3 opARcpF3(outAF3 d,inAF3 a){d=ARcpF3(a);return d;} + AF4 opARcpF4(outAF4 d,inAF4 a){d=ARcpF4(a);return d;} +#endif diff --git a/src/video_core/host_shaders/fsr/ffx_fsr1.h b/src/video_core/host_shaders/fsr/ffx_fsr1.h new file mode 100644 index 000000000..b0fe75ea9 --- /dev/null +++ b/src/video_core/host_shaders/fsr/ffx_fsr1.h @@ -0,0 +1,1200 @@ +// clang-format off +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// AMD FidelityFX SUPER RESOLUTION [FSR 1] ::: SPATIAL SCALING & EXTRAS - v1.20210629 +// +// +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// FSR is a collection of algorithms relating to generating a higher resolution image. +// This specific header focuses on single-image non-temporal image scaling, and related tools. +// +// The core functions are EASU and RCAS: +// [EASU] Edge Adaptive Spatial Upsampling ....... 1x to 4x area range spatial scaling, clamped adaptive elliptical filter. +// [RCAS] Robust Contrast Adaptive Sharpening .... A non-scaling variation on CAS. +// RCAS needs to be applied after EASU as a separate pass. +// +// Optional utility functions are: +// [LFGA] Linear Film Grain Applicator ........... Tool to apply film grain after scaling. +// [SRTM] Simple Reversible Tone-Mapper .......... Linear HDR {0 to FP16_MAX} to {0 to 1} and back. +// [TEPD] Temporal Energy Preserving Dither ...... Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// See each individual sub-section for inline documentation. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FUNCTION PERMUTATIONS +// ===================== +// *F() ..... Single item computation with 32-bit. +// *H() ..... Single item computation with 16-bit, with packing (aka two 16-bit ops in parallel) when possible. +// *Hx2() ... Processing two items in parallel with 16-bit, easier packing. +// Not all interfaces in this file have a *Hx2() form. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [EASU] EDGE ADAPTIVE SPATIAL UPSAMPLING +// +//------------------------------------------------------------------------------------------------------------------------------ +// EASU provides a high quality spatial-only scaling at relatively low cost. +// Meaning EASU is appropiate for laptops and other low-end GPUs. +// Quality from 1x to 4x area scaling is good. +//------------------------------------------------------------------------------------------------------------------------------ +// The scalar uses a modified fast approximation to the standard lanczos(size=2) kernel. +// EASU runs in a single pass, so it applies a directionally and anisotropically adaptive radial lanczos. +// This is also kept as simple as possible to have minimum runtime. +//------------------------------------------------------------------------------------------------------------------------------ +// The lanzcos filter has negative lobes, so by itself it will introduce ringing. +// To remove all ringing, the algorithm uses the nearest 2x2 input texels as a neighborhood, +// and limits output to the minimum and maximum of that neighborhood. +//------------------------------------------------------------------------------------------------------------------------------ +// Input image requirements: +// +// Color needs to be encoded as 3 channel[red, green, blue](e.g.XYZ not supported) +// Each channel needs to be in the range[0, 1] +// Any color primaries are supported +// Display / tonemapping curve needs to be as if presenting to sRGB display or similar(e.g.Gamma 2.0) +// There should be no banding in the input +// There should be no high amplitude noise in the input +// There should be no noise in the input that is not at input pixel granularity +// For performance purposes, use 32bpp formats +//------------------------------------------------------------------------------------------------------------------------------ +// Best to apply EASU at the end of the frame after tonemapping +// but before film grain or composite of the UI. +//------------------------------------------------------------------------------------------------------------------------------ +// Example of including this header for D3D HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan GLSL : +// +// #define A_GPU 1 +// #define A_GLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HLSL_6_2 1 +// #define A_NO_16_BIT_CAST 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of declaring the required input callbacks for GLSL : +// The callbacks need to gather4 for each color channel using the specified texture coordinate 'p'. +// EASU uses gather4 to reduce position computation logic and for free Arrays of Structures to Structures of Arrays conversion. +// +// AH4 FsrEasuRH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,0));} +// AH4 FsrEasuGH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,1));} +// AH4 FsrEasuBH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,2));} +// ... +// The FsrEasuCon function needs to be called from the CPU or GPU to set up constants. +// The difference in viewport and input image size is there to support Dynamic Resolution Scaling. +// To use FsrEasuCon() on the CPU, define A_CPU before including ffx_a and ffx_fsr1. +// Including a GPU example here, the 'con0' through 'con3' values would be stored out to a constant buffer. +// AU4 con0,con1,con2,con3; +// FsrEasuCon(con0,con1,con2,con3, +// 1920.0,1080.0, // Viewport size (top left aligned) in the input image which is to be scaled. +// 3840.0,2160.0, // The size of the input image. +// 2560.0,1440.0); // The output resolution. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrEasuCon( +outAU4 con0, +outAU4 con1, +outAU4 con2, +outAU4 con3, +// This the rendered image resolution being upscaled +AF1 inputViewportInPixelsX, +AF1 inputViewportInPixelsY, +// This is the resolution of the resource containing the input image (useful for dynamic resolution) +AF1 inputSizeInPixelsX, +AF1 inputSizeInPixelsY, +// This is the display resolution which the input image gets upscaled to +AF1 outputSizeInPixelsX, +AF1 outputSizeInPixelsY){ + // Output integer position to a pixel position in viewport. + con0[0]=AU1_AF1(inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)); + con0[1]=AU1_AF1(inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)); + con0[2]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)-AF1_(0.5)); + con0[3]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)-AF1_(0.5)); + // Viewport pixel position to normalized image space. + // This is used to get upper-left of 'F' tap. + con1[0]=AU1_AF1(ARcpF1(inputSizeInPixelsX)); + con1[1]=AU1_AF1(ARcpF1(inputSizeInPixelsY)); + // Centers of gather4, first offset from upper-left of 'F'. + // +---+---+ + // | | | + // +--(0)--+ + // | b | c | + // +---F---+---+---+ + // | e | f | g | h | + // +--(1)--+--(2)--+ + // | i | j | k | l | + // +---+---+---+---+ + // | n | o | + // +--(3)--+ + // | | | + // +---+---+ + con1[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con1[3]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsY)); + // These are from (0) instead of 'F'. + con2[0]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsX)); + con2[1]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con2[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con2[3]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con3[0]=AU1_AF1(AF1_( 0.0)*ARcpF1(inputSizeInPixelsX)); + con3[1]=AU1_AF1(AF1_( 4.0)*ARcpF1(inputSizeInPixelsY)); + con3[2]=con3[3]=0;} + +//If the an offset into the input image resource +A_STATIC void FsrEasuConOffset( + outAU4 con0, + outAU4 con1, + outAU4 con2, + outAU4 con3, + // This the rendered image resolution being upscaled + AF1 inputViewportInPixelsX, + AF1 inputViewportInPixelsY, + // This is the resolution of the resource containing the input image (useful for dynamic resolution) + AF1 inputSizeInPixelsX, + AF1 inputSizeInPixelsY, + // This is the display resolution which the input image gets upscaled to + AF1 outputSizeInPixelsX, + AF1 outputSizeInPixelsY, + // This is the input image offset into the resource containing it (useful for dynamic resolution) + AF1 inputOffsetInPixelsX, + AF1 inputOffsetInPixelsY) { + FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); + con0[2] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsX * ARcpF1(outputSizeInPixelsX) - AF1_(0.5) + inputOffsetInPixelsX); + con0[3] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsY * ARcpF1(outputSizeInPixelsY) - AF1_(0.5) + inputOffsetInPixelsY); +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_EASU_F) + // Input callback prototypes, need to be implemented by calling shader + AF4 FsrEasuRF(AF2 p); + AF4 FsrEasuGF(AF2 p); + AF4 FsrEasuBF(AF2 p); +//------------------------------------------------------------------------------------------------------------------------------ + // Filtering for a given tap for the scalar. + void FsrEasuTapF( + inout AF3 aC, // Accumulated color, with negative lobe. + inout AF1 aW, // Accumulated weight. + AF2 off, // Pixel offset from resolve position to tap. + AF2 dir, // Gradient direction. + AF2 len, // Length. + AF1 lob, // Negative lobe strength. + AF1 clp, // Clipping point. + AF3 c){ // Tap color. + // Rotate offset by direction. + AF2 v; + v.x=(off.x*( dir.x))+(off.y*dir.y); + v.y=(off.x*(-dir.y))+(off.y*dir.x); + // Anisotropy. + v*=len; + // Compute distance^2. + AF1 d2=v.x*v.x+v.y*v.y; + // Limit to the window as at corner, 2 taps can easily be outside. + d2=min(d2,clp); + // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. + // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 + // |_______________________________________| |_______________| + // base window + // The general form of the 'base' is, + // (a*(b*x^2-1)^2-(a-1)) + // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. + AF1 wB=AF1_(2.0/5.0)*d2+AF1_(-1.0); + AF1 wA=lob*d2+AF1_(-1.0); + wB*=wB; + wA*=wA; + wB=AF1_(25.0/16.0)*wB+AF1_(-(25.0/16.0-1.0)); + AF1 w=wB*wA; + // Do weighted average. + aC+=c*w;aW+=w;} +//------------------------------------------------------------------------------------------------------------------------------ + // Accumulate direction and length. + void FsrEasuSetF( + inout AF2 dir, + inout AF1 len, + AF2 pp, + AP1 biS,AP1 biT,AP1 biU,AP1 biV, + AF1 lA,AF1 lB,AF1 lC,AF1 lD,AF1 lE){ + // Compute bilinear weight, branches factor out as predicates are compiler time immediates. + // s t + // u v + AF1 w = AF1_(0.0); + if(biS)w=(AF1_(1.0)-pp.x)*(AF1_(1.0)-pp.y); + if(biT)w= pp.x *(AF1_(1.0)-pp.y); + if(biU)w=(AF1_(1.0)-pp.x)* pp.y ; + if(biV)w= pp.x * pp.y ; + // Direction is the '+' diff. + // a + // b c d + // e + // Then takes magnitude from abs average of both sides of 'c'. + // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. + AF1 dc=lD-lC; + AF1 cb=lC-lB; + AF1 lenX=max(abs(dc),abs(cb)); + lenX=APrxLoRcpF1(lenX); + AF1 dirX=lD-lB; + dir.x+=dirX*w; + lenX=ASatF1(abs(dirX)*lenX); + lenX*=lenX; + len+=lenX*w; + // Repeat for the y axis. + AF1 ec=lE-lC; + AF1 ca=lC-lA; + AF1 lenY=max(abs(ec),abs(ca)); + lenY=APrxLoRcpF1(lenY); + AF1 dirY=lE-lA; + dir.y+=dirY*w; + lenY=ASatF1(abs(dirY)*lenY); + lenY*=lenY; + len+=lenY*w;} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrEasuF( + out AF3 pix, + AU2 ip, // Integer pixel position in output. + AU4 con0, // Constants generated by FsrEasuCon(). + AU4 con1, + AU4 con2, + AU4 con3){ +//------------------------------------------------------------------------------------------------------------------------------ + // Get position of 'f'. + AF2 pp=AF2(ip)*AF2_AU2(con0.xy)+AF2_AU2(con0.zw); + AF2 fp=floor(pp); + pp-=fp; +//------------------------------------------------------------------------------------------------------------------------------ + // 12-tap kernel. + // b c + // e f g h + // i j k l + // n o + // Gather 4 ordering. + // a b + // r g + // For packed FP16, need either {rg} or {ab} so using the following setup for gather in all versions, + // a b <- unused (z) + // r g + // a b a b + // r g r g + // a b + // r g <- unused (z) + // Allowing dead-code removal to remove the 'z's. + AF2 p0=fp*AF2_AU2(con1.xy)+AF2_AU2(con1.zw); + // These are from p0 to avoid pulling two constants on pre-Navi hardware. + AF2 p1=p0+AF2_AU2(con2.xy); + AF2 p2=p0+AF2_AU2(con2.zw); + AF2 p3=p0+AF2_AU2(con3.xy); + AF4 bczzR=FsrEasuRF(p0); + AF4 bczzG=FsrEasuGF(p0); + AF4 bczzB=FsrEasuBF(p0); + AF4 ijfeR=FsrEasuRF(p1); + AF4 ijfeG=FsrEasuGF(p1); + AF4 ijfeB=FsrEasuBF(p1); + AF4 klhgR=FsrEasuRF(p2); + AF4 klhgG=FsrEasuGF(p2); + AF4 klhgB=FsrEasuBF(p2); + AF4 zzonR=FsrEasuRF(p3); + AF4 zzonG=FsrEasuGF(p3); + AF4 zzonB=FsrEasuBF(p3); +//------------------------------------------------------------------------------------------------------------------------------ + // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). + AF4 bczzL=bczzB*AF4_(0.5)+(bczzR*AF4_(0.5)+bczzG); + AF4 ijfeL=ijfeB*AF4_(0.5)+(ijfeR*AF4_(0.5)+ijfeG); + AF4 klhgL=klhgB*AF4_(0.5)+(klhgR*AF4_(0.5)+klhgG); + AF4 zzonL=zzonB*AF4_(0.5)+(zzonR*AF4_(0.5)+zzonG); + // Rename. + AF1 bL=bczzL.x; + AF1 cL=bczzL.y; + AF1 iL=ijfeL.x; + AF1 jL=ijfeL.y; + AF1 fL=ijfeL.z; + AF1 eL=ijfeL.w; + AF1 kL=klhgL.x; + AF1 lL=klhgL.y; + AF1 hL=klhgL.z; + AF1 gL=klhgL.w; + AF1 oL=zzonL.z; + AF1 nL=zzonL.w; + // Accumulate for bilinear interpolation. + AF2 dir=AF2_(0.0); + AF1 len=AF1_(0.0); + FsrEasuSetF(dir,len,pp,true, false,false,false,bL,eL,fL,gL,jL); + FsrEasuSetF(dir,len,pp,false,true ,false,false,cL,fL,gL,hL,kL); + FsrEasuSetF(dir,len,pp,false,false,true ,false,fL,iL,jL,kL,nL); + FsrEasuSetF(dir,len,pp,false,false,false,true ,gL,jL,kL,lL,oL); +//------------------------------------------------------------------------------------------------------------------------------ + // Normalize with approximation, and cleanup close to zero. + AF2 dir2=dir*dir; + AF1 dirR=dir2.x+dir2.y; + AP1 zro=dirR w = -m/(n+e+w+s) +// 1 == (w*(n+e+w+s)+m)/(4*w+1) -> w = (1-m)/(n+e+w+s-4*1) +// Then chooses the 'w' which results in no clipping, limits 'w', and multiplies by the 'sharp' amount. +// This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues. +// So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps. +// As well as switching from 'm' to either the minimum or maximum (depending on side), to help in energy conservation. +// This stabilizes RCAS. +// RCAS does a simple highpass which is normalized against the local contrast then shaped, +// 0.25 +// 0.25 -1 0.25 +// 0.25 +// This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges. +// +// GLSL example for the required callbacks : +// +// AH4 FsrRcasLoadH(ASW2 p){return AH4(imageLoad(imgSrc,ASU2(p)));} +// void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b) +// { +// //do any simple input color conversions here or leave empty if none needed +// } +// +// FsrRcasCon need to be called from the CPU or GPU to set up constants. +// Including a GPU example here, the 'con' value would be stored out to a constant buffer. +// +// AU4 con; +// FsrRcasCon(con, +// 0.0); // The scale is {0.0 := maximum sharpness, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +// --------------- +// RCAS sharpening supports a CAS-like pass-through alpha via, +// #define FSR_RCAS_PASSTHROUGH_ALPHA 1 +// RCAS also supports a define to enable a more expensive path to avoid some sharpening of noise. +// Would suggest it is better to apply film grain after RCAS sharpening (and after scaling) instead of using this define, +// #define FSR_RCAS_DENOISE 1 +//============================================================================================================================== +// This is set at the limit of providing unnatural results for sharpening. +#define FSR_RCAS_LIMIT (0.25-(1.0/16.0)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrRcasCon( +outAU4 con, +// The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +AF1 sharpness){ + // Transform from stops to linear value. + sharpness=AExp2F1(-sharpness); + varAF2(hSharp)=initAF2(sharpness,sharpness); + con[0]=AU1_AF1(sharpness); + con[1]=AU1_AH2_AF2(hSharp); + con[2]=0; + con[3]=0;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_RCAS_F) + // Input callback prototypes that need to be implemented by calling shader + AF4 FsrRcasLoadF(ASU2 p); + void FsrRcasInputF(inout AF1 r,inout AF1 g,inout AF1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasF( + out AF1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AF1 pixG, + out AF1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AF1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASU2 sp=ASU2(ip); + AF3 b=FsrRcasLoadF(sp+ASU2( 0,-1)).rgb; + AF3 d=FsrRcasLoadF(sp+ASU2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AF4 ee=FsrRcasLoadF(sp); + AF3 e=ee.rgb;pixA=ee.a; + #else + AF3 e=FsrRcasLoadF(sp).rgb; + #endif + AF3 f=FsrRcasLoadF(sp+ASU2( 1, 0)).rgb; + AF3 h=FsrRcasLoadF(sp+ASU2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AF1 bR=b.r; + AF1 bG=b.g; + AF1 bB=b.b; + AF1 dR=d.r; + AF1 dG=d.g; + AF1 dB=d.b; + AF1 eR=e.r; + AF1 eG=e.g; + AF1 eB=e.b; + AF1 fR=f.r; + AF1 fG=f.g; + AF1 fB=f.b; + AF1 hR=h.r; + AF1 hG=h.g; + AF1 hB=h.b; + // Run optional input transform. + FsrRcasInputF(bR,bG,bB); + FsrRcasInputF(dR,dG,dB); + FsrRcasInputF(eR,eG,eB); + FsrRcasInputF(fR,fG,fB); + FsrRcasInputF(hR,hG,hB); + // Luma times 2. + AF1 bL=bB*AF1_(0.5)+(bR*AF1_(0.5)+bG); + AF1 dL=dB*AF1_(0.5)+(dR*AF1_(0.5)+dG); + AF1 eL=eB*AF1_(0.5)+(eR*AF1_(0.5)+eG); + AF1 fL=fB*AF1_(0.5)+(fR*AF1_(0.5)+fG); + AF1 hL=hB*AF1_(0.5)+(hR*AF1_(0.5)+hG); + // Noise detection. + AF1 nz=AF1_(0.25)*bL+AF1_(0.25)*dL+AF1_(0.25)*fL+AF1_(0.25)*hL-eL; + nz=ASatF1(abs(nz)*APrxMedRcpF1(AMax3F1(AMax3F1(bL,dL,eL),fL,hL)-AMin3F1(AMin3F1(bL,dL,eL),fL,hL))); + nz=AF1_(-0.5)*nz+AF1_(1.0); + // Min and max of ring. + AF1 mn4R=min(AMin3F1(bR,dR,fR),hR); + AF1 mn4G=min(AMin3F1(bG,dG,fG),hG); + AF1 mn4B=min(AMin3F1(bB,dB,fB),hB); + AF1 mx4R=max(AMax3F1(bR,dR,fR),hR); + AF1 mx4G=max(AMax3F1(bG,dG,fG),hG); + AF1 mx4B=max(AMax3F1(bB,dB,fB),hB); + // Immediate constants for peak range. + AF2 peakC=AF2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AF1 hitMinR=min(mn4R,eR)*ARcpF1(AF1_(4.0)*mx4R); + AF1 hitMinG=min(mn4G,eG)*ARcpF1(AF1_(4.0)*mx4G); + AF1 hitMinB=min(mn4B,eB)*ARcpF1(AF1_(4.0)*mx4B); + AF1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpF1(AF1_(4.0)*mn4R+peakC.y); + AF1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpF1(AF1_(4.0)*mn4G+peakC.y); + AF1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpF1(AF1_(4.0)*mn4B+peakC.y); + AF1 lobeR=max(-hitMinR,hitMaxR); + AF1 lobeG=max(-hitMinG,hitMaxG); + AF1 lobeB=max(-hitMinB,hitMaxB); + AF1 lobe=max(AF1_(-FSR_RCAS_LIMIT),min(AMax3F1(lobeR,lobeG,lobeB),AF1_(0.0)))*AF1_AU1(con.x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AF1 rcpL=APrxMedRcpF1(AF1_(4.0)*lobe+AF1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL; + return;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_H) + // Input callback prototypes that need to be implemented by calling shader + AH4 FsrRcasLoadH(ASW2 p); + void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasH( + out AH1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AH1 pixG, + out AH1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Sharpening algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASW2 sp=ASW2(ip); + AH3 b=FsrRcasLoadH(sp+ASW2( 0,-1)).rgb; + AH3 d=FsrRcasLoadH(sp+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee=FsrRcasLoadH(sp); + AH3 e=ee.rgb;pixA=ee.a; + #else + AH3 e=FsrRcasLoadH(sp).rgb; + #endif + AH3 f=FsrRcasLoadH(sp+ASW2( 1, 0)).rgb; + AH3 h=FsrRcasLoadH(sp+ASW2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AH1 bR=b.r; + AH1 bG=b.g; + AH1 bB=b.b; + AH1 dR=d.r; + AH1 dG=d.g; + AH1 dB=d.b; + AH1 eR=e.r; + AH1 eG=e.g; + AH1 eB=e.b; + AH1 fR=f.r; + AH1 fG=f.g; + AH1 fB=f.b; + AH1 hR=h.r; + AH1 hG=h.g; + AH1 hB=h.b; + // Run optional input transform. + FsrRcasInputH(bR,bG,bB); + FsrRcasInputH(dR,dG,dB); + FsrRcasInputH(eR,eG,eB); + FsrRcasInputH(fR,fG,fB); + FsrRcasInputH(hR,hG,hB); + // Luma times 2. + AH1 bL=bB*AH1_(0.5)+(bR*AH1_(0.5)+bG); + AH1 dL=dB*AH1_(0.5)+(dR*AH1_(0.5)+dG); + AH1 eL=eB*AH1_(0.5)+(eR*AH1_(0.5)+eG); + AH1 fL=fB*AH1_(0.5)+(fR*AH1_(0.5)+fG); + AH1 hL=hB*AH1_(0.5)+(hR*AH1_(0.5)+hG); + // Noise detection. + AH1 nz=AH1_(0.25)*bL+AH1_(0.25)*dL+AH1_(0.25)*fL+AH1_(0.25)*hL-eL; + nz=ASatH1(abs(nz)*APrxMedRcpH1(AMax3H1(AMax3H1(bL,dL,eL),fL,hL)-AMin3H1(AMin3H1(bL,dL,eL),fL,hL))); + nz=AH1_(-0.5)*nz+AH1_(1.0); + // Min and max of ring. + AH1 mn4R=min(AMin3H1(bR,dR,fR),hR); + AH1 mn4G=min(AMin3H1(bG,dG,fG),hG); + AH1 mn4B=min(AMin3H1(bB,dB,fB),hB); + AH1 mx4R=max(AMax3H1(bR,dR,fR),hR); + AH1 mx4G=max(AMax3H1(bG,dG,fG),hG); + AH1 mx4B=max(AMax3H1(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH1 hitMinR=min(mn4R,eR)*ARcpH1(AH1_(4.0)*mx4R); + AH1 hitMinG=min(mn4G,eG)*ARcpH1(AH1_(4.0)*mx4G); + AH1 hitMinB=min(mn4B,eB)*ARcpH1(AH1_(4.0)*mx4B); + AH1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH1(AH1_(4.0)*mn4R+peakC.y); + AH1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH1(AH1_(4.0)*mn4G+peakC.y); + AH1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH1(AH1_(4.0)*mn4B+peakC.y); + AH1 lobeR=max(-hitMinR,hitMaxR); + AH1 lobeG=max(-hitMinG,hitMaxG); + AH1 lobeB=max(-hitMinB,hitMaxB); + AH1 lobe=max(AH1_(-FSR_RCAS_LIMIT),min(AMax3H1(lobeR,lobeG,lobeB),AH1_(0.0)))*AH2_AU1(con.y).x; + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH1 rcpL=APrxMedRcpH1(AH1_(4.0)*lobe+AH1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_HX2) + // Input callback prototypes that need to be implemented by the calling shader + AH4 FsrRcasLoadHx2(ASW2 p); + void FsrRcasInputHx2(inout AH2 r,inout AH2 g,inout AH2 b); +//------------------------------------------------------------------------------------------------------------------------------ + // Can be used to convert from packed Structures of Arrays to Arrays of Structures for store. + void FsrRcasDepackHx2(out AH4 pix0,out AH4 pix1,AH2 pixR,AH2 pixG,AH2 pixB){ + #ifdef A_HLSL + // Invoke a slower path for DX only, since it won't allow uninitialized values. + pix0.a=pix1.a=0.0; + #endif + pix0.rgb=AH3(pixR.x,pixG.x,pixB.x); + pix1.rgb=AH3(pixR.y,pixG.y,pixB.y);} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasHx2( + // Output values are for 2 8x8 tiles in a 16x8 region. + // pix.x = left 8x8 tile + // pix.y = right 8x8 tile + // This enables later processing to easily be packed as well. + out AH2 pixR, + out AH2 pixG, + out AH2 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH2 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // No scaling algorithm uses minimal 3x3 pixel neighborhood. + ASW2 sp0=ASW2(ip); + AH3 b0=FsrRcasLoadHx2(sp0+ASW2( 0,-1)).rgb; + AH3 d0=FsrRcasLoadHx2(sp0+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee0=FsrRcasLoadHx2(sp0); + AH3 e0=ee0.rgb;pixA.r=ee0.a; + #else + AH3 e0=FsrRcasLoadHx2(sp0).rgb; + #endif + AH3 f0=FsrRcasLoadHx2(sp0+ASW2( 1, 0)).rgb; + AH3 h0=FsrRcasLoadHx2(sp0+ASW2( 0, 1)).rgb; + ASW2 sp1=sp0+ASW2(8,0); + AH3 b1=FsrRcasLoadHx2(sp1+ASW2( 0,-1)).rgb; + AH3 d1=FsrRcasLoadHx2(sp1+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee1=FsrRcasLoadHx2(sp1); + AH3 e1=ee1.rgb;pixA.g=ee1.a; + #else + AH3 e1=FsrRcasLoadHx2(sp1).rgb; + #endif + AH3 f1=FsrRcasLoadHx2(sp1+ASW2( 1, 0)).rgb; + AH3 h1=FsrRcasLoadHx2(sp1+ASW2( 0, 1)).rgb; + // Arrays of Structures to Structures of Arrays conversion. + AH2 bR=AH2(b0.r,b1.r); + AH2 bG=AH2(b0.g,b1.g); + AH2 bB=AH2(b0.b,b1.b); + AH2 dR=AH2(d0.r,d1.r); + AH2 dG=AH2(d0.g,d1.g); + AH2 dB=AH2(d0.b,d1.b); + AH2 eR=AH2(e0.r,e1.r); + AH2 eG=AH2(e0.g,e1.g); + AH2 eB=AH2(e0.b,e1.b); + AH2 fR=AH2(f0.r,f1.r); + AH2 fG=AH2(f0.g,f1.g); + AH2 fB=AH2(f0.b,f1.b); + AH2 hR=AH2(h0.r,h1.r); + AH2 hG=AH2(h0.g,h1.g); + AH2 hB=AH2(h0.b,h1.b); + // Run optional input transform. + FsrRcasInputHx2(bR,bG,bB); + FsrRcasInputHx2(dR,dG,dB); + FsrRcasInputHx2(eR,eG,eB); + FsrRcasInputHx2(fR,fG,fB); + FsrRcasInputHx2(hR,hG,hB); + // Luma times 2. + AH2 bL=bB*AH2_(0.5)+(bR*AH2_(0.5)+bG); + AH2 dL=dB*AH2_(0.5)+(dR*AH2_(0.5)+dG); + AH2 eL=eB*AH2_(0.5)+(eR*AH2_(0.5)+eG); + AH2 fL=fB*AH2_(0.5)+(fR*AH2_(0.5)+fG); + AH2 hL=hB*AH2_(0.5)+(hR*AH2_(0.5)+hG); + // Noise detection. + AH2 nz=AH2_(0.25)*bL+AH2_(0.25)*dL+AH2_(0.25)*fL+AH2_(0.25)*hL-eL; + nz=ASatH2(abs(nz)*APrxMedRcpH2(AMax3H2(AMax3H2(bL,dL,eL),fL,hL)-AMin3H2(AMin3H2(bL,dL,eL),fL,hL))); + nz=AH2_(-0.5)*nz+AH2_(1.0); + // Min and max of ring. + AH2 mn4R=min(AMin3H2(bR,dR,fR),hR); + AH2 mn4G=min(AMin3H2(bG,dG,fG),hG); + AH2 mn4B=min(AMin3H2(bB,dB,fB),hB); + AH2 mx4R=max(AMax3H2(bR,dR,fR),hR); + AH2 mx4G=max(AMax3H2(bG,dG,fG),hG); + AH2 mx4B=max(AMax3H2(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH2 hitMinR=min(mn4R,eR)*ARcpH2(AH2_(4.0)*mx4R); + AH2 hitMinG=min(mn4G,eG)*ARcpH2(AH2_(4.0)*mx4G); + AH2 hitMinB=min(mn4B,eB)*ARcpH2(AH2_(4.0)*mx4B); + AH2 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH2(AH2_(4.0)*mn4R+peakC.y); + AH2 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH2(AH2_(4.0)*mn4G+peakC.y); + AH2 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH2(AH2_(4.0)*mn4B+peakC.y); + AH2 lobeR=max(-hitMinR,hitMaxR); + AH2 lobeG=max(-hitMinG,hitMaxG); + AH2 lobeB=max(-hitMinB,hitMaxB); + AH2 lobe=max(AH2_(-FSR_RCAS_LIMIT),min(AMax3H2(lobeR,lobeG,lobeB),AH2_(0.0)))*AH2_(AH2_AU1(con.y).x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH2 rcpL=APrxMedRcpH2(AH2_(4.0)*lobe+AH2_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [LFGA] LINEAR FILM GRAIN APPLICATOR +// +//------------------------------------------------------------------------------------------------------------------------------ +// Adding output-resolution film grain after scaling is a good way to mask both rendering and scaling artifacts. +// Suggest using tiled blue noise as film grain input, with peak noise frequency set for a specific look and feel. +// The 'Lfga*()' functions provide a convenient way to introduce grain. +// These functions limit grain based on distance to signal limits. +// This is done so that the grain is temporally energy preserving, and thus won't modify image tonality. +// Grain application should be done in a linear colorspace. +// The grain should be temporally changing, but have a temporal sum per pixel that adds to zero (non-biased). +//------------------------------------------------------------------------------------------------------------------------------ +// Usage, +// FsrLfga*( +// color, // In/out linear colorspace color {0 to 1} ranged. +// grain, // Per pixel grain texture value {-0.5 to 0.5} ranged, input is 3-channel to support colored grain. +// amount); // Amount of grain (0 to 1} ranged. +//------------------------------------------------------------------------------------------------------------------------------ +// Example if grain texture is monochrome: 'FsrLfgaF(color,AF3_(grain),amount)' +//============================================================================================================================== +#if defined(A_GPU) + // Maximum grain is the minimum distance to the signal limit. + void FsrLfgaF(inout AF3 c,AF3 t,AF1 a){c+=(t*AF3_(a))*min(AF3_(1.0)-c,c);} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + // Half precision version (slower). + void FsrLfgaH(inout AH3 c,AH3 t,AH1 a){c+=(t*AH3_(a))*min(AH3_(1.0)-c,c);} +//------------------------------------------------------------------------------------------------------------------------------ + // Packed half precision version (faster). + void FsrLfgaHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 tR,AH2 tG,AH2 tB,AH1 a){ + cR+=(tR*AH2_(a))*min(AH2_(1.0)-cR,cR);cG+=(tG*AH2_(a))*min(AH2_(1.0)-cG,cG);cB+=(tB*AH2_(a))*min(AH2_(1.0)-cB,cB);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [SRTM] SIMPLE REVERSIBLE TONE-MAPPER +// +//------------------------------------------------------------------------------------------------------------------------------ +// This provides a way to take linear HDR color {0 to FP16_MAX} and convert it into a temporary {0 to 1} ranged post-tonemapped linear. +// The tonemapper preserves RGB ratio, which helps maintain HDR color bleed during filtering. +//------------------------------------------------------------------------------------------------------------------------------ +// Reversible tonemapper usage, +// FsrSrtm*(color); // {0 to FP16_MAX} converted to {0 to 1}. +// FsrSrtmInv*(color); // {0 to 1} converted into {0 to 32768, output peak safe for FP16}. +//============================================================================================================================== +#if defined(A_GPU) + void FsrSrtmF(inout AF3 c){c*=AF3_(ARcpF1(AMax3F1(c.r,c.g,c.b)+AF1_(1.0)));} + // The extra max solves the c=1.0 case (which is a /0). + void FsrSrtmInvF(inout AF3 c){c*=AF3_(ARcpF1(max(AF1_(1.0/32768.0),AF1_(1.0)-AMax3F1(c.r,c.g,c.b))));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + void FsrSrtmH(inout AH3 c){c*=AH3_(ARcpH1(AMax3H1(c.r,c.g,c.b)+AH1_(1.0)));} + void FsrSrtmInvH(inout AH3 c){c*=AH3_(ARcpH1(max(AH1_(1.0/32768.0),AH1_(1.0)-AMax3H1(c.r,c.g,c.b))));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrSrtmHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(AMax3H2(cR,cG,cB)+AH2_(1.0));cR*=rcp;cG*=rcp;cB*=rcp;} + void FsrSrtmInvHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(max(AH2_(1.0/32768.0),AH2_(1.0)-AMax3H2(cR,cG,cB)));cR*=rcp;cG*=rcp;cB*=rcp;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [TEPD] TEMPORAL ENERGY PRESERVING DITHER +// +//------------------------------------------------------------------------------------------------------------------------------ +// Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// Gamma 2.0 is used so that the conversion back to linear is just to square the color. +// The conversion comes in 8-bit and 10-bit modes, designed for output to 8-bit UNORM or 10:10:10:2 respectively. +// Given good non-biased temporal blue noise as dither input, +// the output dither will temporally conserve energy. +// This is done by choosing the linear nearest step point instead of perceptual nearest. +// See code below for details. +//------------------------------------------------------------------------------------------------------------------------------ +// DX SPEC RULES FOR FLOAT->UNORM 8-BIT CONVERSION +// =============================================== +// - Output is 'uint(floor(saturate(n)*255.0+0.5))'. +// - Thus rounding is to nearest. +// - NaN gets converted to zero. +// - INF is clamped to {0.0 to 1.0}. +//============================================================================================================================== +#if defined(A_GPU) + // Hand tuned integer position to dither value, with more values than simple checkerboard. + // Only 32-bit has enough precision for this compddation. + // Output is {0 to <1}. + AF1 FsrTepdDitF(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + // The 1.61803 golden ratio. + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + // Number designed to provide a good visual pattern. + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AFractF1(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 8-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC8F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(255.0))*AF3_(1.0/255.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/255.0);b=b*b; + // Ratio of 'a' to 'b' required to produce 'c'. + // APrxLoRcpF1() won't work here (at least for very high dynamic ranges). + // APrxMedRcpF1() is an IADD,FMA,MUL. + AF3 r=(c-b)*APrxMedRcpF3(a-b); + // Use the ratio as a cutoff to choose 'a' or 'b'. + // AGtZeroF1() is a MUL. + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 10-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC10F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(1023.0))*AF3_(1.0/1023.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/1023.0);b=b*b; + AF3 r=(c-b)*APrxMedRcpF3(a-b); + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/1023.0));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + AH1 FsrTepdDitH(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AH1(AFractF1(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(255.0))*AH3_(1.0/255.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/255.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(1023.0))*AH3_(1.0/1023.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/1023.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/1023.0));} +//============================================================================================================================== + // This computes dither for positions 'p' and 'p+{8,0}'. + AH2 FsrTepdDitHx2(AU2 p,AU1 f){ + AF2 x; + x.x=AF1_(p.x+f); + x.y=x.x+AF1_(8.0); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*AF2_(a)+AF2_(y*b); + return AH2(AFractF2(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(255.0))*AH2_(1.0/255.0); + nG=floor(nG*AH2_(255.0))*AH2_(1.0/255.0); + nB=floor(nB*AH2_(255.0))*AH2_(1.0/255.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/255.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/255.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/255.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/255.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/255.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(1023.0))*AH2_(1.0/1023.0); + nG=floor(nG*AH2_(1023.0))*AH2_(1.0/1023.0); + nB=floor(nB*AH2_(1023.0))*AH2_(1.0/1023.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/1023.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/1023.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/1023.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/1023.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/1023.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/1023.0));} +#endif diff --git a/src/video_core/host_shaders/post_process.frag b/src/video_core/host_shaders/post_process.frag index d222d070c..fb0917528 100644 --- a/src/video_core/host_shaders/post_process.frag +++ b/src/video_core/host_shaders/post_process.frag @@ -8,7 +8,7 @@ layout (location = 0) out vec4 color; layout (binding = 0) uniform sampler2D texSampler; -layout(push_constant) uniform settings { +layout (push_constant) uniform settings { float gamma; bool hdr; } pp; diff --git a/src/video_core/host_shaders/source_shader.h.in b/src/video_core/host_shaders/source_shader.h.in index 43bf5b0c5..3472b2837 100644 --- a/src/video_core/host_shaders/source_shader.h.in +++ b/src/video_core/host_shaders/source_shader.h.in @@ -7,8 +7,8 @@ namespace HostShaders { -constexpr std::string_view @CONTENTS_NAME@ = { +constexpr std::string_view @CONTENTS_NAME@ = R"shader_src( @CONTENTS@ -}; +)shader_src"; } // namespace HostShaders diff --git a/src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp b/src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp new file mode 100644 index 000000000..1c54207e0 --- /dev/null +++ b/src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp @@ -0,0 +1,445 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "fsr_pass.h" + +#include "common/assert.h" +#include "video_core/host_shaders/fsr_comp.h" +#include "video_core/renderer_vulkan/vk_platform.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" + +#define A_CPU +#include "core/debug_state.h" +#include "video_core/host_shaders/fsr/ffx_a.h" +#include "video_core/host_shaders/fsr/ffx_fsr1.h" + +typedef u32 uvec4[4]; + +struct FSRConstants { + uvec4 Const0; + uvec4 Const1; + uvec4 Const2; + uvec4 Const3; + uvec4 Sample; +}; + +namespace Vulkan::HostPasses { + +void FsrPass::Create(vk::Device device, VmaAllocator allocator, u32 num_images) { + this->device = device; + this->num_images = num_images; + + sampler = Check<"create upscaling sampler">(device.createSamplerUnique(vk::SamplerCreateInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eNearest, + .addressModeU = vk::SamplerAddressMode::eClampToEdge, + .addressModeV = vk::SamplerAddressMode::eClampToEdge, + .addressModeW = vk::SamplerAddressMode::eClampToEdge, + .maxAnisotropy = 1.0f, + .minLod = -1000.0f, + .maxLod = 1000.0f, + })); + + std::array layoutBindings{{ + { + .binding = 0, + .descriptorType = vk::DescriptorType::eSampledImage, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eCompute, + }, + { + .binding = 1, + .descriptorType = vk::DescriptorType::eStorageImage, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eCompute, + }, + { + .binding = 2, + .descriptorType = vk::DescriptorType::eSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eCompute, + .pImmutableSamplers = &sampler.get(), + }, + }}; + + descriptor_set_layout = + Check<"create fsr descriptor set layout">(device.createDescriptorSetLayoutUnique({ + .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptor, + .bindingCount = layoutBindings.size(), + .pBindings = layoutBindings.data(), + })); + + const vk::PushConstantRange push_constants{ + .stageFlags = vk::ShaderStageFlagBits::eCompute, + .offset = 0, + .size = sizeof(FSRConstants), + }; + + const auto& cs_easu_module = + Compile(HostShaders::FSR_COMP, vk::ShaderStageFlagBits::eCompute, device, + { + "SAMPLE_EASU=1", + }); + ASSERT(cs_easu_module); + SetObjectName(device, cs_easu_module, "fsr.comp [EASU]"); + + const auto& cs_rcas_module = + Compile(HostShaders::FSR_COMP, vk::ShaderStageFlagBits::eCompute, device, + { + "SAMPLE_RCAS=1", + }); + ASSERT(cs_rcas_module); + SetObjectName(device, cs_rcas_module, "fsr.comp [RCAS]"); + + pipeline_layout = Check<"fsp pipeline layout">(device.createPipelineLayoutUnique({ + .setLayoutCount = 1, + .pSetLayouts = &descriptor_set_layout.get(), + .pushConstantRangeCount = 1, + .pPushConstantRanges = &push_constants, + })); + SetObjectName(device, pipeline_layout.get(), "fsr pipeline layout"); + + const vk::ComputePipelineCreateInfo easu_pinfo{ + .stage{ + .stage = vk::ShaderStageFlagBits::eCompute, + .module = cs_easu_module, + .pName = "main", + }, + .layout = pipeline_layout.get(), + }; + easu_pipeline = + Check<"fsp easu compute pipelines">(device.createComputePipelineUnique({}, easu_pinfo)); + SetObjectName(device, easu_pipeline.get(), "fsr easu pipeline"); + + const vk::ComputePipelineCreateInfo rcas_pinfo{ + .stage{ + .stage = vk::ShaderStageFlagBits::eCompute, + .module = cs_rcas_module, + .pName = "main", + }, + .layout = pipeline_layout.get(), + }; + rcas_pipeline = + Check<"fsp rcas compute pipelines">(device.createComputePipelineUnique({}, rcas_pinfo)); + SetObjectName(device, rcas_pipeline.get(), "fsr rcas pipeline"); + + device.destroyShaderModule(cs_easu_module); + device.destroyShaderModule(cs_rcas_module); + + available_imgs.resize(num_images); + for (int i = 0; i < num_images; ++i) { + auto& img = available_imgs[i]; + img.id = i; + img.intermediary_image = VideoCore::UniqueImage(device, allocator); + img.output_image = VideoCore::UniqueImage(device, allocator); + } +} + +vk::ImageView FsrPass::Render(vk::CommandBuffer cmdbuf, vk::ImageView input, + vk::Extent2D input_size, vk::Extent2D output_size, Settings settings, + bool hdr) { + if (!settings.enable) { + DebugState.is_using_fsr = false; + return input; + } + if (input_size.width >= output_size.width && input_size.height >= output_size.height) { + DebugState.is_using_fsr = false; + return input; + } + + DebugState.is_using_fsr = true; + + if (output_size != cur_size) { + ResizeAndInvalidate(output_size.width, output_size.height); + } + auto [width, height] = cur_size; + + auto& img = available_imgs[cur_image]; + if (++cur_image >= available_imgs.size()) { + cur_image = 0; + } + + if (img.dirty) { + CreateImages(img); + } + + static const int thread_group_work_region_dim = 16; + int dispatch_x = (width + (thread_group_work_region_dim - 1)) / thread_group_work_region_dim; + int dispatch_y = (height + (thread_group_work_region_dim - 1)) / thread_group_work_region_dim; + + constexpr vk::ImageSubresourceRange simple_subresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }; + const std::array enter_barrier{ + vk::ImageMemoryBarrier2{ + .srcStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .srcAccessMask = vk::AccessFlagBits2::eShaderRead, + .dstStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .dstAccessMask = vk::AccessFlagBits2::eShaderWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eGeneral, + .image = img.intermediary_image, + .subresourceRange = simple_subresource, + }, + }; + cmdbuf.pipelineBarrier2({ + .imageMemoryBarrierCount = enter_barrier.size(), + .pImageMemoryBarriers = enter_barrier.data(), + }); + + FSRConstants consts{}; + FsrEasuCon(reinterpret_cast(&consts.Const0), reinterpret_cast(&consts.Const1), + reinterpret_cast(&consts.Const2), reinterpret_cast(&consts.Const3), + static_cast(input_size.width), static_cast(input_size.height), + static_cast(input_size.width), static_cast(input_size.height), (AF1)width, + (AF1)height); + consts.Sample[0] = hdr && !settings.use_rcas ? 1 : 0; + + if (settings.use_rcas) { + + { // easu + std::array img_info{{ + { + .imageView = input, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + }, + { + .imageView = img.intermediary_image_view.get(), + .imageLayout = vk::ImageLayout::eGeneral, + }, + { + .sampler = sampler.get(), + }, + }}; + + std::array set_writes{{ + { + .dstBinding = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eSampledImage, + .pImageInfo = &img_info[0], + }, + { + .dstBinding = 1, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageImage, + .pImageInfo = &img_info[1], + }, + { + .dstBinding = 2, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eSampler, + .pImageInfo = &img_info[2], + }, + }}; + + cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, easu_pipeline.get()); + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, pipeline_layout.get(), 0, + set_writes); + cmdbuf.pushConstants(pipeline_layout.get(), vk::ShaderStageFlagBits::eCompute, 0, + sizeof(FSRConstants), &consts); + cmdbuf.dispatch(dispatch_x, dispatch_y, 1); + } + + std::array img_barrier{ + vk::ImageMemoryBarrier2{ + .srcStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .srcAccessMask = vk::AccessFlagBits2::eShaderStorageWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .dstAccessMask = vk::AccessFlagBits2::eShaderRead, + .oldLayout = vk::ImageLayout::eGeneral, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .image = img.intermediary_image, + .subresourceRange = simple_subresource, + }, + vk::ImageMemoryBarrier2{ + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = vk::AccessFlagBits2::eNone, + .dstStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .dstAccessMask = vk::AccessFlagBits2::eShaderStorageWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eGeneral, + .image = img.output_image, + .subresourceRange = simple_subresource, + }, + }; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .imageMemoryBarrierCount = img_barrier.size(), + .pImageMemoryBarriers = img_barrier.data(), + }); + + { // rcas + consts = {}; + FsrRcasCon(reinterpret_cast(&consts.Const0), settings.rcas_attenuation); + consts.Sample[0] = hdr ? 1 : 0; + + std::array img_info{{ + { + .imageView = img.intermediary_image_view.get(), + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + }, + { + .imageView = img.output_image_view.get(), + .imageLayout = vk::ImageLayout::eGeneral, + }, + { + .sampler = sampler.get(), + }, + }}; + + std::array set_writes{{ + { + .dstBinding = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eSampledImage, + .pImageInfo = &img_info[0], + }, + { + .dstBinding = 1, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageImage, + .pImageInfo = &img_info[1], + }, + { + .dstBinding = 2, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eSampler, + .pImageInfo = &img_info[2], + }, + }}; + + cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, rcas_pipeline.get()); + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, pipeline_layout.get(), 0, + set_writes); + cmdbuf.pushConstants(pipeline_layout.get(), vk::ShaderStageFlagBits::eCompute, 0, + sizeof(FSRConstants), &consts); + cmdbuf.dispatch(dispatch_x, dispatch_y, 1); + } + + } else { + // only easu + std::array img_info{{ + { + .imageView = input, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + }, + { + .imageView = img.output_image_view.get(), + .imageLayout = vk::ImageLayout::eGeneral, + }, + { + .sampler = sampler.get(), + }, + }}; + + std::array set_writes{{ + { + .dstBinding = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eSampledImage, + .pImageInfo = &img_info[0], + }, + { + .dstBinding = 1, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageImage, + .pImageInfo = &img_info[1], + }, + { + .dstBinding = 2, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eSampler, + .pImageInfo = &img_info[2], + }, + }}; + + cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, easu_pipeline.get()); + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, pipeline_layout.get(), 0, + set_writes); + cmdbuf.pushConstants(pipeline_layout.get(), vk::ShaderStageFlagBits::eCompute, 0, + sizeof(FSRConstants), &consts); + cmdbuf.dispatch(dispatch_x, dispatch_y, 1); + } + + const std::array return_barrier{ + vk::ImageMemoryBarrier2{ + .srcStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .srcAccessMask = vk::AccessFlagBits2::eShaderStorageWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eShaderRead, + .oldLayout = vk::ImageLayout::eGeneral, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .image = img.output_image, + .subresourceRange = simple_subresource, + }, + }; + cmdbuf.pipelineBarrier2({ + .imageMemoryBarrierCount = return_barrier.size(), + .pImageMemoryBarriers = return_barrier.data(), + }); + + return img.output_image_view.get(); +} + +void FsrPass::ResizeAndInvalidate(u32 width, u32 height) { + this->cur_size = vk::Extent2D{ + .width = width, + .height = height, + }; + for (int i = 0; i < num_images; ++i) { + available_imgs[i].dirty = true; + } +} + +void FsrPass::CreateImages(Img& img) const { + img.dirty = false; + + vk::ImageCreateInfo image_create_info{ + .imageType = vk::ImageType::e2D, + .format = vk::Format::eR16G16B16A16Sfloat, + .extent{ + .width = cur_size.width, + .height = cur_size.height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + // .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eStorage, + .initialLayout = vk::ImageLayout::eUndefined, + }; + img.intermediary_image.Create(image_create_info); + SetObjectName(device, static_cast(img.intermediary_image), + "FSR Intermediary Image #{}", img.id); + image_create_info.usage = vk::ImageUsageFlagBits::eTransferSrc | + vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eStorage | + vk::ImageUsageFlagBits::eColorAttachment; + img.output_image.Create(image_create_info); + SetObjectName(device, static_cast(img.output_image), "FSR Output Image #{}", img.id); + + vk::ImageViewCreateInfo image_view_create_info{ + .image = img.intermediary_image, + .viewType = vk::ImageViewType::e2D, + .format = vk::Format::eR16G16B16A16Sfloat, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }; + img.intermediary_image_view = Check<"create fsr intermediary image view">( + device.createImageViewUnique(image_view_create_info)); + SetObjectName(device, img.intermediary_image_view.get(), "FSR Intermediary ImageView #{}", + img.id); + + image_view_create_info.image = img.output_image; + img.output_image_view = + Check<"create fsr output image view">(device.createImageViewUnique(image_view_create_info)); + SetObjectName(device, img.output_image_view.get(), "FSR Output ImageView #{}", img.id); +} + +} // namespace Vulkan::HostPasses \ No newline at end of file diff --git a/src/video_core/renderer_vulkan/host_passes/fsr_pass.h b/src/video_core/renderer_vulkan/host_passes/fsr_pass.h new file mode 100644 index 000000000..3d48d85be --- /dev/null +++ b/src/video_core/renderer_vulkan/host_passes/fsr_pass.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" +#include "video_core/texture_cache/image.h" + +namespace Vulkan::HostPasses { + +class FsrPass { +public: + struct Settings { + bool enable{true}; + bool use_rcas{true}; + float rcas_attenuation{0.25f}; + }; + + void Create(vk::Device device, VmaAllocator allocator, u32 num_images); + + vk::ImageView Render(vk::CommandBuffer cmdbuf, vk::ImageView input, vk::Extent2D input_size, + vk::Extent2D output_size, Settings settings, bool hdr); + +private: + struct Img { + u8 id{}; + bool dirty{true}; + + VideoCore::UniqueImage intermediary_image; + vk::UniqueImageView intermediary_image_view; + + VideoCore::UniqueImage output_image; + vk::UniqueImageView output_image_view; + }; + + void ResizeAndInvalidate(u32 width, u32 height); + void CreateImages(Img& img) const; + + vk::Device device{}; + u32 num_images{}; + + vk::UniqueDescriptorSetLayout descriptor_set_layout{}; + vk::UniqueDescriptorSet easu_descriptor_set{}; + vk::UniqueDescriptorSet rcas_descriptor_set{}; + vk::UniqueSampler sampler{}; + vk::UniquePipelineLayout pipeline_layout{}; + vk::UniquePipeline easu_pipeline{}; + vk::UniquePipeline rcas_pipeline{}; + + vk::Extent2D cur_size{}; + u32 cur_image{}; + std::vector available_imgs; +}; + +} // namespace Vulkan::HostPasses diff --git a/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp b/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp new file mode 100644 index 000000000..c854e124f --- /dev/null +++ b/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp @@ -0,0 +1,255 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "pp_pass.h" + +#include "common/assert.h" +#include "video_core/host_shaders/fs_tri_vert.h" +#include "video_core/host_shaders/post_process_frag.h" +#include "video_core/renderer_vulkan/vk_platform.h" +#include "video_core/renderer_vulkan/vk_presenter.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" + +#include + +namespace Vulkan::HostPasses { + +void PostProcessingPass::Create(vk::Device device) { + static const std::array pp_shaders{ + HostShaders::FS_TRI_VERT, + HostShaders::POST_PROCESS_FRAG, + }; + + boost::container::static_vector bindings{ + { + .binding = 0, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + }, + }; + + const vk::DescriptorSetLayoutCreateInfo desc_layout_ci{ + .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, + .bindingCount = static_cast(bindings.size()), + .pBindings = bindings.data(), + }; + + desc_set_layout = Check<"create pp descriptor set layout">( + device.createDescriptorSetLayoutUnique(desc_layout_ci)); + + const vk::PushConstantRange push_constants{ + .stageFlags = vk::ShaderStageFlagBits::eFragment, + .offset = 0, + .size = sizeof(Settings), + }; + + const auto& vs_module = Compile(pp_shaders[0], vk::ShaderStageFlagBits::eVertex, device); + ASSERT(vs_module); + SetObjectName(device, vs_module, "fs_tri.vert"); + + const auto& fs_module = Compile(pp_shaders[1], vk::ShaderStageFlagBits::eFragment, device); + ASSERT(fs_module); + SetObjectName(device, fs_module, "post_process.frag"); + + const std::array shaders_ci{ + vk::PipelineShaderStageCreateInfo{ + .stage = vk::ShaderStageFlagBits::eVertex, + .module = vs_module, + .pName = "main", + }, + vk::PipelineShaderStageCreateInfo{ + .stage = vk::ShaderStageFlagBits::eFragment, + .module = fs_module, + .pName = "main", + }, + }; + + const vk::PipelineLayoutCreateInfo layout_info{ + .setLayoutCount = 1U, + .pSetLayouts = &*desc_set_layout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &push_constants, + }; + + pipeline_layout = + Check<"create pp pipeline layout">(device.createPipelineLayoutUnique(layout_info)); + + const std::array pp_color_formats{ + vk::Format::eB8G8R8A8Unorm, // swapchain.GetSurfaceFormat().format, + }; + const vk::PipelineRenderingCreateInfoKHR pipeline_rendering_ci{ + .colorAttachmentCount = pp_color_formats.size(), + .pColorAttachmentFormats = pp_color_formats.data(), + }; + + const vk::PipelineVertexInputStateCreateInfo vertex_input_info{ + .vertexBindingDescriptionCount = 0u, + .vertexAttributeDescriptionCount = 0u, + }; + + const vk::PipelineInputAssemblyStateCreateInfo input_assembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + }; + + const vk::Viewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = 1.0f, + .height = 1.0f, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + + const vk::Rect2D scissor = { + .offset = {0, 0}, + .extent = {1, 1}, + }; + + const vk::PipelineViewportStateCreateInfo viewport_info{ + .viewportCount = 1, + .pViewports = &viewport, + .scissorCount = 1, + .pScissors = &scissor, + }; + + const vk::PipelineRasterizationStateCreateInfo raster_state{ + .depthClampEnable = false, + .rasterizerDiscardEnable = false, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eClockwise, + .depthBiasEnable = false, + .lineWidth = 1.0f, + }; + + const vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + }; + + const std::array attachments{ + vk::PipelineColorBlendAttachmentState{ + .blendEnable = false, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }, + }; + + const vk::PipelineColorBlendStateCreateInfo color_blending{ + .logicOpEnable = false, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = attachments.size(), + .pAttachments = attachments.data(), + .blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f}, + }; + + const std::array dynamic_states{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + }; + + const vk::PipelineDynamicStateCreateInfo dynamic_info{ + .dynamicStateCount = dynamic_states.size(), + .pDynamicStates = dynamic_states.data(), + }; + + const vk::GraphicsPipelineCreateInfo pipeline_info{ + .pNext = &pipeline_rendering_ci, + .stageCount = shaders_ci.size(), + .pStages = shaders_ci.data(), + .pVertexInputState = &vertex_input_info, + .pInputAssemblyState = &input_assembly, + .pViewportState = &viewport_info, + .pRasterizationState = &raster_state, + .pMultisampleState = &multisampling, + .pColorBlendState = &color_blending, + .pDynamicState = &dynamic_info, + .layout = *pipeline_layout, + }; + + pipeline = Check<"create post process pipeline">(device.createGraphicsPipelineUnique( + /*pipeline_cache*/ {}, pipeline_info)); + + // Once pipeline is compiled, we don't need the shader module anymore + device.destroyShaderModule(vs_module); + device.destroyShaderModule(fs_module); + + // Create sampler resource + const vk::SamplerCreateInfo sampler_ci{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eNearest, + .addressModeU = vk::SamplerAddressMode::eClampToEdge, + .addressModeV = vk::SamplerAddressMode::eClampToEdge, + }; + sampler = Check<"create pp sampler">(device.createSamplerUnique(sampler_ci)); +} + +void PostProcessingPass::Render(vk::CommandBuffer cmdbuf, vk::ImageView input, + vk::Extent2D input_size, Frame& frame, Settings settings) { + const std::array attachments{{ + { + .imageView = frame.image_view, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + }, + }}; + const vk::RenderingInfo rendering_info{ + .renderArea{ + .extent{ + .width = frame.width, + .height = frame.height, + }, + }, + .layerCount = 1, + .colorAttachmentCount = attachments.size(), + .pColorAttachments = attachments.data(), + }; + + vk::DescriptorImageInfo image_info{ + .sampler = *sampler, + .imageView = input, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + }; + + const std::array set_writes{ + vk::WriteDescriptorSet{ + .dstSet = VK_NULL_HANDLE, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &image_info, + }, + }; + + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, *pipeline); + + const std::array viewports = { + vk::Viewport{ + .width = static_cast(frame.width), + .height = static_cast(frame.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }, + }; + + cmdbuf.setViewport(0, viewports); + cmdbuf.setScissor(0, vk::Rect2D{ + .extent{ + .width = frame.width, + .height = frame.height, + }, + }); + + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, set_writes); + cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eFragment, 0, sizeof(Settings), + &settings); + + cmdbuf.beginRendering(rendering_info); + cmdbuf.draw(3, 1, 0, 0); + cmdbuf.endRendering(); +} + +} // namespace Vulkan::HostPasses \ No newline at end of file diff --git a/src/video_core/renderer_vulkan/host_passes/pp_pass.h b/src/video_core/renderer_vulkan/host_passes/pp_pass.h new file mode 100644 index 000000000..6127bb5c1 --- /dev/null +++ b/src/video_core/renderer_vulkan/host_passes/pp_pass.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { +class Frame; +} + +namespace Vulkan::HostPasses { + +class PostProcessingPass { +public: + struct Settings { + float gamma = 1.0f; + u32 hdr = 0; + }; + + void Create(vk::Device device); + + void Render(vk::CommandBuffer cmdbuf, vk::ImageView input, vk::Extent2D input_size, + Frame& output, Settings settings); + +private: + vk::UniquePipeline pipeline{}; + vk::UniquePipelineLayout pipeline_layout{}; + vk::UniqueDescriptorSetLayout desc_set_layout{}; + vk::UniqueSampler sampler{}; +}; + +} // namespace Vulkan::HostPasses diff --git a/src/video_core/renderer_vulkan/vk_platform.h b/src/video_core/renderer_vulkan/vk_platform.h index 0f70312ed..6a6ebeb15 100644 --- a/src/video_core/renderer_vulkan/vk_platform.h +++ b/src/video_core/renderer_vulkan/vk_platform.h @@ -5,7 +5,9 @@ #include +#include "common/assert.h" #include "common/logging/log.h" +#include "common/string_literal.h" #include "common/types.h" #include "video_core/renderer_vulkan/vk_common.h" @@ -48,4 +50,25 @@ void SetObjectName(vk::Device device, const HandleType& handle, const char* form SetObjectName(device, handle, debug_name); } +template +static void Check(vk::Result r) { + if constexpr (msg.len <= 1) { + ASSERT_MSG(r == vk::Result::eSuccess, "vk::Result={}", vk::to_string(r)); + } else { + ASSERT_MSG(r == vk::Result::eSuccess, "Failed to {}: vk::Result={}", msg.value, + vk::to_string(r)); + } +} + +template +static T Check(vk::ResultValue r) { + if constexpr (msg.len <= 1) { + ASSERT_MSG(r.result == vk::Result::eSuccess, "vk::Result={}", vk::to_string(r.result)); + } else { + ASSERT_MSG(r.result == vk::Result::eSuccess, "Failed to {}: vk::Result={}", msg.value, + vk::to_string(r.result)); + } + return std::move(r.value); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 04d0e7ac9..dc7fa8860 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -10,13 +10,13 @@ #include "core/libraries/system/systemservice.h" #include "imgui/renderer/imgui_core.h" #include "sdl_window.h" +#include "video_core/renderer_vulkan/vk_platform.h" #include "video_core/renderer_vulkan/vk_presenter.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" #include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/texture_cache/image.h" #include "video_core/host_shaders/fs_tri_vert.h" -#include "video_core/host_shaders/post_process_frag.h" #include @@ -106,191 +106,6 @@ static vk::Rect2D FitImage(s32 frame_width, s32 frame_height, s32 swapchain_widt dst_rect.offset.x, dst_rect.offset.y); } -void Presenter::CreatePostProcessPipeline() { - static const std::array pp_shaders{ - HostShaders::FS_TRI_VERT, - HostShaders::POST_PROCESS_FRAG, - }; - - boost::container::static_vector bindings{ - { - .binding = 0, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eFragment, - }, - }; - - const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = { - .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, - .bindingCount = static_cast(bindings.size()), - .pBindings = bindings.data(), - }; - auto desc_layout_result = instance.GetDevice().createDescriptorSetLayoutUnique(desc_layout_ci); - ASSERT_MSG(desc_layout_result.result == vk::Result::eSuccess, - "Failed to create descriptor set layout: {}", - vk::to_string(desc_layout_result.result)); - pp_desc_set_layout = std::move(desc_layout_result.value); - - const vk::PushConstantRange push_constants = { - .stageFlags = vk::ShaderStageFlagBits::eFragment, - .offset = 0, - .size = sizeof(PostProcessSettings), - }; - - const auto& vs_module = - Vulkan::Compile(pp_shaders[0], vk::ShaderStageFlagBits::eVertex, instance.GetDevice()); - ASSERT(vs_module); - Vulkan::SetObjectName(instance.GetDevice(), vs_module, "fs_tri.vert"); - - const auto& fs_module = - Vulkan::Compile(pp_shaders[1], vk::ShaderStageFlagBits::eFragment, instance.GetDevice()); - ASSERT(fs_module); - Vulkan::SetObjectName(instance.GetDevice(), fs_module, "post_process.frag"); - - const std::array shaders_ci{ - vk::PipelineShaderStageCreateInfo{ - .stage = vk::ShaderStageFlagBits::eVertex, - .module = vs_module, - .pName = "main", - }, - vk::PipelineShaderStageCreateInfo{ - .stage = vk::ShaderStageFlagBits::eFragment, - .module = fs_module, - .pName = "main", - }, - }; - - const vk::DescriptorSetLayout set_layout = *pp_desc_set_layout; - const vk::PipelineLayoutCreateInfo layout_info = { - .setLayoutCount = 1U, - .pSetLayouts = &set_layout, - .pushConstantRangeCount = 1, - .pPushConstantRanges = &push_constants, - }; - auto [layout_result, layout] = instance.GetDevice().createPipelineLayoutUnique(layout_info); - ASSERT_MSG(layout_result == vk::Result::eSuccess, "Failed to create pipeline layout: {}", - vk::to_string(layout_result)); - pp_pipeline_layout = std::move(layout); - - const std::array pp_color_formats{ - vk::Format::eB8G8R8A8Unorm, // swapchain.GetSurfaceFormat().format, - }; - const vk::PipelineRenderingCreateInfoKHR pipeline_rendering_ci = { - .colorAttachmentCount = 1u, - .pColorAttachmentFormats = pp_color_formats.data(), - }; - - const vk::PipelineVertexInputStateCreateInfo vertex_input_info = { - .vertexBindingDescriptionCount = 0u, - .vertexAttributeDescriptionCount = 0u, - }; - - const vk::PipelineInputAssemblyStateCreateInfo input_assembly = { - .topology = vk::PrimitiveTopology::eTriangleList, - }; - - const vk::Viewport viewport = { - .x = 0.0f, - .y = 0.0f, - .width = 1.0f, - .height = 1.0f, - .minDepth = 0.0f, - .maxDepth = 1.0f, - }; - - const vk::Rect2D scissor = { - .offset = {0, 0}, - .extent = {1, 1}, - }; - - const vk::PipelineViewportStateCreateInfo viewport_info = { - .viewportCount = 1, - .pViewports = &viewport, - .scissorCount = 1, - .pScissors = &scissor, - }; - - const vk::PipelineRasterizationStateCreateInfo raster_state = { - .depthClampEnable = false, - .rasterizerDiscardEnable = false, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, - .depthBiasEnable = false, - .lineWidth = 1.0f, - }; - - const vk::PipelineMultisampleStateCreateInfo multisampling = { - .rasterizationSamples = vk::SampleCountFlagBits::e1, - }; - - const std::array attachments{ - vk::PipelineColorBlendAttachmentState{ - .blendEnable = false, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | - vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, - }, - }; - - const vk::PipelineColorBlendStateCreateInfo color_blending = { - .logicOpEnable = false, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = attachments.size(), - .pAttachments = attachments.data(), - .blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f}, - }; - - const std::array dynamic_states = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor, - }; - - const vk::PipelineDynamicStateCreateInfo dynamic_info = { - .dynamicStateCount = static_cast(dynamic_states.size()), - .pDynamicStates = dynamic_states.data(), - }; - - const vk::GraphicsPipelineCreateInfo pipeline_info = { - .pNext = &pipeline_rendering_ci, - .stageCount = static_cast(shaders_ci.size()), - .pStages = shaders_ci.data(), - .pVertexInputState = &vertex_input_info, - .pInputAssemblyState = &input_assembly, - .pViewportState = &viewport_info, - .pRasterizationState = &raster_state, - .pMultisampleState = &multisampling, - .pColorBlendState = &color_blending, - .pDynamicState = &dynamic_info, - .layout = *pp_pipeline_layout, - }; - - auto result = instance.GetDevice().createGraphicsPipelineUnique( - /*pipeline_cache*/ {}, pipeline_info); - if (result.result == vk::Result::eSuccess) { - pp_pipeline = std::move(result.value); - } else { - UNREACHABLE_MSG("Post process pipeline creation failed!"); - } - - // Once pipeline is compiled, we don't need the shader module anymore - instance.GetDevice().destroyShaderModule(vs_module); - instance.GetDevice().destroyShaderModule(fs_module); - - // Create sampler resource - const vk::SamplerCreateInfo sampler_ci = { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eNearest, - .addressModeU = vk::SamplerAddressMode::eClampToEdge, - .addressModeV = vk::SamplerAddressMode::eClampToEdge, - }; - auto [sampler_result, smplr] = instance.GetDevice().createSamplerUnique(sampler_ci); - ASSERT_MSG(sampler_result == vk::Result::eSuccess, "Failed to create sampler: {}", - vk::to_string(sampler_result)); - pp_sampler = std::move(smplr); -} - Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_) : window{window_}, liverpool{liverpool_}, instance{window, Config::getGpuId(), Config::vkValidationEnabled(), @@ -306,15 +121,15 @@ Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_ present_frames.resize(num_images); for (u32 i = 0; i < num_images; i++) { Frame& frame = present_frames[i]; - auto [fence_result, fence] = - device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}); - ASSERT_MSG(fence_result == vk::Result::eSuccess, "Failed to create present done fence: {}", - vk::to_string(fence_result)); + frame.id = i; + auto fence = Check<"create present done fence">( + device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled})); frame.present_done = fence; free_queue.push(&frame); } - CreatePostProcessPipeline(); + fsr_pass.Create(device, instance.GetAllocator(), num_images); + pp_pass.Create(device); ImGui::Layer::AddLayer(Common::Singleton::Instance()); } @@ -376,6 +191,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) { UNREACHABLE(); } frame->image = vk::Image{unsafe_image}; + SetObjectName(device, frame->image, "Frame image #{}", frame->id); const vk::ImageViewCreateInfo view_info = { .image = frame->image, @@ -389,9 +205,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) { .layerCount = 1, }, }; - auto [view_result, view] = device.createImageView(view_info); - ASSERT_MSG(view_result == vk::Result::eSuccess, "Failed to create frame image view: {}", - vk::to_string(view_result)); + auto view = Check<"create frame image view">(device.createImageView(view_info)); frame->image_view = view; frame->width = width; frame->height = height; @@ -560,13 +374,9 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) // Request a free presentation frame. Frame* frame = GetRenderFrame(); - if (image_id != VideoCore::NULL_IMAGE_ID) { - const auto& image = texture_cache.GetImage(image_id); - const auto extent = image.info.size; - if (frame->width != extent.width || frame->height != extent.height || - frame->is_hdr != swapchain.GetHDR()) { - RecreateFrame(frame, extent.width, extent.height); - } + if (frame->width != expected_frame_width || frame->height != expected_frame_height || + frame->is_hdr != swapchain.GetHDR()) { + RecreateFrame(frame, expected_frame_width, expected_frame_height); } // EOP flips are triggered from GPU thread so use the drawing scheduler to record @@ -575,7 +385,9 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) auto& scheduler = is_eop ? draw_scheduler : flip_scheduler; scheduler.EndRendering(); const auto cmdbuf = scheduler.CommandBuffer(); - if (Config::getVkHostMarkersEnabled()) { + + bool vk_host_markers_enabled = Config::getVkHostMarkersEnabled(); + if (vk_host_markers_enabled) { const auto label = fmt::format("PrepareFrameInternal:{}", image_id.index); cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ .pLabelName = label.c_str(), @@ -605,33 +417,12 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) .pImageMemoryBarriers = &pre_barrier, }); - const std::array attachments = {vk::RenderingAttachmentInfo{ - .imageView = frame->image_view, - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - }}; - const vk::RenderingInfo rendering_info{ - .renderArea = - vk::Rect2D{ - .offset = {0, 0}, - .extent = {frame->width, frame->height}, - }, - .layerCount = 1, - .colorAttachmentCount = attachments.size(), - .pColorAttachments = attachments.data(), - }; - if (image_id != VideoCore::NULL_IMAGE_ID) { auto& image = texture_cache.GetImage(image_id); + vk::Extent2D image_size = {image.info.size.width, image.info.size.height}; image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}, cmdbuf); - static vk::DescriptorImageInfo image_info{ - .sampler = *pp_sampler, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, - }; - VideoCore::ImageViewInfo info{}; info.format = image.info.pixel_format; // Exclude alpha from output frame to avoid blending with UI. @@ -641,52 +432,53 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) .b = vk::ComponentSwizzle::eIdentity, .a = vk::ComponentSwizzle::eOne, }; + vk::ImageView imageView; if (auto view = image.FindView(info)) { - image_info.imageView = *texture_cache.GetImageView(view).image_view; + imageView = *texture_cache.GetImageView(view).image_view; } else { - image_info.imageView = *texture_cache.RegisterImageView(image_id, info).image_view; + imageView = *texture_cache.RegisterImageView(image_id, info).image_view; } - static const std::array set_writes{ - vk::WriteDescriptorSet{ - .dstSet = VK_NULL_HANDLE, - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &image_info, - }, - }; + if (vk_host_markers_enabled) { + cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = "Host/FSR", + }); + } - cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, *pp_pipeline); + imageView = fsr_pass.Render(cmdbuf, imageView, image_size, {frame->width, frame->height}, + fsr_settings, frame->is_hdr); - const auto& dst_rect = - FitImage(image.info.size.width, image.info.size.height, frame->width, frame->height); + if (vk_host_markers_enabled) { + cmdbuf.endDebugUtilsLabelEXT(); + cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = "Host/Post processing", + }); + } + pp_pass.Render(cmdbuf, imageView, image_size, *frame, pp_settings); + if (vk_host_markers_enabled) { + cmdbuf.endDebugUtilsLabelEXT(); + } - const std::array viewports = { - vk::Viewport{ - .x = 1.0f * dst_rect.offset.x, - .y = 1.0f * dst_rect.offset.y, - .width = 1.0f * dst_rect.extent.width, - .height = 1.0f * dst_rect.extent.height, - .minDepth = 0.0f, - .maxDepth = 1.0f, - }, - }; - - cmdbuf.setViewport(0, viewports); - cmdbuf.setScissor(0, {dst_rect}); - - cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pp_pipeline_layout, 0, - set_writes); - cmdbuf.pushConstants(*pp_pipeline_layout, vk::ShaderStageFlagBits::eFragment, 0, - sizeof(PostProcessSettings), &pp_settings); - - cmdbuf.beginRendering(rendering_info); - cmdbuf.draw(3, 1, 0, 0); - cmdbuf.endRendering(); + DebugState.game_resolution = {image_size.width, image_size.height}; + DebugState.output_resolution = {frame->width, frame->height}; } else { // Fix display of garbage images on startup on some drivers + const std::array attachments = {{ + { + .imageView = frame->image_view, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + }, + }}; + const vk::RenderingInfo rendering_info{ + .renderArea{ + .extent{frame->width, frame->height}, + }, + .layerCount = 1, + .colorAttachmentCount = attachments.size(), + .pColorAttachments = attachments.data(), + }; cmdbuf.beginRendering(rendering_info); cmdbuf.endRendering(); } @@ -706,7 +498,7 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) .pImageMemoryBarriers = &post_barrier, }); - if (Config::getVkHostMarkersEnabled()) { + if (vk_host_markers_enabled) { cmdbuf.endDebugUtilsLabelEXT(); } @@ -836,6 +628,7 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) { ImVec2 contentArea = ImGui::GetContentRegionAvail(); const vk::Rect2D imgRect = FitImage(frame->width, frame->height, (s32)contentArea.x, (s32)contentArea.y); + SetExpectedGameSize((s32)contentArea.x, (s32)contentArea.y); ImGui::SetCursorPos(ImGui::GetCursorStartPos() + ImVec2{ (float)imgRect.offset.x, (float)imgRect.offset.y, @@ -914,12 +707,20 @@ Frame* Presenter::GetRenderFrame() { } } - // Initialize default frame image - if (frame->width == 0 || frame->height == 0 || frame->is_hdr != swapchain.GetHDR()) { - RecreateFrame(frame, Config::getScreenWidth(), Config::getScreenHeight()); - } - return frame; } +void Presenter::SetExpectedGameSize(s32 width, s32 height) { + constexpr float expectedRatio = 1920.0 / 1080.0f; + const float ratio = (float)width / (float)height; + + expected_frame_height = height; + expected_frame_width = width; + if (ratio > expectedRatio) { + expected_frame_width = static_cast(height * expectedRatio); + } else { + expected_frame_height = static_cast(width / expectedRatio); + } +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_presenter.h b/src/video_core/renderer_vulkan/vk_presenter.h index 2bfe6e66c..892112397 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.h +++ b/src/video_core/renderer_vulkan/vk_presenter.h @@ -7,6 +7,8 @@ #include "imgui/imgui_config.h" #include "video_core/amdgpu/liverpool.h" +#include "video_core/renderer_vulkan/host_passes/fsr_pass.h" +#include "video_core/renderer_vulkan/host_passes/pp_pass.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_swapchain.h" @@ -32,6 +34,7 @@ struct Frame { vk::Semaphore ready_semaphore; u64 ready_tick; bool is_hdr{false}; + u8 id{}; ImTextureID imgui_texture; }; @@ -45,17 +48,16 @@ enum SchedulerType { class Rasterizer; class Presenter { - struct PostProcessSettings { - float gamma = 1.0f; - u32 hdr = 0; - }; - public: Presenter(Frontend::WindowSDL& window, AmdGpu::Liverpool* liverpool); ~Presenter(); - float& GetGammaRef() { - return pp_settings.gamma; + HostPasses::PostProcessingPass::Settings& GetPPSettingsRef() { + return pp_settings; + } + + HostPasses::FsrPass::Settings& GetFsrSettingsRef() { + return fsr_settings; } Frontend::WindowSDL& GetWindow() const { @@ -117,16 +119,19 @@ public: } private: - void CreatePostProcessPipeline(); Frame* PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop = true); Frame* GetRenderFrame(); + void SetExpectedGameSize(s32 width, s32 height); + private: - PostProcessSettings pp_settings{}; - vk::UniquePipeline pp_pipeline{}; - vk::UniquePipelineLayout pp_pipeline_layout{}; - vk::UniqueDescriptorSetLayout pp_desc_set_layout{}; - vk::UniqueSampler pp_sampler{}; + u32 expected_frame_width{1920}; + u32 expected_frame_height{1080}; + + HostPasses::FsrPass fsr_pass; + HostPasses::FsrPass::Settings fsr_settings{}; + HostPasses::PostProcessingPass::Settings pp_settings{}; + HostPasses::PostProcessingPass pp_pass; Frontend::WindowSDL& window; AmdGpu::Liverpool* liverpool; Instance instance; diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp index 08703c3de..1eb9b27c6 100644 --- a/src/video_core/renderer_vulkan/vk_shader_util.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp @@ -159,7 +159,8 @@ bool InitializeCompiler() { } } // Anonymous namespace -vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device) { +vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device, + std::vector defines) { if (!InitializeCompiler()) { return {}; } @@ -178,12 +179,46 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v glslang::EShTargetLanguageVersion::EShTargetSpv_1_3); shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1); + std::string preambleString; + std::vector processes; + + for (auto& def : defines) { + processes.push_back("define-macro "); + processes.back().append(def); + + preambleString.append("#define "); + if (const size_t equal = def.find_first_of("="); equal != def.npos) { + def[equal] = ' '; + } + preambleString.append(def); + preambleString.append("\n"); + } + + shader->setPreamble(preambleString.c_str()); + shader->addProcesses(processes); + glslang::TShader::ForbidIncluder includer; + + std::string preprocessedStr; + if (!shader->preprocess(&DefaultTBuiltInResource, default_version, profile, false, true, + messages, &preprocessedStr, includer)) [[unlikely]] { + LOG_ERROR(Render_Vulkan, + "Shader preprocess error\n" + "Shader Info Log:\n" + "{}\n{}", + shader->getInfoLog(), shader->getInfoDebugLog()); + LOG_ERROR(Render_Vulkan, "Shader Source:\n{}", code); + return {}; + } + if (!shader->parse(&DefaultTBuiltInResource, default_version, profile, false, true, messages, includer)) [[unlikely]] { - LOG_INFO(Render_Vulkan, "Shader Info Log:\n{}\n{}", shader->getInfoLog(), - shader->getInfoDebugLog()); - LOG_INFO(Render_Vulkan, "Shader Source:\n{}", code); + LOG_ERROR(Render_Vulkan, + "Shader parse error\n" + "Shader Info Log:\n" + "{}\n{}", + shader->getInfoLog(), shader->getInfoDebugLog()); + LOG_ERROR(Render_Vulkan, "Shader Source:\n{}", code); return {}; } @@ -191,8 +226,11 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v auto program = std::make_unique(); program->addShader(shader.get()); if (!program->link(messages)) { - LOG_INFO(Render_Vulkan, "Program Info Log:\n{}\n{}", program->getInfoLog(), - program->getInfoDebugLog()); + LOG_ERROR(Render_Vulkan, + "Shader link error\n" + "Program Info Log:\n" + "{}\n{}", + program->getInfoLog(), program->getInfoDebugLog()); return {}; } diff --git a/src/video_core/renderer_vulkan/vk_shader_util.h b/src/video_core/renderer_vulkan/vk_shader_util.h index 3a86acf2b..14b929782 100644 --- a/src/video_core/renderer_vulkan/vk_shader_util.h +++ b/src/video_core/renderer_vulkan/vk_shader_util.h @@ -16,7 +16,8 @@ namespace Vulkan { * @param stage The pipeline stage the shader will be used in. * @param device The vulkan device handle. */ -vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device); +vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device, + std::vector defines = {}); /** * @brief Creates a vulkan shader module from SPIR-V bytecode. diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 3c85c451c..522e6fd5b 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -113,6 +113,8 @@ static vk::FormatFeatureFlags2 FormatFeatureFlags(const vk::ImageUsageFlags usag return feature_flags; } +UniqueImage::UniqueImage() {} + UniqueImage::UniqueImage(vk::Device device_, VmaAllocator allocator_) : device{device_}, allocator{allocator_} {} @@ -123,6 +125,9 @@ UniqueImage::~UniqueImage() { } void UniqueImage::Create(const vk::ImageCreateInfo& image_ci) { + if (image) { + vmaDestroyImage(allocator, image, allocation); + } const VmaAllocationCreateInfo alloc_info = { .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, diff --git a/src/video_core/texture_cache/image.h b/src/video_core/texture_cache/image.h index 66d65ceec..404e25e88 100644 --- a/src/video_core/texture_cache/image.h +++ b/src/video_core/texture_cache/image.h @@ -35,6 +35,7 @@ enum ImageFlagBits : u32 { DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) struct UniqueImage { + explicit UniqueImage(); explicit UniqueImage(vk::Device device, VmaAllocator allocator); ~UniqueImage(); From 4d0c03fd4af6c498d03e185295c6b82a92afa5e4 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 12 Mar 2025 13:36:01 -0500 Subject: [PATCH 042/194] Properly implement sceVideoOutGetBufferLabelAddress (#2642) * Export sceVideoOutGetBufferLabelAddress It's an exported function, used by red_prig's BLACKSQUAR2 homebrew sample. This also fixes the function's return type accordingly. * More sceVideoOutGetBufferLabelAddress fixups Library decomp shows a hardcoded return 16 on success. Not sure why it does that, but it never hurts to be accurate. Also needs to have an openOrbis-specific export to get it to work with the homebrew sample I'm testing. * Final fixups Removed the port assert and added asserts in libSceGnmDriver for when sceVideoOutGetBufferLabelAddress calls fail. --- src/core/libraries/gnmdriver/gnmdriver.cpp | 6 ++++-- src/core/libraries/videoout/video_out.cpp | 14 ++++++++++++-- src/core/libraries/videoout/video_out.h | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index e2e865def..e8560b2b8 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -1087,7 +1087,8 @@ s32 PS4_SYSV_ABI sceGnmInsertWaitFlipDone(u32* cmdbuf, u32 size, s32 vo_handle, } uintptr_t label_addr{}; - VideoOut::sceVideoOutGetBufferLabelAddress(vo_handle, &label_addr); + ASSERT_MSG(VideoOut::sceVideoOutGetBufferLabelAddress(vo_handle, &label_addr) == 16, + "sceVideoOutGetBufferLabelAddress call failed"); auto* wait_reg_mem = reinterpret_cast(cmdbuf); wait_reg_mem->header = PM4Type3Header{PM4ItOpcode::WaitRegMem, 5}; @@ -2041,7 +2042,8 @@ static inline s32 PatchFlipRequest(u32* cmdbuf, u32 size, u32 vo_handle, u32 buf } uintptr_t label_addr{}; - VideoOut::sceVideoOutGetBufferLabelAddress(vo_handle, &label_addr); + ASSERT_MSG(VideoOut::sceVideoOutGetBufferLabelAddress(vo_handle, &label_addr) == 16, + "sceVideoOutGetBufferLabelAddress call failed"); // Write event to lock the VO surface auto* write_lock = reinterpret_cast(cmdbuf); diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index 4d6972d14..219d0886b 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -295,10 +295,16 @@ s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) { return driver->UnregisterBuffers(port, attributeIndex); } -void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr) { +s32 PS4_SYSV_ABI sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr) { + if (label_addr == nullptr) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; + } auto* port = driver->GetPort(handle); - ASSERT(port); + if (!port) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; + } *label_addr = reinterpret_cast(port->buffer_labels.data()); + return 16; } s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk) { @@ -430,6 +436,8 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceVideoOutIsFlipPending); LIB_FUNCTION("N5KDtkIjjJ4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutUnregisterBuffers); + LIB_FUNCTION("OcQybQejHEY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutGetBufferLabelAddress); LIB_FUNCTION("uquVH4-Du78", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutClose); LIB_FUNCTION("1FZBKy8HeNU", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutGetVblankStatus); @@ -460,6 +468,8 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceVideoOutSetBufferAttribute); LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutRegisterBuffers); + LIB_FUNCTION("OcQybQejHEY", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, + sceVideoOutGetBufferLabelAddress); LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutSubmitFlip); LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutGetFlipStatus); diff --git a/src/core/libraries/videoout/video_out.h b/src/core/libraries/videoout/video_out.h index ad8ce9ed2..f3e661de4 100644 --- a/src/core/libraries/videoout/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -118,6 +118,7 @@ s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata); s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum, const BufferAttribute* attribute); +s32 PS4_SYSV_ABI sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr); s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate); s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle); s32 PS4_SYSV_ABI sceVideoOutWaitVblank(s32 handle); @@ -133,7 +134,6 @@ s32 PS4_SYSV_ABI sceVideoOutColorSettingsSetGamma(SceVideoOutColorSettings* sett s32 PS4_SYSV_ABI sceVideoOutAdjustColor(s32 handle, const SceVideoOutColorSettings* settings); // Internal system functions -void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr); s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk); void RegisterLib(Core::Loader::SymbolsResolver* sym); From 36927a7bbd68b7c374853330cd309617e1db4627 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 12 Mar 2025 20:36:14 +0200 Subject: [PATCH 043/194] New Crowdin updates (#2638) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (French) * New translations en_us.ts (Italian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Russian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Albanian) --- src/qt_gui/translations/ar_SA.ts | 32 +++++++ src/qt_gui/translations/da_DK.ts | 32 +++++++ src/qt_gui/translations/de_DE.ts | 32 +++++++ src/qt_gui/translations/el_GR.ts | 32 +++++++ src/qt_gui/translations/es_ES.ts | 32 +++++++ src/qt_gui/translations/fa_IR.ts | 32 +++++++ src/qt_gui/translations/fi_FI.ts | 32 +++++++ src/qt_gui/translations/fr_FR.ts | 32 +++++++ src/qt_gui/translations/hu_HU.ts | 32 +++++++ src/qt_gui/translations/id_ID.ts | 32 +++++++ src/qt_gui/translations/it_IT.ts | 32 +++++++ src/qt_gui/translations/ja_JP.ts | 32 +++++++ src/qt_gui/translations/ko_KR.ts | 32 +++++++ src/qt_gui/translations/lt_LT.ts | 32 +++++++ src/qt_gui/translations/nb_NO.ts | 138 +++++++++++++++++++------------ src/qt_gui/translations/nl_NL.ts | 32 +++++++ src/qt_gui/translations/pl_PL.ts | 32 +++++++ src/qt_gui/translations/pt_BR.ts | 32 +++++++ src/qt_gui/translations/pt_PT.ts | 32 +++++++ src/qt_gui/translations/ro_RO.ts | 32 +++++++ src/qt_gui/translations/ru_RU.ts | 32 +++++++ src/qt_gui/translations/sq_AL.ts | 32 +++++++ src/qt_gui/translations/sv_SE.ts | 32 +++++++ src/qt_gui/translations/tr_TR.ts | 32 +++++++ src/qt_gui/translations/uk_UA.ts | 32 +++++++ src/qt_gui/translations/vi_VN.ts | 32 +++++++ src/qt_gui/translations/zh_CN.ts | 32 +++++++ src/qt_gui/translations/zh_TW.ts | 32 +++++++ 28 files changed, 949 insertions(+), 53 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index d45af76b8..090cd4c26 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index bc9ed1d39..113d13019 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 931f93905..d366d1116 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index e09bf7192..a61d84022 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index a727792ba..8861185cb 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index f58d691ad..6984b29f8 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 5e1c20d47..81274ae80 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 75a770c09..ff1646f9e 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Impossible de lier les valeurs de l'axe plusieurs fois + + Save + Enregistrer + + + Apply + Appliquer + + + Restore Defaults + Réinitialiser par défaut + + + Cancel + Annuler + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs La molette de la souris ne peut pas être affectée aux sorties de la manette + + Save + Enregistrer + + + Apply + Appliquer + + + Restore Defaults + Réinitialiser par défaut + + + Cancel + Annuler + MainWindow diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index bb510a479..c22d74257 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index e6a8aabd9..1a8b085cf 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 2e6dcf14a..5f57efca3 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Impossibile associare i valori degli assi più di una volta + + Save + Salva + + + Apply + Applica + + + Restore Defaults + Ripristina Impostazioni Predefinite + + + Cancel + Annulla + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs La rotella del mouse non può essere associata ai comandi della levetta analogica + + Save + Salva + + + Apply + Applica + + + Restore Defaults + Ripristina Impostazioni Predefinite + + + Cancel + Annulla + MainWindow diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 981b7cf3b..d93e36770 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index d76a16430..dc5b61038 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 41b203e2b..2f4b6e59b 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 9f71e0aa5..e7c48e426 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -90,7 +90,7 @@ Patches - Programrettelse + Programrettelser Error @@ -146,11 +146,11 @@ Failed to save file: - Kunne ikke lagre fila: + Klarte ikke lagre fila: Failed to download file: - Kunne ikke laste ned fila: + Klarte ikke laste ned fila: Cheats Not Found @@ -170,11 +170,11 @@ Failed to save: - Kunne ikke lagre: + Klarte ikke lagre: Failed to download: - Kunne ikke laste ned: + Klarte ikke laste ned: Download Complete @@ -186,11 +186,11 @@ Failed to parse JSON data from HTML. - Kunne ikke analysere JSON-data fra HTML. + Klarte ikke analysere JSON-data fra HTML. Failed to retrieve HTML page. - Kunne ikke hente HTML-side. + Klarte ikke hente HTML-siden. The game is in version: %1 @@ -210,7 +210,7 @@ Failed to open file: - Kunne ikke åpne fila: + Klarte ikke åpne fila: XML ERROR: @@ -230,7 +230,7 @@ Failed to open files.json for reading. - Kunne ikke åpne files.json for lesing. + Klarte ikke åpne files.json for lesing. Name: @@ -265,7 +265,7 @@ Failed to parse update information. - Kunne ikke analysere oppdaterings-informasjonen. + Klarte ikke analysere oppdateringsinformasjon. No pre-releases found. @@ -341,22 +341,22 @@ Failed to save the update file at - Kunne ikke lagre oppdateringsfila på + Klarte ikke lagre oppdateringsfila på Starting Update... - Starter oppdatering... + Starter oppdatering … Failed to create the update script file - Kunne ikke opprette oppdateringsskriptfila + Klarte ikke opprette oppdateringsskriptfila CompatibilityInfoClass Fetching compatibility data, please wait - Henter kompatibilitetsdata, vennligst vent + Henter kompatibilitetsdata, vent litt. Cancel @@ -364,7 +364,7 @@ Loading... - Laster... + Laster … Error @@ -547,18 +547,34 @@ Cannot bind axis values more than once - Cannot bind axis values more than once + Kan ikke tildele akseverdier mer enn en gang + + + Save + Lagre + + + Apply + Bruk + + + Restore Defaults + Gjenopprett standardinnstillinger + + + Cancel + Avbryt EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Rediger oppsett for tastatur, mus og kontroller Use Per-Game configs - Use Per-Game configs + Bruk oppsett per spill Error @@ -566,11 +582,11 @@ Could not open the file for reading - Could not open the file for reading + Klarte ikke åpne fila for lesing Could not open the file for writing - Could not open the file for writing + Klarte ikke åpne fila for skriving Save Changes @@ -586,11 +602,11 @@ Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Vil du tilbakestille alle dine tilpassede innstillinger til standarden? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Vil du tilbakestille dette oppsettet til standard oppsett? Reset to Default @@ -608,7 +624,7 @@ GameInfoClass Loading game list, please wait :3 - Laster spilliste, vennligst vent :3 + Laster spilliste, vent litt :3 Cancel @@ -616,7 +632,7 @@ Loading... - Laster... + Laster … @@ -706,7 +722,7 @@ Game does not initialize properly / crashes the emulator - Spillet initialiseres ikke riktig / krasjer emulatoren + Spillet initialiseres ikke riktig eller krasjer emulatoren Game boots, but only displays a blank screen @@ -726,7 +742,7 @@ Click to see details on github - Klikk for å se detaljer på GitHub + Trykk for å se detaljer på GitHub Last updated @@ -768,7 +784,7 @@ SFO Viewer - SFO viser + SFO-viser Trophy Viewer @@ -776,7 +792,7 @@ Open Folder... - Åpne mappe... + Åpne mappe … Open Game Folder @@ -792,7 +808,7 @@ Copy info... - Kopier info... + Kopier info … Copy Name @@ -816,7 +832,7 @@ Delete... - Slett... + Slett … Delete Game @@ -836,7 +852,7 @@ Compatibility... - Kompatibilitet... + Kompatibilitet … Update database @@ -943,7 +959,7 @@ HelpDialog Quickstart - Quickstart + Hurtigstart FAQ @@ -955,7 +971,7 @@ Special Bindings - Special Bindings + Spesielle hurtigtaster Keybindings @@ -1065,7 +1081,7 @@ Touchpad Click - Berøringsplate knapp + Berøringsplateknapp Mouse to Joystick @@ -1137,23 +1153,23 @@ Speed Multiplier (def 1.0): - Hurtighetsmultiplikator (def 1.0): + Hastighetsmultiplikator (def 1.0): Common Config Selected - Common Config Selected + Felles oppsett valgt This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Denne knappen kopierer oppsettet fra felles oppsettet til den valgte profilen, og kan ikke brukes når den gjeldende brukte profilen er felles oppsettet. Copy values from Common Config - Copy values from Common Config + Kopier verdier fra felles oppsettet Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + Vil du overskrive eksisterende valg av oppsett med felles oppsettet? Unable to Save @@ -1161,26 +1177,42 @@ Cannot bind any unique input more than once - Cannot bind any unique input more than once + Kan ikke tildele unike oppsett mer enn en gang Press a key - Press a key + Trykk på en tast Cannot set mapping - Cannot set mapping + Klarte ikke tildele Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + Musehjulet kan ikke tildeles analogstikkene + + + Save + Lagre + + + Apply + Bruk + + + Restore Defaults + Gjenopprett standardinnstillinger + + + Cancel + Avbryt MainWindow Open/Add Elf Folder - Åpne/Legg til Elf-mappe + Åpne eller legg til Elf-mappe Install Packages (PKG) @@ -1200,7 +1232,7 @@ Configure... - Sett opp... + Sett opp … Install application from a .pkg file @@ -1280,7 +1312,7 @@ Search... - Søk... + Søk … File @@ -1694,7 +1726,7 @@ Add... - Legg til... + Legg til … Remove @@ -1786,7 +1818,7 @@ Play title music - Spill tittelmusikk + Spill av tittelmusikk Update Compatibility Database On Startup @@ -1878,7 +1910,7 @@ 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). - Deaktiver trofé hurtigmeny:\nDeaktiver trofévarsler i spillet. Trofé-fremgang kan fortsatt ved help av troféviseren (høyreklikk på spillet i hovedvinduet). + Slå av trofévarsler:\nFjerner trofévarsler i spillet. Troféfremgang kan fortsatt vises ved hjelp av troféviseren (høyreklikk på spillet i hovedvinduet). Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. @@ -1938,7 +1970,7 @@ Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. - Bredde/Høyde:\nAngir størrelsen på emulatorvinduet 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 annerledes fra oppløsningen i spillet. Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! @@ -2134,7 +2166,7 @@ Cannot create portable user folder - Cannot create portable user folder + Klarte ikke opprette separat brukermappe %1 already exists @@ -2142,7 +2174,7 @@ Portable user folder created - Portable user folder created + Separat brukermappe opprettet %1 successfully created. @@ -2150,7 +2182,7 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Åpne mappa med tilpassede bilder og lyder for trofé:\nDu kan legge til tilpassede bilder til trofeer og en lyd.\nLegg filene til custom_trophy med følgende navn:\ntrophy.wav ELLER trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nMerk: Lyden avspilles kun i Qt-versjonen. diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 8596c7e71..f6c062da3 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index faa464792..4c4a33ec2 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 8f3975cb1..794215401 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Não é possível vincular os valores do eixo mais de uma vez + + Save + Salvar + + + Apply + Aplicar + + + Restore Defaults + Restaurar Padrões + + + Cancel + Cancelar + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs A rolagem do mouse não pode ser mapeada para saídas do analógico + + Save + Salvar + + + Apply + Aplicar + + + Restore Defaults + Restaurar Padrões + + + Cancel + Cancelar + MainWindow diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index 0667447e1..dc0059b86 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index ded417640..4261bf9e2 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index ecf155dbf..6ca16121f 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Невозможно привязать значения оси более одного раза + + Save + Сохранить + + + Apply + Применить + + + Restore Defaults + По умолчанию + + + Cancel + Отмена + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Колесо не может быть назначено для вывода стиков + + Save + Сохранить + + + Apply + Применить + + + Restore Defaults + По умолчанию + + + Cancel + Отмена + MainWindow diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index da64729d0..3d1f1a222 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Nuk mund të caktohen vlerat e boshtit më shumë se një herë + + Save + Ruaj + + + Apply + Zbato + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Rrota e miut nuk mund të caktohet për daljet e levës + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index eb453b4d9..de3781414 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Kan inte binda axelvärden fler än en gång + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mushjulet kan inte mappas till spakutmatningar + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 095c73c7b..fd4669369 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Eksen değerleri birden fazla kez bağlanamaz + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mouse tekerleği analog çıkışlarına atanamaz + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 688cedf9d..1c074be6a 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Неможливо пере назначити кнопку більше одного разу + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Коліщатко миші не можна прив'язати зі значенням стиків + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index d20c07dd9..8fa0889bc 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index f761fe8c4..80b322112 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once 摇杆 X/Y 轴的操作绑定不在同一直线 + + Save + 保存 + + + Apply + 应用 + + + Restore Defaults + 恢复默认设置 + + + Cancel + 取消 + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs 鼠标滚轮无法映射到摇杆 + + Save + 保存 + + + Apply + 应用 + + + Restore Defaults + 恢复默认设置 + + + Cancel + 取消 + MainWindow diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 311ce3ab8..2950f541f 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -549,6 +549,22 @@ Cannot bind axis values more than once Cannot bind axis values more than once + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + EditorDialog @@ -1175,6 +1191,22 @@ Mousewheel cannot be mapped to stick outputs Mousewheel cannot be mapped to stick outputs + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + MainWindow From 5691046dcc2c1bd57f5b6f21fc5ced8735771f41 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Thu, 13 Mar 2025 13:10:24 -0300 Subject: [PATCH 044/194] Renderer fixes (Splash + Aspect Ratio) (#2645) * rewrite splash removed Splash class rewrite using imgui texture manager fix crashes & old validation error * handle games with abnormal aspect ratios --- CMakeLists.txt | 2 - src/common/elf_info.h | 7 + src/core/devtools/layer.cpp | 4 +- src/core/file_format/splash.cpp | 38 ---- src/core/file_format/splash.h | 42 ----- src/core/libraries/videoout/driver.cpp | 10 +- src/emulator.cpp | 8 +- src/imgui/renderer/texture_manager.cpp | 1 + .../renderer_vulkan/vk_presenter.cpp | 171 +++++------------- src/video_core/renderer_vulkan/vk_presenter.h | 5 +- 10 files changed, 66 insertions(+), 222 deletions(-) delete mode 100644 src/core/file_format/splash.cpp delete mode 100644 src/core/file_format/splash.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 99620e7d3..f4c23b7c6 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -644,8 +644,6 @@ set(CORE src/core/aerolib/stubs.cpp src/core/file_format/playgo_chunk.h src/core/file_format/trp.cpp src/core/file_format/trp.h - src/core/file_format/splash.h - src/core/file_format/splash.cpp src/core/file_sys/fs.cpp src/core/file_sys/fs.h src/core/loader.cpp diff --git a/src/common/elf_info.h b/src/common/elf_info.h index d885709cd..062cee012 100644 --- a/src/common/elf_info.h +++ b/src/common/elf_info.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -69,6 +70,8 @@ class ElfInfo { u32 raw_firmware_ver = 0; PSFAttributes psf_attributes{}; + std::filesystem::path splash_path{}; + public: static constexpr u32 FW_15 = 0x1500000; static constexpr u32 FW_16 = 0x1600000; @@ -116,6 +119,10 @@ public: ASSERT(initialized); return psf_attributes; } + + [[nodiscard]] const std::filesystem::path& GetSplashPath() const { + return splash_path; + } }; } // namespace Common diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 5f0fd0c95..87fd9ffb3 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -21,8 +21,8 @@ extern std::unique_ptr presenter; using namespace ImGui; -using namespace Core::Devtools; -using L = Core::Devtools::Layer; +using namespace ::Core::Devtools; +using L = ::Core::Devtools::Layer; static bool show_simple_fps = false; static bool visibility_toggled = false; diff --git a/src/core/file_format/splash.cpp b/src/core/file_format/splash.cpp deleted file mode 100644 index 4eb701cf7..000000000 --- a/src/core/file_format/splash.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "common/assert.h" -#include "common/io_file.h" -#include "common/stb.h" -#include "splash.h" - -bool Splash::Open(const std::filesystem::path& filepath) { - ASSERT_MSG(filepath.extension().string() == ".png", "Unexpected file format passed"); - - Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - - std::vector png_file{}; - const auto png_size = file.GetSize(); - png_file.resize(png_size); - file.Seek(0); - file.Read(png_file); - - auto* img_mem = stbi_load_from_memory(png_file.data(), png_file.size(), - reinterpret_cast(&img_info.width), - reinterpret_cast(&img_info.height), - reinterpret_cast(&img_info.num_channels), 4); - if (!img_mem) { - return false; - } - - const auto img_size = img_info.GetSizeBytes(); - img_data.resize(img_size); - std::memcpy(img_data.data(), img_mem, img_size); - stbi_image_free(img_mem); - return true; -} diff --git a/src/core/file_format/splash.h b/src/core/file_format/splash.h deleted file mode 100644 index 7c563f317..000000000 --- a/src/core/file_format/splash.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include "common/types.h" - -class Splash { -public: - struct ImageInfo { - u32 width; - u32 height; - u32 num_channels; - - u32 GetSizeBytes() const { - return width * height * 4; // we always forcing rgba8 for simplicity - } - }; - - Splash() = default; - ~Splash() = default; - - bool Open(const std::filesystem::path& filepath); - [[nodiscard]] bool IsLoaded() const { - return img_data.size(); - } - - const auto& GetImageData() const { - return img_data; - } - - ImageInfo GetImageInfo() const { - return img_info; - } - -private: - ImageInfo img_info{}; - std::vector img_data{}; -}; diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 0f832910c..d2c980882 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -160,11 +160,8 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) { } void VideoOutDriver::Flip(const Request& req) { - // Whatever the game is rendering show splash if it is active - if (!presenter->ShowSplash(req.frame)) { - // Present the frame. - presenter->Present(req.frame); - } + // Present the frame. + presenter->Present(req.frame); // Update flip status. auto* port = req.port; @@ -201,9 +198,6 @@ void VideoOutDriver::Flip(const Request& req) { } void VideoOutDriver::DrawBlankFrame() { - if (presenter->ShowSplash(nullptr)) { - return; - } const auto empty_frame = presenter->PrepareBlankFrame(false); presenter->Present(empty_frame); } diff --git a/src/emulator.cpp b/src/emulator.cpp index 758720325..b6586ecfd 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -24,7 +24,6 @@ #include "common/singleton.h" #include "common/version.h" #include "core/file_format/psf.h" -#include "core/file_format/splash.h" #include "core/file_format/trp.h" #include "core/file_sys/fs.h" #include "core/libraries/disc_map/disc_map.h" @@ -185,12 +184,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vectorGetHostPath("/app0/sce_sys/pic1.png"); if (std::filesystem::exists(pic1_path)) { - auto* splash = Common::Singleton::Instance(); - if (!splash->IsLoaded()) { - if (!splash->Open(pic1_path)) { - LOG_ERROR(Loader, "Game splash: unable to open file"); - } - } + game_info.splash_path = pic1_path; } game_info.initialized = true; diff --git a/src/imgui/renderer/texture_manager.cpp b/src/imgui/renderer/texture_manager.cpp index e217cd130..49f912a92 100644 --- a/src/imgui/renderer/texture_manager.cpp +++ b/src/imgui/renderer/texture_manager.cpp @@ -175,6 +175,7 @@ void WorkerLoop() { auto texture = Vulkan::UploadTexture(pixels, vk::Format::eR8G8B8A8Unorm, width, height, width * height * 4 * sizeof(stbi_uc)); + stbi_image_free((void*)pixels); core->upload_data = texture; core->width = width; diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index dc7fa8860..4a6a5c7c2 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -6,7 +6,6 @@ #include "common/singleton.h" #include "core/debug_state.h" #include "core/devtools/layer.h" -#include "core/file_format/splash.h" #include "core/libraries/system/systemservice.h" #include "imgui/renderer/imgui_core.h" #include "sdl_window.h" @@ -21,6 +20,8 @@ #include #include + +#include "common/elf_info.h" #include "imgui/renderer/imgui_impl_vulkan.h" namespace Vulkan { @@ -269,116 +270,10 @@ Frame* Presenter::PrepareLastFrame() { return frame; } -bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) { - const auto* splash = Common::Singleton::Instance(); - if (splash->GetImageData().empty()) { - return false; - } - - if (!Libraries::SystemService::IsSplashVisible()) { - return false; - } - - draw_scheduler.EndRendering(); - const auto cmdbuf = draw_scheduler.CommandBuffer(); - - if (Config::getVkHostMarkersEnabled()) { - cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ - .pLabelName = "ShowSplash", - }); - } - - if (!frame) { - if (!splash_img.has_value()) { - VideoCore::ImageInfo info{}; - info.pixel_format = vk::Format::eR8G8B8A8Unorm; - info.type = vk::ImageType::e2D; - info.size = - VideoCore::Extent3D{splash->GetImageInfo().width, splash->GetImageInfo().height, 1}; - info.pitch = splash->GetImageInfo().width; - info.guest_address = VAddr(splash->GetImageData().data()); - info.guest_size = splash->GetImageData().size(); - info.mips_layout.emplace_back(splash->GetImageData().size(), - splash->GetImageInfo().width, - splash->GetImageInfo().height, 0); - splash_img.emplace(instance, present_scheduler, info); - splash_img->flags &= ~VideoCore::GpuDirty; - texture_cache.RefreshImage(*splash_img); - - splash_img->Transit(vk::ImageLayout::eTransferSrcOptimal, - vk::AccessFlagBits2::eTransferRead, {}, cmdbuf); - } - - frame = GetRenderFrame(); - } - - const auto frame_subresources = vk::ImageSubresourceRange{ - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }; - - const auto pre_barrier = - vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eTransfer, - .srcAccessMask = vk::AccessFlagBits2::eTransferRead, - .dstStageMask = vk::PipelineStageFlagBits2::eTransfer, - .dstAccessMask = vk::AccessFlagBits2::eTransferWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eTransferDstOptimal, - .image = frame->image, - .subresourceRange{frame_subresources}}; - - cmdbuf.pipelineBarrier2(vk::DependencyInfo{ - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &pre_barrier, - }); - - cmdbuf.blitImage(splash_img->image, vk::ImageLayout::eTransferSrcOptimal, frame->image, - vk::ImageLayout::eTransferDstOptimal, - MakeImageBlitFit(splash->GetImageInfo().width, splash->GetImageInfo().height, - frame->width, frame->height), - vk::Filter::eLinear); - - const auto post_barrier = - vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eTransfer, - .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, - .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, - .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eTransferDstOptimal, - .newLayout = vk::ImageLayout::eGeneral, - .image = frame->image, - .subresourceRange{frame_subresources}}; - - cmdbuf.pipelineBarrier2(vk::DependencyInfo{ - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &post_barrier, - }); - - if (Config::getVkHostMarkersEnabled()) { - cmdbuf.endDebugUtilsLabelEXT(); - } - - // Flush frame creation commands. - frame->ready_semaphore = draw_scheduler.GetMasterSemaphore()->Handle(); - frame->ready_tick = draw_scheduler.CurrentTick(); - SubmitInfo info{}; - draw_scheduler.Flush(info); - - Present(frame); - return true; -} - Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) { // Request a free presentation frame. Frame* frame = GetRenderFrame(); - if (frame->width != expected_frame_width || frame->height != expected_frame_height || - frame->is_hdr != swapchain.GetHDR()) { - RecreateFrame(frame, expected_frame_width, expected_frame_height); - } - // EOP flips are triggered from GPU thread so use the drawing scheduler to record // commands. Otherwise we are dealing with a CPU flip which could have arrived // from any guest thread. Use a separate scheduler for that. @@ -420,6 +315,11 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) if (image_id != VideoCore::NULL_IMAGE_ID) { auto& image = texture_cache.GetImage(image_id); vk::Extent2D image_size = {image.info.size.width, image.info.size.height}; + float ratio = (float)image_size.width / (float)image_size.height; + if (ratio != expected_ratio) { + expected_ratio = ratio; + } + image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}, cmdbuf); @@ -625,18 +525,43 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) { ImGui::SetNextWindowDockID(dockId, ImGuiCond_Once); ImGui::Begin("Display##game_display", nullptr, ImGuiWindowFlags_NoNav); + auto game_texture = frame->imgui_texture; + auto game_width = frame->width; + auto game_height = frame->height; + + if (Libraries::SystemService::IsSplashVisible()) { // draw splash + if (!splash_img.has_value()) { + splash_img.emplace(); + auto splash_path = Common::ElfInfo::Instance().GetSplashPath(); + if (!splash_path.empty()) { + splash_img = ImGui::RefCountedTexture::DecodePngFile(splash_path); + } + } + if (auto& splash_image = this->splash_img.value()) { + auto [im_id, width, height] = splash_image.GetTexture(); + game_texture = im_id; + game_width = width; + game_height = height; + } + } + ImVec2 contentArea = ImGui::GetContentRegionAvail(); - const vk::Rect2D imgRect = - FitImage(frame->width, frame->height, (s32)contentArea.x, (s32)contentArea.y); SetExpectedGameSize((s32)contentArea.x, (s32)contentArea.y); - ImGui::SetCursorPos(ImGui::GetCursorStartPos() + ImVec2{ - (float)imgRect.offset.x, - (float)imgRect.offset.y, - }); - ImGui::Image(frame->imgui_texture, { - static_cast(imgRect.extent.width), - static_cast(imgRect.extent.height), - }); + + const auto imgRect = + FitImage(game_width, game_height, (s32)contentArea.x, (s32)contentArea.y); + ImVec2 offset{ + static_cast(imgRect.offset.x), + static_cast(imgRect.offset.y), + }; + ImVec2 size{ + static_cast(imgRect.extent.width), + static_cast(imgRect.extent.height), + }; + + ImGui::SetCursorPos(ImGui::GetCursorStartPos() + offset); + ImGui::Image(game_texture, size); + ImGui::End(); ImGui::PopStyleVar(3); ImGui::PopStyleColor(); @@ -707,19 +632,23 @@ Frame* Presenter::GetRenderFrame() { } } + if (frame->width != expected_frame_width || frame->height != expected_frame_height || + frame->is_hdr != swapchain.GetHDR()) { + RecreateFrame(frame, expected_frame_width, expected_frame_height); + } + return frame; } void Presenter::SetExpectedGameSize(s32 width, s32 height) { - constexpr float expectedRatio = 1920.0 / 1080.0f; const float ratio = (float)width / (float)height; expected_frame_height = height; expected_frame_width = width; - if (ratio > expectedRatio) { - expected_frame_width = static_cast(height * expectedRatio); + if (ratio > expected_ratio) { + expected_frame_width = static_cast(height * expected_ratio); } else { - expected_frame_height = static_cast(width / expectedRatio); + expected_frame_height = static_cast(width / expected_ratio); } } diff --git a/src/video_core/renderer_vulkan/vk_presenter.h b/src/video_core/renderer_vulkan/vk_presenter.h index 892112397..ad2708474 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.h +++ b/src/video_core/renderer_vulkan/vk_presenter.h @@ -6,6 +6,7 @@ #include #include "imgui/imgui_config.h" +#include "imgui/imgui_texture.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/renderer_vulkan/host_passes/fsr_pass.h" #include "video_core/renderer_vulkan/host_passes/pp_pass.h" @@ -92,7 +93,6 @@ public: }) != vo_buffers_addr.cend(); } - bool ShowSplash(Frame* frame = nullptr); void Present(Frame* frame, bool is_reusing_frame = false); void RecreateFrame(Frame* frame, u32 width, u32 height); Frame* PrepareLastFrame(); @@ -125,6 +125,7 @@ private: void SetExpectedGameSize(s32 width, s32 height); private: + float expected_ratio{1920.0 / 1080.0f}; u32 expected_frame_width{1920}; u32 expected_frame_height{1080}; @@ -148,7 +149,7 @@ private: std::mutex free_mutex; std::condition_variable free_cv; std::condition_variable_any frame_cv; - std::optional splash_img; + std::optional splash_img; std::vector vo_buffers_addr; }; From 171f755c139764d83e6fc712fcbbcc9d4c5c5956 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:02:34 -0700 Subject: [PATCH 045/194] build: Compile for Sandy Bridge CPU target. (#2651) --- .gitmodules | 4 ---- CMakeLists.txt | 19 ++++++++++--------- externals/CMakeLists.txt | 29 +++++++++++++---------------- externals/cryptopp | 2 +- externals/cryptoppwin | 1 - 5 files changed, 24 insertions(+), 31 deletions(-) delete mode 160000 externals/cryptoppwin diff --git a/.gitmodules b/.gitmodules index 3d0d21c5b..ca229bedd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,10 +6,6 @@ path = externals/cryptopp url = https://github.com/shadps4-emu/ext-cryptopp.git shallow = true -[submodule "externals/cryptoppwin"] - path = externals/cryptoppwin - url = https://github.com/shadps4-emu/ext-cryptoppwin.git - shallow = true [submodule "externals/zlib-ng"] path = externals/zlib-ng url = https://github.com/shadps4-emu/ext-zlib-ng.git diff --git a/CMakeLists.txt b/CMakeLists.txt index f4c23b7c6..f05587d38 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,8 +37,10 @@ option(ENABLE_UPDATER "Enables the options to updater" ON) # First, determine whether to use CMAKE_OSX_ARCHITECTURES or CMAKE_SYSTEM_PROCESSOR. if (APPLE AND CMAKE_OSX_ARCHITECTURES) set(BASE_ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}") -else() +elseif (CMAKE_SYSTEM_PROCESSOR) set(BASE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") +else() + set(BASE_ARCHITECTURE "${CMAKE_HOST_SYSTEM_PROCESSOR}") endif() # Next, match common architecture strings down to a known common value. @@ -50,7 +52,12 @@ else() message(FATAL_ERROR "Unsupported CPU architecture: ${BASE_ARCHITECTURE}") endif() -if (APPLE AND ARCHITECTURE STREQUAL "x86_64" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") +if (ARCHITECTURE STREQUAL "x86_64") + # Set x86_64 target level to Sandy Bridge to generally match what is supported for PS4 guest code with CPU patches. + add_compile_options(-march=sandybridge) +endif() + +if (APPLE AND ARCHITECTURE STREQUAL "x86_64" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64") # Exclude ARM homebrew path to avoid conflicts when cross compiling. list(APPEND CMAKE_IGNORE_PREFIX_PATH "/opt/homebrew") @@ -995,7 +1002,7 @@ endif() create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) -target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers) +target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers cryptopp::cryptopp) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") @@ -1044,12 +1051,6 @@ if (NOT ENABLE_QT_GUI) target_link_libraries(shadps4 PRIVATE SDL3::SDL3) endif() -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND MSVC) - target_link_libraries(shadps4 PRIVATE cryptoppwin) -else() - target_link_libraries(shadps4 PRIVATE cryptopp::cryptopp) -endif() - if (ENABLE_QT_GUI) target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia) add_definitions(-DENABLE_QT_GUI) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index bb434677d..3b29a838e 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -26,22 +26,19 @@ if (NOT TARGET fmt::fmt) add_subdirectory(fmt) endif() -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND MSVC) - # If it is clang and MSVC we will add a static lib - # CryptoPP - add_subdirectory(cryptoppwin) - target_include_directories(cryptoppwin INTERFACE cryptoppwin/include) -else() - # CryptoPP - if (NOT TARGET cryptopp::cryptopp) - set(CRYPTOPP_INSTALL OFF) - set(CRYPTOPP_BUILD_TESTING OFF) - set(CRYPTOPP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp) - add_subdirectory(cryptopp-cmake) - file(COPY cryptopp DESTINATION cryptopp FILES_MATCHING PATTERN "*.h") - # remove externals/cryptopp from include directories because it contains a conflicting zlib.h file - set_target_properties(cryptopp PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/cryptopp") - endif() +# CryptoPP +if (NOT TARGET cryptopp::cryptopp) + set(CRYPTOPP_INSTALL OFF) + set(CRYPTOPP_BUILD_TESTING OFF) + set(CRYPTOPP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp) + # cryptopp instruction set checks do not account for added compile options, + # so disable extensions in the library config to match our chosen target CPU. + set(CRYPTOPP_DISABLE_AESNI ON) + set(CRYPTOPP_DISABLE_AVX2 ON) + add_subdirectory(cryptopp-cmake) + file(COPY cryptopp DESTINATION cryptopp FILES_MATCHING PATTERN "*.h") + # remove externals/cryptopp from include directories because it contains a conflicting zlib.h file + set_target_properties(cryptopp PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/cryptopp") endif() if (NOT TARGET FFmpeg::ffmpeg) diff --git a/externals/cryptopp b/externals/cryptopp index 60f81a77e..ee84a3144 160000 --- a/externals/cryptopp +++ b/externals/cryptopp @@ -1 +1 @@ -Subproject commit 60f81a77e0c9a0e7ffc1ca1bc438ddfa2e43b78e +Subproject commit ee84a3144137ac0a1294c0b4a342dca13b66923f diff --git a/externals/cryptoppwin b/externals/cryptoppwin deleted file mode 160000 index bc3441dd2..000000000 --- a/externals/cryptoppwin +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bc3441dd2d6a9728e747dc0180bc8b9065a2923c From b520994960700e565c56fc722db06be688716a7b Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 19 Mar 2025 12:28:55 +0200 Subject: [PATCH 046/194] New Crowdin updates (#2644) * New translations en_us.ts (Albanian) * New translations en_us.ts (Albanian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Spanish) * New translations en_us.ts (Spanish) * New translations en_us.ts (Spanish) * New translations en_us.ts (Swedish) * New translations en_us.ts (Spanish) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Ukrainian) --- src/qt_gui/translations/es_ES.ts | 332 +++++++++++++++---------------- src/qt_gui/translations/nb_NO.ts | 14 +- src/qt_gui/translations/sq_AL.ts | 16 +- src/qt_gui/translations/sv_SE.ts | 16 +- src/qt_gui/translations/uk_UA.ts | 16 +- src/qt_gui/translations/zh_CN.ts | 2 +- 6 files changed, 198 insertions(+), 198 deletions(-) diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 8861185cb..be3c701a9 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -443,15 +443,15 @@ Config Selection - Config Selection + Selección de Configuraciones Common Config - Common Config + Configuración Estándar Use per-game configs - Use per-game configs + Usar configuraciones por juego L1 / LB @@ -535,78 +535,78 @@ Override Lightbar Color - Override Lightbar Color + Reemplazar el Color de la Barra de Luz Override Color - Override Color + Reemplazar Color Unable to Save - Unable to Save + No se Pudo Guardar Cannot bind axis values more than once - Cannot bind axis values more than once + No se pueden vincular valores del eje más de una vez Save - Save + Guardar Apply - Apply + Aplicar Restore Defaults - Restore Defaults + Restaurar Valores Por Defecto Cancel - Cancel + Cancelar EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Editar Asignaciones de Teclado + Ratón y Mando Use Per-Game configs - Use Per-Game configs + Usar Configuraciones por Juego Error - Error + Error Could not open the file for reading - Could not open the file for reading + No se pudo abrir el archivo para la lectura Could not open the file for writing - Could not open the file for writing + No se pudo abrir el archivo para escritura Save Changes - Save Changes + Guardar Cambios Do you want to save changes? - Do you want to save changes? + ¿Quieres guardar los cambios? Help - Help + Ayuda Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + ¿Desea restablecer su configuración predeterminada personalizada a la configuración por defecto original? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + ¿Quieres restablecer esta configuración a tu configuración por defecto personalizada? Reset to Default @@ -702,7 +702,7 @@ Never Played - Never Played + Nunca Jugado h @@ -788,7 +788,7 @@ Trophy Viewer - Ver trofeos + Expositor de Trofeos Open Folder... @@ -848,7 +848,7 @@ Delete Trophy - Delete Trophy + Eliminar Trofeo Compatibility... @@ -928,7 +928,7 @@ No log file found for this game! - No log file found for this game! + ¡No se encontró un archivo de registro para este juego! Failed to convert icon. @@ -940,7 +940,7 @@ This game has no saved trophies to delete! - This game has no saved trophies to delete! + ¡Este juego no tiene trofeos guardados para eliminar! Save Data @@ -948,7 +948,7 @@ Trophy - Trophy + Trofeo SFO Viewer for @@ -959,23 +959,23 @@ HelpDialog Quickstart - Quickstart + Inicio Rápido FAQ - FAQ + Preguntas Frecuentes (FAQ) Syntax - Syntax + Sintaxis Special Bindings - Special Bindings + Asignación de Teclas Especiales Keybindings - Keybindings + Asignación de Teclas @@ -1001,55 +1001,55 @@ KBMSettings Configure Controls - Configure Controls + Configurar Controles D-Pad - D-Pad + Cruceta Up - Up + Arriba unmapped - unmapped + sin vincular Left - Left + Izquierda Right - Right + Derecha Down - Down + Abajo Left Analog Halfmode - Left Analog Halfmode + Modo Reducido del Stick Izquierdo hold to move left stick at half-speed - hold to move left stick at half-speed + manten para mover el stick izquierdo a la mitad de la velocidad Left Stick - Left Stick + Stick Izquierdo Config Selection - Config Selection + Selección de Configuraciones Common Config - Common Config + Configuración Estándar Use per-game configs - Use per-game configs + Usar configuraciones por juego L1 @@ -1061,11 +1061,11 @@ Text Editor - Text Editor + Editor de Texto Help - Help + Ayuda R1 @@ -1085,127 +1085,127 @@ Mouse to Joystick - Ratón A Joystick + Ratón a Joystick *press F7 ingame to activate - *press F7 ingame to activate + * presiona F7 en el juego para activar R3 - R3 + R3 Options - Options + Opciones Mouse Movement Parameters - Mouse Movement Parameters + Parámetros Movimiento del Ratón note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + nota: haga clic en Botón de ayuda/Asignación de Teclas Especiales para más información Face Buttons - Face Buttons + Botones de Acción Triangle - Triangle + Triángulo Square - Square + Cuadrado Circle - Circle + Círculo Cross - Cross + Equis Right Analog Halfmode - Right Analog Halfmode + Modo Reducido del Stick Derecho hold to move right stick at half-speed - hold to move right stick at half-speed + manten para mover el stick derecho a la mitad de la velocidad Right Stick - Right Stick + Stick Derecho Speed Offset (def 0.125): - Speed Offset (def 0.125): + Compensación de Velocidad (def 0.125): Copy from Common Config - Copy from Common Config + Copiar desde la Configuración Estándar Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Zona Muerta (def 0.50): Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Multiplicador de Velocidad (def 1.0): Common Config Selected - Common Config Selected + Configuración Estándar Seleccionada This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Este botón copia mapeos de la Configuración Estándar al perfil seleccionado actualmente, y no se puede utilizar cuando el perfil seleccionado es la Configuración Estándar. Copy values from Common Config - Copy values from Common Config + Copiar valores de la Configuración Estándar Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + ¿Quiere sobrescribir los mapeos existentes con los mapeos de la Configuración Estándar? Unable to Save - Unable to Save + No se Pudo Guardar Cannot bind any unique input more than once - Cannot bind any unique input more than once + No se puede vincular ninguna entrada única más de una vez Press a key - Press a key + Pulsa una tecla Cannot set mapping - Cannot set mapping + No se pudo asignar el mapeo Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + La rueda del ratón no puede ser mapeada al stick Save - Save + Guardar Apply - Apply + Aplicar Restore Defaults - Restore Defaults + Restaurar Valores Por Defecto Cancel - Cancel + Cancelar @@ -1284,11 +1284,11 @@ List View - Vista de lista + Vista de Lista Grid View - Vista de cuadrícula + Vista de Cuadrícula Elf Viewer @@ -1296,7 +1296,7 @@ Game Install Directory - Carpeta de instalación de los juegos + Carpeta de Instalación de Juegos Download Cheats/Patches @@ -1304,7 +1304,7 @@ Dump Game List - Volcar lista de juegos + Volcar Lista de Juegos PKG Viewer @@ -1324,11 +1324,11 @@ Game List Icons - Iconos de los juegos + Iconos de Juegos Game List Mode - Tipo de lista + Tipo de Lista Settings @@ -1372,7 +1372,7 @@ Game List - Lista de juegos + Lista de Juegos * Unsupported Vulkan Version @@ -1496,7 +1496,7 @@ PKG is a patch or DLC, please install the game first! - El archivo PKG es un parche o un DLC, ¡debes instalar el juego primero! + El archivo PKG es un parche o DLC, ¡debes instalar el juego primero! Game is already running! @@ -1511,7 +1511,7 @@ PKGViewer Open Folder - Abrir carpeta + Abrir Carpeta PKG ERROR @@ -1523,7 +1523,7 @@ Serial - Numero de serie + Número de Serie Installed @@ -1543,7 +1543,7 @@ App Ver - Versión de aplicación + Versión de la Aplicación FW @@ -1590,7 +1590,7 @@ Console Language - Idioma de la consola + Idioma de la Consola Emulator Language @@ -1602,7 +1602,7 @@ Enable Separate Update Folder - Habilitar carpeta independiente de actualizaciones + Habilitar Carpeta Independiente de Actualizaciones Default tab when opening settings @@ -1614,7 +1614,7 @@ Show Splash - Mostrar splash + Mostrar Pantalla de Bienvenida Enable Discord Rich Presence @@ -1622,11 +1622,11 @@ Username - Nombre de usuario + Nombre de Usuario Trophy Key - Clave de trofeos + Clave de Trofeos Trophy @@ -1634,7 +1634,7 @@ Open the custom trophy images/sounds folder - Open the custom trophy images/sounds folder + Abrir la carpeta de trofeos/sonidos personalizados Logger @@ -1642,15 +1642,15 @@ Log Type - Tipo de registro + Tipo de Registro Log Filter - Filtro de registro + Filtro de Registro Open Log Location - Abrir ubicación del registro + Abrir Ubicación del registro Input @@ -1662,11 +1662,11 @@ Hide Cursor - Ocultar cursor + Ocultar Cursor Hide Cursor Idle Timeout - Tiempo de espera para ocultar cursor inactivo + Tiempo de Espera para Ocultar Cursor Inactivo s @@ -1678,7 +1678,7 @@ Back Button Behavior - Comportamiento del botón de retroceso + Comportamiento del Botón de Retroceso Graphics @@ -1694,7 +1694,7 @@ Graphics Device - Dispositivo gráfico + Dispositivo Gráfico Vblank Divider @@ -1706,7 +1706,7 @@ Enable Shaders Dumping - Habilitar volcado de shaders + Habilitar volcado de Shaders Enable NULL GPU @@ -1722,7 +1722,7 @@ Game Folders - Carpetas de juego + Carpetas de Juegos Add... @@ -1738,27 +1738,27 @@ Enable Debug Dumping - Habilitar volcado de depuración + Habilitar Volcado de Depuración Enable Vulkan Validation Layers - Habilitar capas de validación de Vulkan + Habilitar Capas de Validación de Vulkan Enable Vulkan Synchronization Validation - Habilitar validación de sincronización de Vulkan + Habilitar Validación de Sincronización de Vulkan Enable RenderDoc Debugging - Habilitar depuración de RenderDoc + Habilitar Depuración de RenderDoc Enable Crash Diagnostics - Habilitar diagnóstico de fallos + Habilitar Diagnóstico de Fallos Collect Shaders - Recopilar shaders + Recopilar Shaders Copy GPU Buffers @@ -1766,11 +1766,11 @@ Host Debug Markers - Host Debug Markers + Marcadores de Depuración del Host Guest Debug Markers - Guest Debug Markers + Marcadores de Depuración del Invitado Update @@ -1778,11 +1778,11 @@ Check for Updates at Startup - Buscar actualizaciones al iniciar + Buscar Actualizaciones al Iniciar Always Show Changelog - Mostrar siempre el registro de cambios + Mostrar Siempre el Registro de Cambios Update Channel @@ -1790,7 +1790,7 @@ Check for Updates - Verificar actualizaciones + Buscar Actualizaciones GUI Settings @@ -1802,11 +1802,11 @@ Disable Trophy Notification - Disable Trophy Notification + Desactivar Notificaciones de Trofeos Background Image - Imagen de fondo + Imagen de Fondo Show Background Image @@ -1826,7 +1826,7 @@ Game Compatibility - Game Compatibility + Compatibilidad Display Compatibility Data @@ -1858,7 +1858,7 @@ Point your mouse at an option to display its description. - Coloque el mouse sobre una opción para mostrar su descripción. + Coloque el ratón sobre una opción para mostrar su descripción. Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. @@ -1870,7 +1870,7 @@ Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. + Habilitar Carpeta Independiente de Actualizaciónes:\nHabilita el instalar actualizaciones del juego en una carpeta separada para mas facilidad en la gestión.\nPuede crearse manualmente añadiendo la actualización extraída a la carpeta del juego con el nombre "CUSA00000-UPDATE" donde el CUSA ID coincide con el ID del juego. Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. @@ -1886,7 +1886,7 @@ Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + Clave de Trofeos:\nClave utilizada para descifrar trofeos. Debe obtenerse de tu consola desbloqueada.\nSolo debe contener caracteres hexadecimales. Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. @@ -1910,15 +1910,15 @@ 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). - 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). + Desactivar Notificaciones de trofeos:\nDesactiva las notificaciones de trofeos en el juego. El progreso de trofeos todavía puede ser rastreado usando el Expositor de Trofeos (haz clic derecho en el juego en la ventana principal). Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. - Ocultar Cursor:\nElija cuándo desaparecerá el cursor:\nNunca: Siempre verá el mouse.\nInactivo: Establezca un tiempo para que desaparezca después de estar inactivo.\nSiempre: nunca verá el mouse. + Ocultar Cursor:\nElija cuándo desaparecerá el cursor:\nNunca: Siempre verá el ratón.\nInactivo: Establezca un tiempo para que desaparezca después de estar inactivo.\nSiempre: nunca verá el ratón. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - Establezca un tiempo para que el mouse desaparezca después de estar inactivo. + Establezca un tiempo para que el ratón desaparezca después de estar inactivo. Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. @@ -1926,15 +1926,15 @@ Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + Mostrar Datos de Compatibilidad:\nMuestra información de compatibilidad de juegos en vista de tabla. Habilite "Actualizar Compatibilidad al Iniciar" para obtener información actualizada. Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + Actualizar Compatibilidad al Iniciar:\nActualiza automáticamente la base de datos de compatibilidad cuando shadPS4 se inicia. Update Compatibility Database:\nImmediately update the compatibility database. - Update Compatibility Database:\nImmediately update the compatibility database. + Actualizar Base de Datos de Compatibilidad:\nActualizar inmediatamente la base de datos de compatibilidad. Never @@ -1978,7 +1978,7 @@ Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. - Habilitar la Volcadura de Sombras:\nPor el bien de la depuración técnica, guarda las sombras del juego en una carpeta mientras se renderizan. + Habilitar la Volcadura de Shaders:\nPor el bien de la depuración técnica, guarda las sombras del juego en una carpeta mientras se renderizan. Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. @@ -1986,7 +1986,7 @@ Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. - Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + Habilitar HDR:\nHabilita HDR en juegos que lo soporten.\nTu monitor debe tener soporte para el espacio de color PQ BT2020 y el formato RGB10A2 de cadena de intercambio. Game Folders:\nThe list of folders to check for installed games. @@ -2018,31 +2018,31 @@ Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). - Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + Recopilar Shaders:\nNecesitas esto habilitado para editar shaders con el menú de depuración (Ctrl + F10). 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. - 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. + Diagnóstico de cuelgues:\nCrea un archivo .yaml con información sobre el estado de Vulkan en el momento del cuelgue.\nÚtil para depurar errores de tipo 'Dispositivo perdido' . Con esto activado, deberías habilitar los marcadores de depuración de Host E Invitado.\nNo funciona en GPUs de Intel.\nNecesitas activar las Capas de Validación de Vulkan y el SDK de Vulkan para que funcione. Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. - Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + Copiar Búferes de GPU:\nSortea condiciones de carrera que implican envíos de GPU.\nPuede o no ayudar con cuelgues del tipo 0 de PM4. 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 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. + Marcadores de Depuración del Host:\n Inserta información del emulador como marcadores para comandos AMDGPU específicos, además de proporcionar nombres de depuración.\nCon esto activado, deberías habilitar el diagnóstico de fallos.\nUtil para programas como RenderDoc. 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 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. + Marcadores de Depuración del Invitado:\n Inserta cualquier marcador que el propio juego ha añadido al búfer de comandos.\nCon esto activado, deberías habilitar el diagnóstico de fallos.\nUtil para programas como RenderDoc. Save Data Path:\nThe folder where game save data will be saved. - Save Data Path:\nThe folder where game save data will be saved. + Ruta de Guardado de Datos:\nLa carpeta donde se guardarán los datos del juego. Browse:\nBrowse for a folder to set as the save data path. - Browse:\nBrowse for a folder to set as the save data path. + Buscar:\nBusque una carpeta para establecer como ruta de datos guardados. Release @@ -2054,11 +2054,11 @@ Set the volume of the background music. - Set the volume of the background music. + Establece el volumen de la música de fondo. Enable Motion Controls - Habilitar controles de movimiento + Habilitar Controles de Movimiento Save Data Path @@ -2070,15 +2070,15 @@ async - async + asíncrono sync - sync + síncrono Auto Select - Selección automática + Selección Automática Directory to install games @@ -2094,7 +2094,7 @@ Display Mode - Modo de imagen + Modo de Imagen Windowed @@ -2102,15 +2102,15 @@ Fullscreen - Pantalla completa + Pantalla Completa Fullscreen (Borderless) - Pantalla completa (sin bordes) + Pantalla Completa (sin bordes) Window Size - Tamaño de ventana + Tamaño de Ventana W: @@ -2122,90 +2122,90 @@ Separate Log Files - Separate Log Files + Archivos de Registro Independientes Separate Log Files:\nWrites a separate logfile for each game. - Separate Log Files:\nWrites a separate logfile for each game. + Archivos de Registro Independientes:\nEscribe un archivo de registro separado para cada juego. Trophy Notification Position - Trophy Notification Position + Posición de Notificación de Trofeos Left - Left + Izquierda Right - Right + Derecha Top - Top + Arriba Bottom - Bottom + Abajo Notification Duration - Notification Duration + Duración de Notificaciones Portable User Folder - Portable User Folder + Carpeta Portable de Usuario Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Crear Carpeta Portable de Usuario a partir de la Carpeta de Usuario Estándar Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Carpeta portable de usuario:\nAlmacena la configuración de shadPS4 y los datos que se aplicarán sólo a la compilación shadPS4 ubicada en la carpeta actual. Reinicia la aplicación después de crear la carpeta portable de usuario para empezar a usarla. Cannot create portable user folder - Cannot create portable user folder + No se pudo crear la carpeta portable de usuario %1 already exists - %1 already exists + %1 ya existe Portable user folder created - Portable user folder created + Carpeta Portable de Usuario Creada %1 successfully created. - %1 successfully created. + %1 creado correctamente. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Abre la carpeta de trofeos/sonidos personalizados:\nPuedes añadir imágenes y un audio personalizados a los trofeos.\nAñade los archivos a custom_trophy con los siguientes nombres:\ntrophy.wav o trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNota: El sonido sólo funcionará en versiones QT. TrophyViewer Trophy Viewer - Vista de trofeos + Expositor de Trofeos Progress - Progress + Progreso Show Earned Trophies - Show Earned Trophies + Mostrar Trofeos Obtenidos Show Not Earned Trophies - Show Not Earned Trophies + Mostrar Trofeos No Obtenidos Show Hidden Trophies - Show Hidden Trophies + Mostrar Trofeos Ocultos diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index e7c48e426..f627ff640 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1734,11 +1734,11 @@ Debug - Feilretting + Feilsøking Enable Debug Dumping - Bruk feilrettingsdumping + Bruk feilsøkingsdumping Enable Vulkan Validation Layers @@ -1950,15 +1950,15 @@ Touchpad Left - Berøringsplate Venstre + Berøringsplate venstre Touchpad Right - Berøringsplate Høyre + Berøringsplate høyre Touchpad Center - Berøringsplate Midt + Berøringsplate midten None @@ -2002,7 +2002,7 @@ Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. - Bruk feilrettingsdumping:\nLagrer import- og eksport-symbolene og filoverskrifts-informasjonen til det nåværende kjørende PS4-programmet i en mappe. + Bruk feilsøkingsdumping:\nLagrer import- og eksport-symbolene og filoverskrifts-informasjonen til det nåværende kjørende PS4-programmet i en mappe. Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. @@ -2058,7 +2058,7 @@ Enable Motion Controls - Bruk bevegelseskontroller + Bruk bevegelsesstyring Save Data Path diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 3d1f1a222..bf58dc60d 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -559,11 +559,11 @@ Restore Defaults - Restore Defaults + Rikthe Paracaktimet Cancel - Cancel + Anulo @@ -1193,19 +1193,19 @@ Save - Save + Ruaj Apply - Apply + Zbato Restore Defaults - Restore Defaults + Rikthe Paracaktimet Cancel - Cancel + Anulo @@ -1606,7 +1606,7 @@ Default tab when opening settings - Skeda e parazgjedhur kur hapen cilësimet + Skeda e paracaktuar kur hapen cilësimet Show Game Size In List @@ -1850,7 +1850,7 @@ Restore Defaults - Rikthe paracaktimet + Rikthe Paracaktimet Close diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index de3781414..d631859d4 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -551,19 +551,19 @@ Save - Save + Spara Apply - Apply + Tillämpa Restore Defaults - Restore Defaults + Återställ till standard Cancel - Cancel + Avbryt @@ -1193,19 +1193,19 @@ Save - Save + Spara Apply - Apply + Tillämpa Restore Defaults - Restore Defaults + Återställ till standard Cancel - Cancel + Avbryt diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 1c074be6a..6083577fa 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -551,19 +551,19 @@ Save - Save + Зберегти Apply - Apply + Застосувати Restore Defaults - Restore Defaults + За замовчуванням Cancel - Cancel + Відмінити @@ -1193,19 +1193,19 @@ Save - Save + Зберегти Apply - Apply + Застосувати Restore Defaults - Restore Defaults + За замовчуванням Cancel - Cancel + Відмінити diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 80b322112..6e1fcdc95 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1105,7 +1105,7 @@ note: click Help Button/Special Keybindings for more information - 注意:点击帮助按钮 -> Special Bindings 获取更多信息 + 注意:点击帮助按钮 -> 获取更多关于映射特殊键位的信息 Face Buttons From 9298b074fc0b1641d263eecbeeb429ec21566acc Mon Sep 17 00:00:00 2001 From: Missake212 Date: Wed, 19 Mar 2025 20:09:29 +0000 Subject: [PATCH 047/194] Changing "submit report" button behavior in the GUI (#2654) * change code to serial * delete emulator version function * Adding the line back, someone else will need to look into it * fix epic fail --- src/qt_gui/gui_context_menus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 24b421b9d..c49fc99b6 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -521,7 +521,7 @@ public: "title", QString("%1 - %2").arg(QString::fromStdString(m_games[itemID].serial), QString::fromStdString(m_games[itemID].name))); query.addQueryItem("game-name", QString::fromStdString(m_games[itemID].name)); - query.addQueryItem("game-code", QString::fromStdString(m_games[itemID].serial)); + query.addQueryItem("game-serial", QString::fromStdString(m_games[itemID].serial)); query.addQueryItem("game-version", QString::fromStdString(m_games[itemID].version)); query.addQueryItem("emulator-version", QString(Common::VERSION)); url.setQuery(query); From 4eb6304076dc77f589ae293e39a8f0400b8565eb Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Wed, 19 Mar 2025 22:18:51 +0100 Subject: [PATCH 048/194] Only display the "Submit a report" button for release builds (#2656) Co-authored-by: Missake212 --- src/qt_gui/gui_context_menus.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index c49fc99b6..d3c51be85 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -116,7 +116,9 @@ public: compatibilityMenu->addAction(updateCompatibility); compatibilityMenu->addAction(viewCompatibilityReport); - compatibilityMenu->addAction(submitCompatibilityReport); + if (Common::isRelease) { + compatibilityMenu->addAction(submitCompatibilityReport); + } menu.addMenu(compatibilityMenu); From 2a05af22e17d3ece3cf04746799c710a60f31334 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Wed, 19 Mar 2025 23:20:00 +0200 Subject: [PATCH 049/194] emit_spirv: Fix comparison type (#2658) --- src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index e2d702389..9f8784797 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -236,7 +236,7 @@ Id EmitFindILsb64(EmitContext& ctx, Id value) { const Id hi{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 1U)}; const Id lo_lsb{ctx.OpFindILsb(ctx.U32[1], lo)}; const Id hi_lsb{ctx.OpFindILsb(ctx.U32[1], hi)}; - const Id found_lo{ctx.OpINotEqual(ctx.U32[1], lo_lsb, ctx.ConstU32(u32(-1)))}; + const Id found_lo{ctx.OpINotEqual(ctx.U1[1], lo_lsb, ctx.ConstU32(u32(-1)))}; return ctx.OpSelect(ctx.U32[1], found_lo, lo_lsb, hi_lsb); } From b1885baddaffd6f7c8f26e42e9754b7c71ce8aed Mon Sep 17 00:00:00 2001 From: Dmugetsu <168934208+diegolix29@users.noreply.github.com> Date: Thu, 20 Mar 2025 14:24:47 -0600 Subject: [PATCH 050/194] clock_gettime fixes for windows (#2659) * Using OrbisKernelTimespec under clock_gettime, orbis_clock_gettime, sceKernelClockGettime to fix compatibility issues. * final fix test * Roamic suggestions --- src/core/libraries/kernel/time.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/core/libraries/kernel/time.cpp b/src/core/libraries/kernel/time.cpp index 42d959885..b7e4c1756 100644 --- a/src/core/libraries/kernel/time.cpp +++ b/src/core/libraries/kernel/time.cpp @@ -172,7 +172,7 @@ static s32 clock_gettime(u32 clock_id, struct timespec* ts) { } #endif -int PS4_SYSV_ABI orbis_clock_gettime(s32 clock_id, struct timespec* ts) { +int PS4_SYSV_ABI orbis_clock_gettime(s32 clock_id, struct OrbisKernelTimespec* ts) { if (ts == nullptr) { return ORBIS_KERNEL_ERROR_EFAULT; } @@ -265,17 +265,18 @@ int PS4_SYSV_ABI orbis_clock_gettime(s32 clock_id, struct timespec* ts) { return EINVAL; } - return clock_gettime(pclock_id, ts); + timespec t{}; + int result = clock_gettime(pclock_id, &t); + ts->tv_sec = t.tv_sec; + ts->tv_nsec = t.tv_nsec; + return result; } int PS4_SYSV_ABI sceKernelClockGettime(s32 clock_id, OrbisKernelTimespec* tp) { - struct timespec ts; - const auto res = orbis_clock_gettime(clock_id, &ts); + const auto res = orbis_clock_gettime(clock_id, tp); if (res < 0) { return ErrnoToSceKernelError(res); } - tp->tv_sec = ts.tv_sec; - tp->tv_nsec = ts.tv_nsec; return ORBIS_OK; } @@ -469,4 +470,4 @@ void RegisterTime(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("-o5uEDpN+oY", "libkernel", 1, "libkernel", 1, 1, sceKernelConvertUtcToLocaltime); } -} // namespace Libraries::Kernel +} // namespace Libraries::Kernel \ No newline at end of file From c19b692a66b9c1ff8c9036c92fae38aff9ff0ee4 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Fri, 21 Mar 2025 04:26:00 +0800 Subject: [PATCH 051/194] Sync text editor changes to KBM GUI (#2660) Co-authored-by: rainmakerv2 <30595646+jpau02@users.noreply.github.com> --- src/qt_gui/kbm_gui.cpp | 13 +++++-------- src/qt_gui/kbm_gui.h | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index c148884e9..8777dda95 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -15,7 +15,6 @@ #include "ui_kbm_gui.h" HelpDialog* HelpWindow; - KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* parent) : QDialog(parent), m_game_info(game_info_get), ui(new Ui::KBMSettings) { @@ -48,6 +47,7 @@ KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* ui->ProfileComboBox->setCurrentText("Common Config"); ui->TitleLabel->setText("Common Config"); + config_id = "default"; connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) { if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { @@ -72,6 +72,7 @@ KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* connect(ui->TextEditorButton, &QPushButton::clicked, this, [this]() { auto kbmWindow = new EditorDialog(this); kbmWindow->exec(); + SetUIValuestoMappings(config_id); }); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] { @@ -84,9 +85,6 @@ KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* connect(ui->ProfileComboBox, &QComboBox::currentTextChanged, this, [this] { GetGameTitle(); - std::string config_id = (ui->ProfileComboBox->currentText() == "Common Config") - ? "default" - : ui->ProfileComboBox->currentText().toStdString(); SetUIValuestoMappings(config_id); }); @@ -385,10 +383,6 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) { lines.push_back(output_string + " = " + input_string); lines.push_back(""); - - std::string config_id = (ui->ProfileComboBox->currentText() == "Common Config") - ? "default" - : ui->ProfileComboBox->currentText().toStdString(); const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); std::fstream file(config_file); int lineCount = 0; @@ -626,6 +620,9 @@ void KBMSettings::GetGameTitle() { } } } + config_id = (ui->ProfileComboBox->currentText() == "Common Config") + ? "default" + : ui->ProfileComboBox->currentText().toStdString(); } void KBMSettings::onHelpClicked() { diff --git a/src/qt_gui/kbm_gui.h b/src/qt_gui/kbm_gui.h index e63d7bd43..06e58eef6 100644 --- a/src/qt_gui/kbm_gui.h +++ b/src/qt_gui/kbm_gui.h @@ -42,7 +42,7 @@ private: QTimer* timer; QPushButton* MappingButton; QList ButtonsList; - + std::string config_id; const std::vector ControllerInputs = { "cross", "circle", "square", "triangle", "l1", "r1", "l2", "r2", "l3", From 2e54afb295035e601e5b7189cda56441f201397d Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 21 Mar 2025 16:12:14 +0200 Subject: [PATCH 052/194] fix debug version for cryptopp (#2664) --- externals/cryptopp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/cryptopp b/externals/cryptopp index ee84a3144..effed0d0b 160000 --- a/externals/cryptopp +++ b/externals/cryptopp @@ -1 +1 @@ -Subproject commit ee84a3144137ac0a1294c0b4a342dca13b66923f +Subproject commit effed0d0b865afc23ed67e0916f83734e4b9b3b7 From 3b2c01272383e1fcd0b82c7873e1ebf1a641aada Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 23 Mar 2025 00:14:04 +0200 Subject: [PATCH 053/194] tagged v0.7.0 --- dist/net.shadps4.shadPS4.metainfo.xml | 3 +++ src/common/version.h | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml index c8c9d5c23..99f9e070d 100644 --- a/dist/net.shadps4.shadPS4.metainfo.xml +++ b/dist/net.shadps4.shadPS4.metainfo.xml @@ -37,6 +37,9 @@ Game + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.7.0 + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.6.0 diff --git a/src/common/version.h b/src/common/version.h index e7f6cc817..e27ed505d 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -8,7 +8,7 @@ namespace Common { -constexpr char VERSION[] = "0.6.1 WIP"; -constexpr bool isRelease = false; +constexpr char VERSION[] = "0.7.0"; +constexpr bool isRelease = true; } // namespace Common From d7b947dd796bf80422340f6a58756ede8d1be2a3 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 23 Mar 2025 00:27:20 +0200 Subject: [PATCH 054/194] starting v0.7.1 WIP --- src/common/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/version.h b/src/common/version.h index e27ed505d..652f36e6d 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -8,7 +8,7 @@ namespace Common { -constexpr char VERSION[] = "0.7.0"; -constexpr bool isRelease = true; +constexpr char VERSION[] = "0.7.1 WIP"; +constexpr bool isRelease = false; } // namespace Common From 0fa1220eca95514812682140221917af1b8e4932 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sat, 22 Mar 2025 19:27:42 -0300 Subject: [PATCH 055/194] Add option to enable/disable game folders individually (#2662) * Add option to enable/disable game folders individually A checkbox (button to select) game folders has been added to the menu, allowing you to Enable/Disable them without having to remove the folder. config.toml is now saved in alphabetical order * ordering is separation in a function * remove my comment in portuguese :) --- src/common/config.cpp | 137 +++++++++++++++++++++++++++++---- src/common/config.h | 14 +++- src/qt_gui/settings_dialog.cpp | 48 +++++++++--- 3 files changed, 170 insertions(+), 29 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 514024c30..0b5fb8be1 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -81,7 +81,7 @@ static std::string trophyKey; // Gui static bool load_game_size = true; -std::vector settings_install_dirs = {}; +static std::vector settings_install_dirs = {}; std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path save_data_path = {}; u32 main_window_geometry_x = 400; @@ -519,22 +519,34 @@ void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { main_window_geometry_h = h; } -bool addGameInstallDir(const std::filesystem::path& dir) { - if (std::find(settings_install_dirs.begin(), settings_install_dirs.end(), dir) == - settings_install_dirs.end()) { - settings_install_dirs.push_back(dir); - return true; +bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) { + for (const auto& install_dir : settings_install_dirs) { + if (install_dir.path == dir) { + return false; + } } - return false; + settings_install_dirs.push_back({dir, enabled}); + return true; } void removeGameInstallDir(const std::filesystem::path& dir) { - auto iterator = std::find(settings_install_dirs.begin(), settings_install_dirs.end(), dir); + auto iterator = + std::find_if(settings_install_dirs.begin(), settings_install_dirs.end(), + [&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; }); if (iterator != settings_install_dirs.end()) { settings_install_dirs.erase(iterator); } } +void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled) { + auto iterator = + std::find_if(settings_install_dirs.begin(), settings_install_dirs.end(), + [&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; }); + if (iterator != settings_install_dirs.end()) { + iterator->enabled = enabled; + } +} + void setAddonInstallDir(const std::filesystem::path& dir) { settings_addon_install_dir = dir; } @@ -590,8 +602,15 @@ void setEmulatorLanguage(std::string language) { emulator_language = language; } -void setGameInstallDirs(const std::vector& settings_install_dirs_config) { - settings_install_dirs = settings_install_dirs_config; +void setGameInstallDirs(const std::vector& dirs_config) { + settings_install_dirs.clear(); + for (const auto& dir : dirs_config) { + settings_install_dirs.push_back({dir, true}); + } +} + +void setAllGameInstallDirs(const std::vector& dirs_config) { + settings_install_dirs = dirs_config; } void setSaveDataPath(const std::filesystem::path& path) { @@ -614,7 +633,17 @@ u32 getMainWindowGeometryH() { return main_window_geometry_h; } -const std::vector& getGameInstallDirs() { +const std::vector getGameInstallDirs() { + std::vector enabled_dirs; + for (const auto& dir : settings_install_dirs) { + if (dir.enabled) { + enabled_dirs.push_back(dir.path); + } + } + return enabled_dirs; +} + +const std::vector& getAllGameInstallDirs() { return settings_install_dirs; } @@ -809,8 +838,23 @@ void load(const std::filesystem::path& path) { const auto install_dir_array = toml::find_or>(gui, "installDirs", {}); - for (const auto& dir : install_dir_array) { - addGameInstallDir(std::filesystem::path{dir}); + + std::vector install_dirs_enabled; + try { + install_dirs_enabled = toml::find>(gui, "installDirsEnabled"); + } catch (...) { + // If it does not exist, assume that all are enabled. + install_dirs_enabled.resize(install_dir_array.size(), true); + } + + if (install_dirs_enabled.size() < install_dir_array.size()) { + install_dirs_enabled.resize(install_dir_array.size(), true); + } + + settings_install_dirs.clear(); + for (size_t i = 0; i < install_dir_array.size(); i++) { + settings_install_dirs.push_back( + {std::filesystem::path{install_dir_array[i]}, install_dirs_enabled[i]}); } save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {}); @@ -853,6 +897,37 @@ void load(const std::filesystem::path& path) { } } +void sortTomlSections(toml::ordered_value& data) { + toml::ordered_value ordered_data; + std::vector section_order = {"General", "Input", "GPU", "Vulkan", + "Debug", "Keys", "GUI", "Settings"}; + + for (const auto& section : section_order) { + if (data.contains(section)) { + std::vector keys; + for (const auto& item : data.at(section).as_table()) { + keys.push_back(item.first); + } + + std::sort(keys.begin(), keys.end(), [](const std::string& a, const std::string& b) { + return std::lexicographical_compare( + a.begin(), a.end(), b.begin(), b.end(), [](char a_char, char b_char) { + return std::tolower(a_char) < std::tolower(b_char); + }); + }); + + toml::ordered_value ordered_section; + for (const auto& key : keys) { + ordered_section[key] = data.at(section).at(key); + } + + ordered_data[section] = ordered_section; + } + } + + data = ordered_data; +} + void save(const std::filesystem::path& path) { toml::ordered_value data; @@ -922,14 +997,37 @@ void save(const std::filesystem::path& path) { data["Debug"]["CollectShader"] = isShaderDebug; data["Debug"]["isSeparateLogFilesEnabled"] = isSeparateLogFilesEnabled; data["Debug"]["FPSColor"] = isFpsColor; - data["Keys"]["TrophyKey"] = trophyKey; std::vector install_dirs; - for (const auto& dirString : settings_install_dirs) { - install_dirs.emplace_back(std::string{fmt::UTF(dirString.u8string()).data}); + std::vector install_dirs_enabled; + + // temporary structure for ordering + struct DirEntry { + std::string path_str; + bool enabled; + }; + + std::vector sorted_dirs; + for (const auto& dirInfo : settings_install_dirs) { + sorted_dirs.push_back( + {std::string{fmt::UTF(dirInfo.path.u8string()).data}, dirInfo.enabled}); } + + // Sort directories alphabetically + std::sort(sorted_dirs.begin(), sorted_dirs.end(), [](const DirEntry& a, const DirEntry& b) { + return std::lexicographical_compare( + a.path_str.begin(), a.path_str.end(), b.path_str.begin(), b.path_str.end(), + [](char a_char, char b_char) { return std::tolower(a_char) < std::tolower(b_char); }); + }); + + for (const auto& entry : sorted_dirs) { + install_dirs.push_back(entry.path_str); + install_dirs_enabled.push_back(entry.enabled); + } + data["GUI"]["installDirs"] = install_dirs; + data["GUI"]["installDirsEnabled"] = install_dirs_enabled; data["GUI"]["saveDataPath"] = std::string{fmt::UTF(save_data_path.u8string()).data}; data["GUI"]["loadGameSizeEnabled"] = load_game_size; @@ -940,9 +1038,13 @@ void save(const std::filesystem::path& path) { data["GUI"]["showBackgroundImage"] = showBackgroundImage; data["Settings"]["consoleLanguage"] = m_language; + // Sorting of TOML sections + sortTomlSections(data); + std::ofstream file(path, std::ios::binary); file << data; file.close(); + saveMainWindow(path); } @@ -984,6 +1086,9 @@ void saveMainWindow(const std::filesystem::path& path) { data["GUI"]["elfDirs"] = m_elf_viewer; data["GUI"]["recentFiles"] = m_recent_files; + // Sorting of TOML sections + sortTomlSections(data); + std::ofstream file(path, std::ios::binary); file << data; file.close(); diff --git a/src/common/config.h b/src/common/config.h index 82d65d30e..e495be52a 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -9,6 +9,11 @@ namespace Config { +struct GameInstallDir { + std::filesystem::path path; + bool enabled; +}; + enum HideCursorState : s16 { Never, Idle, Always }; void load(const std::filesystem::path& path); @@ -98,7 +103,8 @@ void setUserName(const std::string& type); void setUpdateChannel(const std::string& type); void setChooseHomeTab(const std::string& type); void setSeparateUpdateEnabled(bool use); -void setGameInstallDirs(const std::vector& settings_install_dirs_config); +void setGameInstallDirs(const std::vector& dirs_config); +void setAllGameInstallDirs(const std::vector& dirs_config); void setSaveDataPath(const std::filesystem::path& path); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); @@ -133,8 +139,9 @@ void setVkGuestMarkersEnabled(bool enable); // Gui void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); -bool addGameInstallDir(const std::filesystem::path& dir); +bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true); void removeGameInstallDir(const std::filesystem::path& dir); +void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled); void setAddonInstallDir(const std::filesystem::path& dir); void setMainWindowTheme(u32 theme); void setIconSize(u32 size); @@ -153,7 +160,8 @@ u32 getMainWindowGeometryX(); u32 getMainWindowGeometryY(); u32 getMainWindowGeometryW(); u32 getMainWindowGeometryH(); -const std::vector& getGameInstallDirs(); +const std::vector getGameInstallDirs(); +const std::vector& getAllGameInstallDirs(); std::filesystem::path getAddonInstallDir(); u32 getMainWindowTheme(); u32 getIconSize(); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 41caccec9..a8beef8da 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -246,12 +246,13 @@ SettingsDialog::SettingsDialog(std::span physical_devices, // PATH TAB { connect(ui->addFolderButton, &QPushButton::clicked, this, [this]() { - const auto config_dir = Config::getGameInstallDirs(); QString file_path_string = QFileDialog::getExistingDirectory(this, tr("Directory to install games")); auto file_path = Common::FS::PathFromQString(file_path_string); - if (!file_path.empty() && Config::addGameInstallDir(file_path)) { + if (!file_path.empty() && Config::addGameInstallDir(file_path, true)) { QListWidgetItem* item = new QListWidgetItem(file_path_string); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Checked); ui->gameFoldersListWidget->addItem(item); } }); @@ -783,6 +784,17 @@ void SettingsDialog::UpdateSettings() { emit BackgroundOpacityChanged(ui->backgroundImageOpacitySlider->value()); Config::setShowBackgroundImage(ui->showBackgroundImageCheckBox->isChecked()); + std::vector dirs_with_states; + for (int i = 0; i < ui->gameFoldersListWidget->count(); i++) { + QListWidgetItem* item = ui->gameFoldersListWidget->item(i); + QString path_string = item->text(); + auto path = Common::FS::PathFromQString(path_string); + bool enabled = (item->checkState() == Qt::Checked); + + dirs_with_states.push_back({path, enabled}); + } + Config::setAllGameInstallDirs(dirs_with_states); + #ifdef ENABLE_DISCORD_RPC auto* rpc = Common::Singleton::Instance(); if (Config::getEnableDiscordRPC()) { @@ -797,6 +809,7 @@ void SettingsDialog::UpdateSettings() { } void SettingsDialog::ResetInstallFolders() { + ui->gameFoldersListWidget->clear(); std::filesystem::path userdir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); const toml::value data = toml::parse(userdir / "config.toml"); @@ -805,21 +818,36 @@ void SettingsDialog::ResetInstallFolders() { const toml::value& gui = data.at("GUI"); const auto install_dir_array = toml::find_or>(gui, "installDirs", {}); - std::vector settings_install_dirs_config = {}; - for (const auto& dir : install_dir_array) { - if (std::find(settings_install_dirs_config.begin(), settings_install_dirs_config.end(), - dir) == settings_install_dirs_config.end()) { - settings_install_dirs_config.push_back(dir); - } + std::vector install_dirs_enabled; + try { + install_dirs_enabled = toml::find>(gui, "installDirsEnabled"); + } catch (...) { + // If it does not exist, assume that all are enabled. + install_dirs_enabled.resize(install_dir_array.size(), true); } - for (const auto& dir : settings_install_dirs_config) { + if (install_dirs_enabled.size() < install_dir_array.size()) { + install_dirs_enabled.resize(install_dir_array.size(), true); + } + + std::vector settings_install_dirs_config; + + for (size_t i = 0; i < install_dir_array.size(); i++) { + std::filesystem::path dir = install_dir_array[i]; + bool enabled = install_dirs_enabled[i]; + + settings_install_dirs_config.push_back({dir, enabled}); + QString path_string; Common::FS::PathToQString(path_string, dir); + QListWidgetItem* item = new QListWidgetItem(path_string); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked); ui->gameFoldersListWidget->addItem(item); } - Config::setGameInstallDirs(settings_install_dirs_config); + + Config::setAllGameInstallDirs(settings_install_dirs_config); } } From a80c4a7f482efd5278daa08c374e78149226942c Mon Sep 17 00:00:00 2001 From: Pavel <68122101+red-prig@users.noreply.github.com> Date: Sun, 23 Mar 2025 01:27:57 +0300 Subject: [PATCH 056/194] Reset previous buffer label instead of current one (#2663) * Reset previous buffer label * Reset flip label also when registering buffer --- src/core/libraries/videoout/driver.cpp | 13 ++++++++++--- src/core/libraries/videoout/driver.h | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index d2c980882..8f725e549 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -63,6 +63,7 @@ void VideoOutDriver::Close(s32 handle) { main_port.is_open = false; main_port.flip_rate = 0; + main_port.prev_index = -1; ASSERT(main_port.flip_events.empty()); } @@ -133,6 +134,10 @@ int VideoOutDriver::RegisterBuffers(VideoOutPort* port, s32 startIndex, void* co .address_right = 0, }; + // Reset flip label also when registering buffer + port->buffer_labels[startIndex + i] = 0; + port->SignalVoLabel(); + presenter->RegisterVideoOutSurface(group, address); LOG_INFO(Lib_VideoOut, "buffers[{}] = {:#x}", i + startIndex, address); } @@ -190,11 +195,13 @@ void VideoOutDriver::Flip(const Request& req) { } } - // Reset flip label - if (req.index != -1) { - port->buffer_labels[req.index] = 0; + // Reset prev flip label + if (port->prev_index != -1) { + port->buffer_labels[port->prev_index] = 0; port->SignalVoLabel(); } + // save to prev buf index + port->prev_index = req.index; } void VideoOutDriver::DrawBlankFrame() { diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index e57b189b5..3b0df43b9 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -32,6 +32,7 @@ struct VideoOutPort { std::condition_variable vo_cv; std::condition_variable vblank_cv; int flip_rate = 0; + int prev_index = -1; bool is_open = false; bool is_mode_changing = false; // Used to prevent flip during mode change From 1f9ac53c28a12f2ebd33f0b080c342e34a4ed110 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Sun, 23 Mar 2025 00:35:42 +0200 Subject: [PATCH 057/194] shader_recompiler: Improve divergence handling and readlane elimintation (#2667) * control_flow_graph: Improve divergence handling * recompiler: Simplify optimization passes Removes a redudant constant propagation and cleans up the passes a little * ir_passes: Add new readlane elimination pass The algorithm has grown complex enough where it deserves its own pass. The old implementation could only handle a single phi level properly, however this one should be able to eliminate vast majority of lane cases remaining. It first performs a traversal of the phi tree to ensure that all phi sources can be rewritten into an expected value and then performs elimintation by recursively duplicating the phi nodes at each step, in order to preserve control flow. * clang format * control_flow_graph: Remove debug code --- CMakeLists.txt | 2 +- .../frontend/control_flow_graph.cpp | 151 ++++++++++-------- .../frontend/control_flow_graph.h | 2 +- .../ir/passes/constant_propagation_pass.cpp | 50 ------ .../ir/passes/hull_shader_transform.cpp | 4 + src/shader_recompiler/ir/passes/ir_passes.h | 4 +- .../ir/passes/readlane_elimination_pass.cpp | 115 +++++++++++++ .../ir/passes/ring_access_elimination.cpp | 5 +- src/shader_recompiler/recompiler.cpp | 17 +- 9 files changed, 211 insertions(+), 139 deletions(-) create mode 100644 src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f05587d38..185205221 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -771,6 +771,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h src/shader_recompiler/ir/passes/identity_removal_pass.cpp src/shader_recompiler/ir/passes/ir_passes.h src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp + src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp src/shader_recompiler/ir/passes/resource_tracking_pass.cpp src/shader_recompiler/ir/passes/ring_access_elimination.cpp src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -1121,7 +1122,6 @@ cmrc_add_resource_library(embedded-resources src/images/gold.png src/images/platinum.png src/images/silver.png) - target_link_libraries(shadps4 PRIVATE res::embedded) # ImGui resources diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index 126cb4eb6..cf1882b8c 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/assert.h" #include "shader_recompiler/frontend/control_flow_graph.h" @@ -39,9 +40,6 @@ static IR::Condition MakeCondition(const GcnInst& inst) { return IR::Condition::Execz; case Opcode::S_CBRANCH_EXECNZ: return IR::Condition::Execnz; - case Opcode::S_AND_SAVEEXEC_B64: - case Opcode::S_ANDN2_B64: - return IR::Condition::Execnz; default: return IR::Condition::True; } @@ -76,9 +74,9 @@ CFG::CFG(Common::ObjectPool& block_pool_, std::span inst_l index_to_pc.resize(inst_list.size() + 1); labels.reserve(LabelReserveSize); EmitLabels(); - EmitDivergenceLabels(); EmitBlocks(); LinkBlocks(); + SplitDivergenceScopes(); } void CFG::EmitLabels() { @@ -112,7 +110,7 @@ void CFG::EmitLabels() { std::ranges::sort(labels); } -void CFG::EmitDivergenceLabels() { +void CFG::SplitDivergenceScopes() { const auto is_open_scope = [](const GcnInst& inst) { // An open scope instruction is an instruction that modifies EXEC // but also saves the previous value to restore later. This indicates @@ -136,64 +134,97 @@ void CFG::EmitDivergenceLabels() { (inst.opcode == Opcode::S_ANDN2_B64 && inst.dst[0].field == OperandField::ExecLo); }; - // Since we will be adding new labels, avoid iterating those as well. - const size_t end_size = labels.size(); - for (u32 l = 0; l < end_size; l++) { - const Label start = labels[l]; - // Stop if we reached end of existing labels. - if (l == end_size - 1) { - break; - } - const Label end = labels[l + 1]; - const size_t end_index = GetIndex(end); - + for (auto blk = blocks.begin(); blk != blocks.end(); blk++) { + auto next_blk = std::next(blk); s32 curr_begin = -1; - s32 last_exec_idx = -1; - for (size_t index = GetIndex(start); index < end_index; index++) { + for (size_t index = blk->begin_index; index <= blk->end_index; index++) { const auto& inst = inst_list[index]; - 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. + const bool is_close = is_close_scope(inst); + if ((is_close || index == blk->end_index) && curr_begin != -1) { + // If there are no instructions inside scope don't do anything. + if (index - curr_begin == 1) { 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 first instruction affected by the exec mask. + do { + ++curr_begin; + } while (IgnoresExecMask(inst_list[curr_begin])); + + // Determine the last instruction affected by the exec mask. + s32 curr_end = index; + while (IgnoresExecMask(inst_list[curr_end])) { + --curr_end; + } + + // Create a new block for the divergence scope. + Block* block = block_pool.Create(); + block->begin = index_to_pc[curr_begin]; + block->end = index_to_pc[curr_end]; + block->begin_index = curr_begin; + block->end_index = curr_end; + block->end_inst = inst_list[curr_end]; + blocks.insert_before(next_blk, *block); + + // If we are inside the parent block, make an epilogue block and jump to it. + if (curr_end != blk->end_index) { + Block* epi_block = block_pool.Create(); + epi_block->begin = index_to_pc[curr_end + 1]; + epi_block->end = blk->end; + epi_block->begin_index = curr_end + 1; + epi_block->end_index = blk->end_index; + epi_block->end_inst = blk->end_inst; + epi_block->cond = blk->cond; + epi_block->end_class = blk->end_class; + epi_block->branch_true = blk->branch_true; + epi_block->branch_false = blk->branch_false; + blocks.insert_before(next_blk, *epi_block); + + // Have divergence block always jump to epilogue block. + block->cond = IR::Condition::True; + block->branch_true = epi_block; + block->branch_false = nullptr; + + // If the parent block fails to enter divergence block make it jump to + // epilogue too + blk->branch_false = epi_block; + } else { + // No epilogue block is needed since the divergence block + // also ends the parent block. Inherit the end condition. + auto& parent_blk = *blk; + ASSERT(blk->cond == IR::Condition::True && blk->branch_true); + block->cond = IR::Condition::True; + block->branch_true = blk->branch_true; + block->branch_false = nullptr; + + // If the parent block didn't enter the divergence scope + // have it jump directly to the next one + blk->branch_false = blk->branch_true; + } + + // Shrink parent block to end right before curr_begin + // and make it jump to divergence block + --curr_begin; + blk->end = index_to_pc[curr_begin]; + blk->end_index = curr_begin; + blk->end_inst = inst_list[curr_begin]; + blk->cond = IR::Condition::Execnz; + blk->end_class = EndClass::Branch; + blk->branch_true = block; + } + // 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; } } } - - // Sort labels to make sure block insertion is correct. - std::ranges::sort(labels); } void CFG::EmitBlocks() { @@ -234,22 +265,6 @@ void CFG::LinkBlocks() { for (auto it = blocks.begin(); it != blocks.end(); it++) { auto& block = *it; const auto end_inst{block.end_inst}; - // Handle divergence block inserted here. - if (end_inst.opcode == Opcode::S_AND_SAVEEXEC_B64 || - end_inst.opcode == Opcode::S_ANDN2_B64 || end_inst.IsCmpx()) { - // Blocks are stored ordered by address in the set - auto next_it = std::next(it); - auto* target_block = &(*next_it); - ++target_block->num_predecessors; - block.branch_true = target_block; - - auto merge_it = std::next(next_it); - auto* merge_block = &(*merge_it); - ++merge_block->num_predecessors; - block.branch_false = merge_block; - block.end_class = EndClass::Branch; - continue; - } // If the block doesn't end with a branch we simply // need to link with the next block. diff --git a/src/shader_recompiler/frontend/control_flow_graph.h b/src/shader_recompiler/frontend/control_flow_graph.h index d98d4b05d..0acce3306 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.h +++ b/src/shader_recompiler/frontend/control_flow_graph.h @@ -57,9 +57,9 @@ public: private: void EmitLabels(); - void EmitDivergenceLabels(); void EmitBlocks(); void LinkBlocks(); + void SplitDivergenceScopes(); void AddLabel(Label address) { const auto it = std::ranges::find(labels, address); diff --git a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp index c8a4b13cb..5c66b1115 100644 --- a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp @@ -251,54 +251,6 @@ void FoldCmpClass(IR::Block& block, IR::Inst& inst) { } } -void FoldReadLane(IR::Block& block, IR::Inst& inst) { - const u32 lane = inst.Arg(1).U32(); - IR::Inst* prod = inst.Arg(0).InstRecursive(); - - const auto search_chain = [lane](const IR::Inst* prod) -> IR::Value { - while (prod->GetOpcode() == IR::Opcode::WriteLane) { - if (prod->Arg(2).U32() == lane) { - return prod->Arg(1); - } - prod = prod->Arg(0).InstRecursive(); - } - return {}; - }; - - if (prod->GetOpcode() == IR::Opcode::WriteLane) { - if (const IR::Value value = search_chain(prod); !value.IsEmpty()) { - inst.ReplaceUsesWith(value); - } - return; - } - - if (prod->GetOpcode() == IR::Opcode::Phi) { - boost::container::small_vector phi_args; - for (size_t arg_index = 0; arg_index < prod->NumArgs(); ++arg_index) { - const IR::Inst* arg{prod->Arg(arg_index).InstRecursive()}; - if (arg->GetOpcode() != IR::Opcode::WriteLane) { - return; - } - const IR::Value value = search_chain(arg); - if (value.IsEmpty()) { - continue; - } - phi_args.emplace_back(value); - } - if (std::ranges::all_of(phi_args, [&](IR::Value value) { return value == phi_args[0]; })) { - inst.ReplaceUsesWith(phi_args[0]); - return; - } - const auto insert_point = IR::Block::InstructionList::s_iterator_to(*prod); - IR::Inst* const new_phi{&*block.PrependNewInst(insert_point, IR::Opcode::Phi)}; - new_phi->SetFlags(IR::Type::U32); - for (size_t arg_index = 0; arg_index < phi_args.size(); arg_index++) { - new_phi->AddPhiOperand(prod->PhiBlock(arg_index), phi_args[arg_index]); - } - inst.ReplaceUsesWith(IR::Value{new_phi}); - } -} - void ConstantPropagation(IR::Block& block, IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::IAdd32: @@ -408,8 +360,6 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { case IR::Opcode::SelectF32: case IR::Opcode::SelectF64: return FoldSelect(inst); - case IR::Opcode::ReadLane: - return FoldReadLane(block, inst); case IR::Opcode::FPNeg32: FoldWhenAllImmediates(inst, [](f32 a) { return -a; }); return; diff --git a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp index 48727e32a..5cf8a1525 100644 --- a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp +++ b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp @@ -1,11 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later + #include "common/assert.h" #include "shader_recompiler/info.h" #include "shader_recompiler/ir/attribute.h" #include "shader_recompiler/ir/breadth_first_search.h" #include "shader_recompiler/ir/ir_emitter.h" #include "shader_recompiler/ir/opcodes.h" +#include "shader_recompiler/ir/passes/ir_passes.h" #include "shader_recompiler/ir/pattern_matching.h" #include "shader_recompiler/ir/program.h" #include "shader_recompiler/runtime_info.h" @@ -734,6 +736,8 @@ void TessellationPreprocess(IR::Program& program, RuntimeInfo& runtime_info) { } } } + + ConstantPropagationPass(program.post_order_blocks); } } // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir/passes/ir_passes.h b/src/shader_recompiler/ir/passes/ir_passes.h index 69628dbfd..760dbb112 100644 --- a/src/shader_recompiler/ir/passes/ir_passes.h +++ b/src/shader_recompiler/ir/passes/ir_passes.h @@ -17,11 +17,11 @@ void IdentityRemovalPass(IR::BlockList& program); void DeadCodeEliminationPass(IR::Program& program); void ConstantPropagationPass(IR::BlockList& program); void FlattenExtendedUserdataPass(IR::Program& program); +void ReadLaneEliminationPass(IR::Program& program); void ResourceTrackingPass(IR::Program& program); void CollectShaderInfoPass(IR::Program& program); void LowerBufferFormatToRaw(IR::Program& program); -void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info, - Stage stage); +void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info); void TessellationPreprocess(IR::Program& program, RuntimeInfo& runtime_info); void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info); void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info); diff --git a/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp b/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp new file mode 100644 index 000000000..fbe382d41 --- /dev/null +++ b/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "shader_recompiler/ir/program.h" + +namespace Shader::Optimization { + +static IR::Inst* SearchChain(IR::Inst* inst, u32 lane) { + while (inst->GetOpcode() == IR::Opcode::WriteLane) { + if (inst->Arg(2).U32() == lane) { + // We found a possible write lane source, return it. + return inst; + } + inst = inst->Arg(0).InstRecursive(); + } + return inst; +} + +static bool IsPossibleToEliminate(IR::Inst* inst, u32 lane) { + // Breadth-first search visiting the right most arguments first + boost::container::small_vector visited; + std::queue queue; + queue.push(inst); + + while (!queue.empty()) { + // Pop one instruction from the queue + IR::Inst* inst{queue.front()}; + queue.pop(); + + // If it's a WriteLane search for possible candidates + if (inst = SearchChain(inst, lane); inst->GetOpcode() == IR::Opcode::WriteLane) { + // We found a possible write lane source, stop looking here. + continue; + } + // If there are other instructions in-between that use the value we can't eliminate. + if (inst->GetOpcode() != IR::Opcode::ReadLane && inst->GetOpcode() != IR::Opcode::Phi) { + return false; + } + // Visit the right most arguments first + for (size_t arg = inst->NumArgs(); arg--;) { + auto arg_value{inst->Arg(arg)}; + if (arg_value.IsImmediate()) { + continue; + } + // Queue instruction if it hasn't been visited + IR::Inst* arg_inst{arg_value.InstRecursive()}; + if (std::ranges::find(visited, arg_inst) == visited.end()) { + visited.push_back(arg_inst); + queue.push(arg_inst); + } + } + } + return true; +} + +using PhiMap = std::unordered_map; + +static IR::Value GetRealValue(PhiMap& phi_map, IR::Inst* inst, u32 lane) { + // If this is a WriteLane op search the chain for a possible candidate. + if (inst = SearchChain(inst, lane); inst->GetOpcode() == IR::Opcode::WriteLane) { + return inst->Arg(1); + } + + // If this is a phi, duplicate it and populate its arguments with real values. + if (inst->GetOpcode() == IR::Opcode::Phi) { + // We are in a phi cycle, use the already duplicated phi. + const auto [it, is_new_phi] = phi_map.try_emplace(inst); + if (!is_new_phi) { + return IR::Value{it->second}; + } + + // Create new phi and insert it right before the old one. + const auto insert_point = IR::Block::InstructionList::s_iterator_to(*inst); + IR::Block* block = inst->GetParent(); + IR::Inst* new_phi{&*block->PrependNewInst(insert_point, IR::Opcode::Phi)}; + new_phi->SetFlags(IR::Type::U32); + it->second = new_phi; + + // Gather all arguments. + for (size_t arg_index = 0; arg_index < inst->NumArgs(); arg_index++) { + IR::Inst* arg_prod = inst->Arg(arg_index).InstRecursive(); + const IR::Value arg = GetRealValue(phi_map, arg_prod, lane); + new_phi->AddPhiOperand(inst->PhiBlock(arg_index), arg); + } + return IR::Value{new_phi}; + } + UNREACHABLE(); +} + +void ReadLaneEliminationPass(IR::Program& program) { + PhiMap phi_map; + for (IR::Block* const block : program.blocks) { + for (IR::Inst& inst : block->Instructions()) { + if (inst.GetOpcode() != IR::Opcode::ReadLane) { + continue; + } + const u32 lane = inst.Arg(1).U32(); + IR::Inst* prod = inst.Arg(0).InstRecursive(); + + // Check simple case of no control flow and phis + if (prod = SearchChain(prod, lane); prod->GetOpcode() == IR::Opcode::WriteLane) { + inst.ReplaceUsesWith(prod->Arg(1)); + continue; + } + + // Traverse the phi tree to see if it's possible to eliminate + if (prod->GetOpcode() == IR::Opcode::Phi && IsPossibleToEliminate(prod, lane)) { + inst.ReplaceUsesWith(GetRealValue(phi_map, prod, lane)); + phi_map.clear(); + } + } + } +} + +} // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp index d6f1efb12..071b94ac0 100644 --- a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp +++ b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp @@ -11,8 +11,7 @@ namespace Shader::Optimization { -void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info, - Stage stage) { +void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info) { auto& info = program.info; const auto& ForEachInstruction = [&](auto func) { @@ -24,7 +23,7 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim } }; - switch (stage) { + switch (program.info.stage) { case Stage::Local: { ForEachInstruction([=](IR::IREmitter& ir, IR::Inst& inst) { const auto opcode = inst.GetOpcode(); diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index 1c132ebbb..5004e0beb 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -1,9 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "common/config.h" -#include "common/io_file.h" -#include "common/path_util.h" #include "shader_recompiler/frontend/control_flow_graph.h" #include "shader_recompiler/frontend/decode.h" #include "shader_recompiler/frontend/structured_control_flow.h" @@ -63,26 +60,18 @@ IR::Program TranslateProgram(std::span code, Pools& pools, Info& info program.post_order_blocks = Shader::IR::PostOrder(program.syntax_list.front()); // Run optimization passes - const auto stage = program.info.stage; - Shader::Optimization::SsaRewritePass(program.post_order_blocks); + Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); Shader::Optimization::IdentityRemovalPass(program.blocks); if (info.l_stage == LogicalStage::TessellationControl) { - // Tess passes require previous const prop passes for now (for simplicity). TODO allow - // fine grained folding or opportunistic folding we set an operand to an immediate - Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); Shader::Optimization::TessellationPreprocess(program, runtime_info); - Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); Shader::Optimization::HullShaderTransform(program, runtime_info); } else if (info.l_stage == LogicalStage::TessellationEval) { - Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); Shader::Optimization::TessellationPreprocess(program, runtime_info); - Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); Shader::Optimization::DomainShaderTransform(program, runtime_info); } - Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); - Shader::Optimization::RingAccessElimination(program, runtime_info, stage); - Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); + Shader::Optimization::RingAccessElimination(program, runtime_info); + Shader::Optimization::ReadLaneEliminationPass(program); Shader::Optimization::FlattenExtendedUserdataPass(program); Shader::Optimization::ResourceTrackingPass(program); Shader::Optimization::LowerBufferFormatToRaw(program); From 6f944ab1173eb478f7d83db3c637badef2289aaf Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Sun, 23 Mar 2025 22:39:15 +0800 Subject: [PATCH 058/194] Fix spacing for translated KBM GUI (#2670) --- src/qt_gui/kbm_gui.ui | 290 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 262 insertions(+), 28 deletions(-) diff --git a/src/qt_gui/kbm_gui.ui b/src/qt_gui/kbm_gui.ui index 0eac88105..c8d63cd00 100644 --- a/src/qt_gui/kbm_gui.ui +++ b/src/qt_gui/kbm_gui.ui @@ -11,10 +11,16 @@ 0 0 - 1193 - 754 + 1234 + 796 + + + 0 + 0 + + Qt::FocusPolicy::StrongFocus @@ -38,8 +44,8 @@ 0 0 - 1173 - 704 + 1214 + 746 @@ -47,8 +53,8 @@ 0 0 - 1171 - 703 + 1211 + 741 @@ -63,7 +69,7 @@ true - + 0 0 @@ -149,6 +155,12 @@ + + + 160 + 0 + + Left @@ -180,6 +192,12 @@ + + + 160 + 0 + + Right @@ -282,11 +300,17 @@ - + 0 0 + + + 344 + 16777215 + + Left Analog Halfmode @@ -319,7 +343,7 @@ - + 0 0 @@ -405,6 +429,12 @@ + + + 160 + 0 + + Left @@ -436,6 +466,12 @@ + + + 160 + 0 + + 179 @@ -528,18 +564,24 @@ - + 0 - + 0 0 + + + 0 + 0 + + 12 @@ -652,6 +694,18 @@ + + + 0 + 0 + + + + + 160 + 0 + + L1 @@ -670,6 +724,12 @@ + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -683,6 +743,12 @@ + + + 0 + 0 + + 160 @@ -707,6 +773,12 @@ + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -724,6 +796,18 @@ + + + 0 + 0 + + + + + 0 + 0 + + true @@ -735,6 +819,12 @@ + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -745,6 +835,12 @@ + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -762,6 +858,12 @@ + + + 0 + 0 + + 160 @@ -786,6 +888,12 @@ + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -799,6 +907,18 @@ + + + 0 + 0 + + + + + 160 + 0 + + R2 @@ -817,6 +937,12 @@ + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -857,7 +983,7 @@ - 420 + 500 200 @@ -887,6 +1013,12 @@ + + + 0 + 0 + + 160 @@ -911,6 +1043,12 @@ + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -924,12 +1062,30 @@ + + + 0 + 0 + + + + + 160 + 0 + + Touchpad Click + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -947,6 +1103,18 @@ + + + 0 + 0 + + + + + 0 + 0 + + Mouse to Joystick @@ -982,6 +1150,12 @@ + + + 0 + 0 + + 160 @@ -1006,6 +1180,12 @@ + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -1019,9 +1199,15 @@ + + + 0 + 0 + + - 145 + 160 0 @@ -1043,6 +1229,12 @@ + + + 0 + 0 + + Qt::FocusPolicy::NoFocus @@ -1064,6 +1256,12 @@ + + + 0 + 0 + + false @@ -1196,6 +1394,25 @@ + + + + + 0 + 0 + + + + + true + false + + + + note: click Help Button/Special Keybindings for more information + + +
@@ -1203,19 +1420,6 @@ - - - - - true - false - - - - note: click Help Button/Special Keybindings for more information - - - @@ -1226,7 +1430,7 @@ - + 0 0 @@ -1306,6 +1510,12 @@ + + + 160 + 0 + + Square @@ -1337,6 +1547,12 @@ + + + 160 + 0 + + Circle @@ -1444,6 +1660,12 @@ 0 + + + 344 + 16777215 + + Right Analog Halfmode @@ -1476,7 +1698,7 @@ - + 0 0 @@ -1568,6 +1790,12 @@ + + + 160 + 0 + + Left @@ -1599,6 +1827,12 @@ + + + 160 + 0 + + Right From 99332e4ec281dc6541948cd94c8f7dad83dacee4 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sun, 23 Mar 2025 22:24:49 +0100 Subject: [PATCH 059/194] Handle "-patch" as the suffix for game update folders (#2674) * Handle "-patch" as the suffix for game update folders * clang * clang 2 --- src/core/file_format/pkg.cpp | 2 +- src/core/file_sys/fs.cpp | 4 ++++ src/emulator.cpp | 2 +- src/qt_gui/game_info.cpp | 2 +- src/qt_gui/game_info.h | 6 ++++++ src/qt_gui/gui_context_menus.h | 23 +++++++++++++++++++++-- src/qt_gui/kbm_config_dialog.cpp | 3 ++- src/qt_gui/main_window.cpp | 6 +++--- 8 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/core/file_format/pkg.cpp b/src/core/file_format/pkg.cpp index a6b5eb9a8..ecc5f10a4 100644 --- a/src/core/file_format/pkg.cpp +++ b/src/core/file_format/pkg.cpp @@ -350,7 +350,7 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem:: auto title_id = GetTitleID(); if (parent_path.filename() != title_id && - !fmt::UTF(extract_path.u8string()).data.ends_with("-UPDATE")) { + !fmt::UTF(extract_path.u8string()).data.ends_with("-patch")) { extractPaths[ndinode_counter] = parent_path / title_id; } else { // DLCs path has different structure diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index ec940503f..4dad44874 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -70,6 +70,10 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea std::filesystem::path host_path = mount->host_path / rel_path; std::filesystem::path patch_path = mount->host_path; patch_path += "-UPDATE"; + if (!std::filesystem::exists(patch_path)) { + patch_path = mount->host_path; + patch_path += "-patch"; + } patch_path /= rel_path; if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) && diff --git a/src/emulator.cpp b/src/emulator.cpp index b6586ecfd..4ec62995b 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -80,7 +80,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vectorshow(); @@ -434,6 +450,9 @@ public: QString folder_path, game_update_path, dlc_path, save_data_path, trophy_data_path; Common::FS::PathToQString(folder_path, m_games[itemID].path); game_update_path = folder_path + "-UPDATE"; + if (!std::filesystem::exists(Common::FS::PathFromQString(game_update_path))) { + game_update_path = folder_path + "-patch"; + } Common::FS::PathToQString( dlc_path, Config::getAddonInstallDir() / Common::FS::PathFromQString(folder_path).parent_path().filename()); diff --git a/src/qt_gui/kbm_config_dialog.cpp b/src/qt_gui/kbm_config_dialog.cpp index 49a6bcd89..1851c591d 100644 --- a/src/qt_gui/kbm_config_dialog.cpp +++ b/src/qt_gui/kbm_config_dialog.cpp @@ -225,7 +225,8 @@ void EditorDialog::loadInstalledGames() { QDir parentFolder(installDir); QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (const auto& fileInfo : fileList) { - if (fileInfo.isDir() && !fileInfo.filePath().endsWith("-UPDATE")) { + if (fileInfo.isDir() && (!fileInfo.filePath().endsWith("-UPDATE") || + !fileInfo.filePath().endsWith("-patch"))) { gameComboBox->addItem(fileInfo.fileName()); // Add game name to combo box } } diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index bde32a52d..68135048e 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -840,7 +840,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int // Default paths auto game_folder_path = game_install_dir / pkg.GetTitleID(); auto game_update_path = use_game_update ? game_folder_path.parent_path() / - (std::string{pkg.GetTitleID()} + "-UPDATE") + (std::string{pkg.GetTitleID()} + "-patch") : game_folder_path; const int max_depth = 5; @@ -851,7 +851,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int if (found_game.has_value()) { game_folder_path = found_game.value().parent_path(); game_update_path = use_game_update ? game_folder_path.parent_path() / - (std::string{pkg.GetTitleID()} + "-UPDATE") + (std::string{pkg.GetTitleID()} + "-patch") : game_folder_path; } } else { @@ -866,7 +866,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int game_folder_path = game_install_dir / pkg.GetTitleID(); } game_update_path = use_game_update ? game_folder_path.parent_path() / - (std::string{pkg.GetTitleID()} + "-UPDATE") + (std::string{pkg.GetTitleID()} + "-patch") : game_folder_path; } From 10bf3d383c80e3bd5436a9ea99e7d2c96ed4fa96 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 23 Mar 2025 18:25:11 -0300 Subject: [PATCH 060/194] QT: Fix PR 2662 (#2676) --- src/common/config.cpp | 6 +++--- src/common/config.h | 2 +- src/qt_gui/main.cpp | 7 ++++++- src/qt_gui/settings_dialog.cpp | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 0b5fb8be1..fd6538f85 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -82,6 +82,7 @@ static std::string trophyKey; // Gui static bool load_game_size = true; static std::vector settings_install_dirs = {}; +std::vector install_dirs_enabled = {}; std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path save_data_path = {}; u32 main_window_geometry_x = 400; @@ -643,8 +644,8 @@ const std::vector getGameInstallDirs() { return enabled_dirs; } -const std::vector& getAllGameInstallDirs() { - return settings_install_dirs; +const std::vector getGameInstallDirsEnabled() { + return install_dirs_enabled; } std::filesystem::path getAddonInstallDir() { @@ -839,7 +840,6 @@ void load(const std::filesystem::path& path) { const auto install_dir_array = toml::find_or>(gui, "installDirs", {}); - std::vector install_dirs_enabled; try { install_dirs_enabled = toml::find>(gui, "installDirsEnabled"); } catch (...) { diff --git a/src/common/config.h b/src/common/config.h index e495be52a..4202da88a 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -161,7 +161,7 @@ u32 getMainWindowGeometryY(); u32 getMainWindowGeometryW(); u32 getMainWindowGeometryH(); const std::vector getGameInstallDirs(); -const std::vector& getAllGameInstallDirs(); +const std::vector getGameInstallDirsEnabled(); std::filesystem::path getAddonInstallDir(); u32 getMainWindowTheme(); u32 getIconSize(); diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 36dc226ae..d70294e40 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -157,8 +157,13 @@ int main(int argc, char* argv[]) { } } + bool allInstallDirsDisabled = + std::all_of(Config::getGameInstallDirsEnabled().begin(), + Config::getGameInstallDirsEnabled().end(), [](bool val) { return !val; }); + // If no game directory is set and no command line argument, prompt for it - if (Config::getGameInstallDirs().empty() && !has_command_line_argument) { + if (Config::getGameInstallDirs().empty() && allInstallDirsDisabled && + !has_command_line_argument) { GameInstallDialog dlg; dlg.exec(); } diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index a8beef8da..d789f6f48 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -821,7 +821,7 @@ void SettingsDialog::ResetInstallFolders() { std::vector install_dirs_enabled; try { - install_dirs_enabled = toml::find>(gui, "installDirsEnabled"); + install_dirs_enabled = Config::getGameInstallDirsEnabled(); } catch (...) { // If it does not exist, assume that all are enabled. install_dirs_enabled.resize(install_dir_array.size(), true); From 4f8e5dfd7c03b89bc133d96bb19adb28cb62d074 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 24 Mar 2025 10:25:37 +0200 Subject: [PATCH 061/194] New Crowdin updates (#2671) * New translations en_us.ts (German) * New translations en_us.ts (German) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Turkish) * New translations en_us.ts (Norwegian Bokmal) --- src/qt_gui/translations/de_DE.ts | 364 +++++++++++++++---------------- src/qt_gui/translations/nb_NO.ts | 2 +- src/qt_gui/translations/pt_BR.ts | 10 +- src/qt_gui/translations/tr_TR.ts | 40 ++-- 4 files changed, 208 insertions(+), 208 deletions(-) diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index d366d1116..7f395c1c8 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -38,7 +38,7 @@ Version: - Version: + Version: Size: @@ -50,7 +50,7 @@ Repository: - Repository: + Quellen: Download Cheats @@ -86,11 +86,11 @@ Cheats - Cheats + Cheats Patches - Patches + Patches Error @@ -407,59 +407,59 @@ ControlSettings Configure Controls - Configure Controls + Steuerung einrichten D-Pad - D-Pad + Steuerkreuz Up - Up + Oben Left - Left + Links Right - Right + Rechts Down - Down + Runter Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + Linker Stick tote Zone (def:2 max:127) Left Deadzone - Left Deadzone + Linke Deadzone Left Stick - Left Stick + Linker Analogstick Config Selection - Config Selection + Konfigurationsauswahl Common Config - Common Config + Standard Konfiguration Use per-game configs - Use per-game configs + Benutze Per-Game Einstellungen L1 / LB - L1 / LB + L1 / LB L2 / LT - L2 / LT + L2 / LT Back @@ -467,59 +467,59 @@ R1 / RB - R1 / RB + R1 / RB R2 / RT - R2 / RT + R2 / RT L3 - L3 + L3 Options / Start - Options / Start + Options / Start R3 - R3 + R3 Face Buttons - Face Buttons + Aktionstasten Triangle / Y - Triangle / Y + Dreieck / Y Square / X - Square / X + Quadrat / X Circle / B - Circle / B + Kreis / B Cross / A - Cross / A + Kreuz / A Right Stick Deadzone (def:2, max:127) - Right Stick Deadzone (def:2, max:127) + Rechter Stick tote Zone (def:2, max:127) Right Deadzone - Right Deadzone + Rechte tote Zone Right Stick - Right Stick + Rechter Analogstick Color Adjustment - Color Adjustment + Farbanpassung R: @@ -535,46 +535,46 @@ Override Lightbar Color - Override Lightbar Color + Farbe der Leuchtleiste überschreiben Override Color - Override Color + Farbe überschreiben Unable to Save - Unable to Save + Speichern nicht möglich Cannot bind axis values more than once - Cannot bind axis values more than once + Achsenwerte können nicht mehr als einmal gebunden werden Save - Save + Speichern Apply - Apply + Übernehmen Restore Defaults - Restore Defaults + Werkseinstellungen wiederherstellen Cancel - Cancel + Abbrechen EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Tastatur + Maus und Controller Eingabezuordnungen bearbeiten Use Per-Game configs - Use Per-Game configs + Benutze Per-Game Einstellungen Error @@ -582,19 +582,19 @@ Could not open the file for reading - Could not open the file for reading + Datei konnte nicht zum Lesen geöffnet werden Could not open the file for writing - Could not open the file for writing + Datei konnte nicht zum Schreiben geöffnet werden Save Changes - Save Changes + Änderungen Speichern Do you want to save changes? - Do you want to save changes? + Sollen die Änderungen gespeichert werden? Help @@ -602,15 +602,15 @@ Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Möchten Sie Ihre eigene Standardkonfiguration auf die ursprüngliche Standardkonfiguration zurücksetzen? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Möchten Sie diese Konfiguration auf Ihre eigene Standardkonfiguration zurücksetzen? Reset to Default - Reset to Default + Auf Standard zurücksetzen @@ -655,7 +655,7 @@ Directory to install DLC - Directory to install DLC + Verzeichnis zum Installieren von DLC @@ -666,7 +666,7 @@ Name - Name + Name Serial @@ -678,11 +678,11 @@ Region - Region + Region Firmware - Firmware + Firmware Size @@ -690,7 +690,7 @@ Version - Version + Version Path @@ -706,15 +706,15 @@ h - h + h m - m + m s - s + s Compatibility is untested @@ -753,23 +753,23 @@ GameListUtils B - B + B KB - KB + KB MB - MB + MB GB - GB + GB TB - TB + TB @@ -780,7 +780,7 @@ Cheats / Patches - Cheats / Patches + Cheats / Patches SFO Viewer @@ -848,7 +848,7 @@ Delete Trophy - Delete Trophy + Trophäe löschen Compatibility... @@ -904,7 +904,7 @@ DLC - DLC + DLC Delete %1 @@ -924,27 +924,27 @@ This game has no update folder to open! - This game has no update folder to open! + Dieses Spiel hat keinen Update-Ordner zum öffnen! No log file found for this game! - No log file found for this game! + Keine Protokolldatei für dieses Spiel gefunden! Failed to convert icon. - Failed to convert icon. + Fehler beim Konvertieren des Symbols. This game has no save data to delete! - This game has no save data to delete! + Dieses Spiel hat keine Speicherdaten zum Löschen! This game has no saved trophies to delete! - This game has no saved trophies to delete! + Dieses Spiel hat keine gespeicherten Trophäen zum Löschen! Save Data - Save Data + Gespeicherte Daten Trophy @@ -952,7 +952,7 @@ SFO Viewer for - SFO Viewer for + SFO-Betrachter für @@ -963,19 +963,19 @@ FAQ - FAQ + Häufig gestellte Fragen Syntax - Syntax + Syntax Special Bindings - Special Bindings + Spezielle Zuordnungen Keybindings - Keybindings + Tastenbelegung @@ -990,78 +990,78 @@ Install All Queued to Selected Folder - Install All Queued to Selected Folder + Installieren Sie alles aus der Warteschlange in den ausgewählten Ordner Delete PKG File on Install - Delete PKG File on Install + PKG-Datei beim Installieren löschen KBMSettings Configure Controls - Configure Controls + Steuerung konfigurieren D-Pad - D-Pad + Steuerkreuz Up - Up + Oben unmapped - unmapped + nicht zugeordnet Left - Left + Links Right - Right + Rechts Down - Down + Runter Left Analog Halfmode - Left Analog Halfmode + Linker Analog-Halbmodus hold to move left stick at half-speed - hold to move left stick at half-speed + Halten um den linken Analogstick mit Halbgeschwindigkeit zu bewegen Left Stick - Left Stick + Linker Analogstick Config Selection - Config Selection + Konfigurationsauswahl Common Config - Common Config + Allgemeine Konfiguration Use per-game configs - Use per-game configs + Benutze Per-Game Einstellungen L1 - L1 + L1 L2 - L2 + L2 Text Editor - Text Editor + Textbearbeiter Help @@ -1069,143 +1069,143 @@ R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click - Touchpad Click + Touchpad-Klick Mouse to Joystick - Mouse to Joystick + Maus zu Joystick *press F7 ingame to activate - *press F7 ingame to activate + *Zum Aktivieren F7 ingame drücken R3 - R3 + R3 Options - Options + Options Mouse Movement Parameters - Mouse Movement Parameters + Mausbewegungsparameter note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + Hinweis: Klicken Sie auf Hilfe-Button/Special Tastaturbelegungen für weitere Informationen Face Buttons - Face Buttons + Aktionstasten Triangle - Triangle + Dreieck Square - Square + Quadrat Circle - Circle + Kreis Cross - Cross + Kreuz Right Analog Halfmode - Right Analog Halfmode + Rechter Analog-Halbmodus hold to move right stick at half-speed - hold to move right stick at half-speed + Halten um den rechten Analogstick mit Halbgeschwindigkeit zu bewegen Right Stick - Right Stick + Rechter Analogstick Speed Offset (def 0.125): - Speed Offset (def 0.125): + Geschwindigkeitsversatz (Def 0.125): Copy from Common Config - Copy from Common Config + Von allgemeiner Konfiguration kopieren Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Tote Zone Versatz (Def 0.50): Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Geschwindigkeit Multiplikator (def 1.0): Common Config Selected - Common Config Selected + Allgemeine Konfiguration ausgewählt This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Diese Schaltfläche kopiert Zuordnungen aus der allgemeinen Konfiguration in das aktuell ausgewählte Profil, und kann nicht verwendet werden, wenn das aktuell ausgewählte Profil die allgemeine Konfiguration ist. Copy values from Common Config - Copy values from Common Config + Werte von allgemeiner Konfiguration kopieren Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + Möchten Sie die vorhandenen Zuordnungen mit den Zuordnungen der allgemeinen Konfigurationen überschreiben? Unable to Save - Unable to Save + Speichern nicht möglich Cannot bind any unique input more than once - Cannot bind any unique input more than once + Kann keine eindeutige Eingabe mehr als einmal zuordnen Press a key - Press a key + Drücken Sie eine Taste Cannot set mapping - Cannot set mapping + Kann Zuordnung nicht festlegen Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + Mausrad kann nicht zu Analogstick Ausgabe zugeordnet werden Save - Save + Speichern Apply - Apply + Übernehmen Restore Defaults - Restore Defaults + Werkseinstellungen wiederherstellen Cancel - Cancel + Abbrechen @@ -1484,27 +1484,27 @@ Run Game - Run Game + Spiel ausführen Eboot.bin file not found - Eboot.bin file not found + Eboot.bin Datei nicht gefunden PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) + PKG-Datei (*.PKG *.pkg) PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! + PKG ist ein Patch oder DLC, bitte installieren Sie zuerst das Spiel! Game is already running! - Game is already running! + Spiel läuft bereits! shadPS4 - shadPS4 + shadPS4 @@ -1527,7 +1527,7 @@ Installed - Installed + Installiert Size @@ -1543,11 +1543,11 @@ App Ver - App Ver + App Ver FW - FW + FW Region @@ -1555,7 +1555,7 @@ Flags - Flags + Markierungen Path @@ -1571,7 +1571,7 @@ Package - Package + Paket @@ -1598,7 +1598,7 @@ Emulator - Emulator + Emulator Enable Separate Update Folder @@ -1634,11 +1634,11 @@ Open the custom trophy images/sounds folder - Open the custom trophy images/sounds folder + Öffne den benutzerdefinierten Ordner für Trophäenbilder/Sounds Logger - Logger + Protokollführer Log Type @@ -1658,7 +1658,7 @@ Cursor - Cursor + Mauszeiger Hide Cursor @@ -1670,11 +1670,11 @@ s - s + s Controller - Controller + Kontroller Back Button Behavior @@ -1714,7 +1714,7 @@ Enable HDR - Enable HDR + HDR aktivieren Paths @@ -1734,7 +1734,7 @@ Debug - Debug + Debug Enable Debug Dumping @@ -1802,19 +1802,19 @@ Disable Trophy Notification - Disable Trophy Notification + Trophäen-Benachrichtigung deaktivieren Background Image - Background Image + Hintergrundbild Show Background Image - Show Background Image + Hintergrundbild anzeigen Opacity - Opacity + Transparenz Play title music @@ -1902,7 +1902,7 @@ Background Image:\nControl the opacity of the game background image. - Background Image:\nControl the opacity of the game background image. + Hintergrundbild:\nSteuere die Deckkraft des Spiel-Hintergrundbilds. Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. @@ -1986,7 +1986,7 @@ Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. - Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + HDR:\nAktiviert HDR in Spielen, die es unterstützen.\nIhr Monitor muss Unterstützung für den BT2020 PQ Farbraum und das RGB10A2 Swapchain Format haben. Game Folders:\nThe list of folders to check for installed games. @@ -2038,23 +2038,23 @@ Save Data Path:\nThe folder where game save data will be saved. - Save Data Path:\nThe folder where game save data will be saved. + Datenpfad speichern:\nDer Ordner, in dem Spieldaten gespeichert werden. Browse:\nBrowse for a folder to set as the save data path. - Browse:\nBrowse for a folder to set as the save data path. + Durchsuchen:\nDurchsuchen eines Ordners, um den Speicherdatenpfad festzulegen. Release - Release + Veröffentlichung Nightly - Nightly + Nightly Set the volume of the background music. - Set the volume of the background music. + Legen Sie die Lautstärke der Hintergrundmusik fest. Enable Motion Controls @@ -2070,11 +2070,11 @@ async - async + asynchron sync - sync + syncron Auto Select @@ -2086,7 +2086,7 @@ Directory to save data - Directory to save data + Verzeichnis um Daten zu speichern Video @@ -2094,43 +2094,43 @@ Display Mode - Display Mode + Anzeigemodus Windowed - Windowed + Fenster Fullscreen - Fullscreen + Vollbild Fullscreen (Borderless) - Fullscreen (Borderless) + Vollbild (randlos) Window Size - Window Size + Fenstergröße W: - W: + W: H: - H: + H: Separate Log Files - Separate Log Files + Separate Protokolldateien Separate Log Files:\nWrites a separate logfile for each game. - Separate Log Files:\nWrites a separate logfile for each game. + Separate Protokolldateien:\nSchreibt für jedes Spiel eine separate Logdatei. Trophy Notification Position - Trophy Notification Position + Trophäen-Benachrichtigungsposition Left @@ -2142,43 +2142,43 @@ Top - Top + Zuoberst Bottom - Bottom + Zuunterst Notification Duration - Notification Duration + Benachrichtigungsdauer Portable User Folder - Portable User Folder + Portable Benutzerordner Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Erstellen eines portablen Benutzerordners aus dem allgemeinen Benutzer Ordner Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portablen Benutzerordner:\nspeichert ShadPS4 Einstellungen und Daten, die nur auf den ShadPS4 Build im aktuellen Ordner angewendet werden. Starten Sie die App nach dem Erstellen des tragbaren Benutzerordners neu, um sie zu verwenden. Cannot create portable user folder - Cannot create portable user folder + Kann keinen portablen Benutzerordner erstellen %1 already exists - %1 already exists + %1 existiert bereits Portable user folder created - Portable user folder created + Portablen Benutzerordner erstellt %1 successfully created. - %1 successfully created. + %1 erfolgreich erstellt. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index f627ff640..b257b548b 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1602,7 +1602,7 @@ Enable Separate Update Folder - Bruk seperat oppdateringsmappe + Bruk separat oppdateringsmappe Default tab when opening settings diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 794215401..e37a3fe96 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -730,7 +730,7 @@ Game displays an image but does not go past the menu - Jogo exibe imagem mas não passa do menu + O jogo exibe imagem mas não passa do menu Game has game-breaking glitches or unplayable performance @@ -1284,11 +1284,11 @@ List View - Visualizar em Lista + Visualização em Lista Grid View - Visualizar em Grade + Visualização em Grade Elf Viewer @@ -2030,11 +2030,11 @@ 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. - Marcadores de Depuração de Host:\nInsere informações vindo do emulador como marcadores para comandos AMDGPU específicos em torno de comandos Vulkan, além de fornecer nomes de depuração aos recursos.\nSe isso estiver habilitado, ative os Diagnósticos de Falha.\nÚtil para programas como o RenderDoc. + Marcadores de Depuração do Host:\nInsere informações vindo do emulador como marcadores para comandos AMDGPU específicos em torno de comandos Vulkan, além de fornecer nomes de depuração aos recursos.\nSe isso estiver habilitado, ative os Diagnósticos de Falhas.\nÚtil para programas como o RenderDoc. 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. - Marcadores de Depuração de Convidado:\nInsere quaisquer marcadores de depuração que o próprio jogo adicionou ao buffer de comando.\nSe isso estiver habilitado, ative os Diagnósticos de Falha.\nÚtil para programas como o RenderDoc. + Marcadores de Depuração do Convidado:\nInsere quaisquer marcadores de depuração que o próprio jogo adicionou ao buffer de comando.\nSe isso estiver habilitado, ative os Diagnósticos de Falhas.\nÚtil para programas como o RenderDoc. Save Data Path:\nThe folder where game save data will be saved. diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index fd4669369..394704274 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -26,11 +26,11 @@ Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n - Cheats/Patches deneysel niteliktedir.\nDikkatli kullanın.\n\nCheat'leri ayrı ayrı indirerek, depo seçerek ve indirme düğmesine tıklayarak indirin.\nPatches sekmesinde tüm patch'leri bir kerede indirebilir, hangi patch'leri kullanmak istediğinizi seçebilir ve seçiminizi kaydedebilirsiniz.\n\nCheats/Patches'i geliştirmediğimiz için,\nproblemleri cheat yazarına bildirin.\n\nYeni bir cheat mi oluşturduğunuz? Şu adresi ziyaret edin:\n + Hileler/Yamalar deneysel özelliklerdir.\nDikkatli kullanın.\n\nHileleri depo seçerek ve indirme düğmesine tıklayarak ayrı ayrı indirin.\nYamalar sekmesinde tüm yamaları tek seferde indirebilir, hangi yamaları kullanmak istediğinizi seçebilir ve seçiminizi kaydedebilirsiniz.\n\nHileleri ve yamaları biz geliştirmediğimiz için\nsorunlarınızı hile geliştiricisine bildirin.\n\nYeni bir hile oluşturduysanız şu adresi ziyaret edin:\n No Image Available - Görüntü Mevcut Değil + Kaynak Mevcut Değil Serial: @@ -70,7 +70,7 @@ Do you want to delete the selected file?\n%1 - Seçilen dosyayı silmek istiyor musunuz?\n%1 + Seçili dosyayı silmek istiyor musunuz?\n%1 Select Patch File: @@ -122,7 +122,7 @@ Success - Başarı + Başarılı Options saved successfully. @@ -138,11 +138,11 @@ File Exists - Dosya Var + Dosya mevcut File already exists. Do you want to replace it? - Dosya zaten var. Üzerine yazmak ister misiniz? + Dosya zaten mevcut. Var olan dosyayı değiştirmek istiyor musunuz? Failed to save file: @@ -313,7 +313,7 @@ Update - Güncelleme + Güncelle No @@ -551,26 +551,26 @@ Save - Save + Kaydet Apply - Apply + Uygula Restore Defaults - Restore Defaults + Varsayılanlara Sıfırla Cancel - Cancel + İptal EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Klavye + Fare ve Kontrolcü tuş atamalarını düzenle Use Per-Game configs @@ -1141,7 +1141,7 @@ Speed Offset (def 0.125): - Speed Offset (def 0.125): + Hız Sapması (varsayılan 0.125): Copy from Common Config @@ -1149,7 +1149,7 @@ Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Ölü Bölge Sapması (varsayılan 0.50): Speed Multiplier (def 1.0): @@ -1161,7 +1161,7 @@ This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Bu tuş, Ortak Yapılandırma'daki atamaları seçili profile kopyalar ve seçili profil Ortak Yapılandırma ise kullanılamaz. Copy values from Common Config @@ -1193,19 +1193,19 @@ Save - Save + Kaydet Apply - Apply + Uygula Restore Defaults - Restore Defaults + Varsayılanlara Sıfırla Cancel - Cancel + İptal @@ -2030,7 +2030,7 @@ 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 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. + Ana Bilgisayar Hata Ayıklama Göstergeleri:\nVulkan komutlarının etrafına belirli AMDGPU komutları için göstergeler gibi emülatör tarafı bilgileri ekler ve kaynaklara hata ayıklama adları verir.\nBunu etkinleştirdiyseniz, Çökme Tanılamaları'nı etkinleştirmelisiniz.\nRenderDoc gibi programlar için faydalıdır. 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. From 16a68d78eb71c8b3cfa697ba9989d27f3c4ef961 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Mon, 24 Mar 2025 05:25:51 -0300 Subject: [PATCH 062/194] Trophy Viewer - Select Game (#2678) * Trophy Viewer - Select Game * TR - Button in Utils +icon TR - Button in Utils +icon I also made a small correction to the game folder list, where the checkboxes were being filled in incorrectly. --- REUSE.toml | 1 + src/common/config.cpp | 6 ++- src/images/trophy_icon.png | Bin 0 -> 16958 bytes src/qt_gui/gui_context_menus.h | 28 +++++++++++- src/qt_gui/main_window.cpp | 55 ++++++++++++++++++++++ src/qt_gui/main_window_ui.h | 8 ++++ src/qt_gui/translations/en_US.ts | 12 +++++ src/qt_gui/trophy_viewer.cpp | 76 +++++++++++++++++++++++++++---- src/qt_gui/trophy_viewer.h | 20 +++++++- src/shadps4.qrc | 1 + 10 files changed, 193 insertions(+), 14 deletions(-) create mode 100644 src/images/trophy_icon.png diff --git a/REUSE.toml b/REUSE.toml index d9b307d39..793990bd8 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -30,6 +30,7 @@ path = [ "src/images/dump_icon.png", "src/images/exit_icon.png", "src/images/file_icon.png", + "src/images/trophy_icon.png", "src/images/flag_china.png", "src/images/flag_eu.png", "src/images/flag_jp.png", diff --git a/src/common/config.cpp b/src/common/config.cpp index fd6538f85..e0a348fbe 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -645,7 +645,11 @@ const std::vector getGameInstallDirs() { } const std::vector getGameInstallDirsEnabled() { - return install_dirs_enabled; + std::vector enabled_dirs; + for (const auto& dir : settings_install_dirs) { + enabled_dirs.push_back(dir.enabled); + } + return enabled_dirs; } std::filesystem::path getAddonInstallDir() { diff --git a/src/images/trophy_icon.png b/src/images/trophy_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..559e7dbb2c1e2d4b5c5cb3a066a08209b7054839 GIT binary patch literal 16958 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 Xb9M+i^O$<{P6LDsqXhfsB allTrophyGames; + for (const auto& game : m_games) { + TrophyGameInfo gameInfo; + gameInfo.name = QString::fromStdString(game.name); + Common::FS::PathToQString(gameInfo.trophyPath, game.serial); + Common::FS::PathToQString(gameInfo.gameTrpPath, game.path); + + auto update_path = Common::FS::PathFromQString(gameInfo.gameTrpPath); + update_path += "-UPDATE"; + if (std::filesystem::exists(update_path)) { + Common::FS::PathToQString(gameInfo.gameTrpPath, update_path); + } else { + update_path = Common::FS::PathFromQString(gameInfo.gameTrpPath); + update_path += "-patch"; + if (std::filesystem::exists(update_path)) { + Common::FS::PathToQString(gameInfo.gameTrpPath, update_path); + } + } + + allTrophyGames.append(gameInfo); + } + + QString gameName = QString::fromStdString(m_games[itemID].name); + TrophyViewer* trophyViewer = + new TrophyViewer(trophyPath, gameTrpPath, gameName, allTrophyGames); trophyViewer->show(); connect(widget->parent(), &QWidget::destroyed, trophyViewer, [trophyViewer]() { trophyViewer->deleteLater(); }); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 68135048e..27551e997 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -593,6 +593,60 @@ void MainWindow::CreateConnects() { pkgViewer->show(); }); + // Trophy Viewer + connect(ui->trophyViewerAct, &QAction::triggered, this, [this]() { + if (m_game_info->m_games.empty()) { + QMessageBox::information( + this, tr("Trophy Viewer"), + tr("No games found. Please add your games to your library first.")); + return; + } + + const auto& firstGame = m_game_info->m_games[0]; + QString trophyPath, gameTrpPath; + Common::FS::PathToQString(trophyPath, firstGame.serial); + Common::FS::PathToQString(gameTrpPath, firstGame.path); + + auto game_update_path = Common::FS::PathFromQString(gameTrpPath); + game_update_path += "-UPDATE"; + if (std::filesystem::exists(game_update_path)) { + Common::FS::PathToQString(gameTrpPath, game_update_path); + } else { + game_update_path = Common::FS::PathFromQString(gameTrpPath); + game_update_path += "-patch"; + if (std::filesystem::exists(game_update_path)) { + Common::FS::PathToQString(gameTrpPath, game_update_path); + } + } + + QVector allTrophyGames; + for (const auto& game : m_game_info->m_games) { + TrophyGameInfo gameInfo; + gameInfo.name = QString::fromStdString(game.name); + Common::FS::PathToQString(gameInfo.trophyPath, game.serial); + Common::FS::PathToQString(gameInfo.gameTrpPath, game.path); + + auto update_path = Common::FS::PathFromQString(gameInfo.gameTrpPath); + update_path += "-UPDATE"; + if (std::filesystem::exists(update_path)) { + Common::FS::PathToQString(gameInfo.gameTrpPath, update_path); + } else { + update_path = Common::FS::PathFromQString(gameInfo.gameTrpPath); + update_path += "-patch"; + if (std::filesystem::exists(update_path)) { + Common::FS::PathToQString(gameInfo.gameTrpPath, update_path); + } + } + + allTrophyGames.append(gameInfo); + } + + QString gameName = QString::fromStdString(firstGame.name); + TrophyViewer* trophyViewer = + new TrophyViewer(trophyPath, gameTrpPath, gameName, allTrophyGames); + trophyViewer->show(); + }); + // Themes connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar); @@ -1169,6 +1223,7 @@ void MainWindow::SetUiIcons(bool isWhite) { ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite)); ui->menuGame_List_Mode->setIcon(RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite)); ui->pkgViewerAct->setIcon(RecolorIcon(ui->pkgViewerAct->icon(), isWhite)); + ui->trophyViewerAct->setIcon(RecolorIcon(ui->trophyViewerAct->icon(), isWhite)); ui->configureAct->setIcon(RecolorIcon(ui->configureAct->icon(), isWhite)); ui->addElfFolderAct->setIcon(RecolorIcon(ui->addElfFolderAct->icon(), isWhite)); } diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 3ebfcee9e..246c2afd6 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -27,6 +27,7 @@ public: QAction* downloadCheatsPatchesAct; QAction* dumpGameListAct; QAction* pkgViewerAct; + QAction* trophyViewerAct; #ifdef ENABLE_UPDATER QAction* updaterAct; #endif @@ -139,6 +140,10 @@ public: pkgViewerAct = new QAction(MainWindow); pkgViewerAct->setObjectName("pkgViewer"); pkgViewerAct->setIcon(QIcon(":images/file_icon.png")); + trophyViewerAct = new QAction(MainWindow); + trophyViewerAct->setObjectName("trophyViewer"); + trophyViewerAct->setIcon(QIcon(":images/trophy_icon.png")); + #ifdef ENABLE_UPDATER updaterAct = new QAction(MainWindow); updaterAct->setObjectName("updaterAct"); @@ -321,6 +326,7 @@ public: menuUtils->addAction(downloadCheatsPatchesAct); menuUtils->addAction(dumpGameListAct); menuUtils->addAction(pkgViewerAct); + menuUtils->addAction(trophyViewerAct); #ifdef ENABLE_UPDATER menuHelp->addAction(updaterAct); #endif @@ -379,6 +385,8 @@ public: dumpGameListAct->setText( QCoreApplication::translate("MainWindow", "Dump Game List", nullptr)); pkgViewerAct->setText(QCoreApplication::translate("MainWindow", "PKG Viewer", nullptr)); + trophyViewerAct->setText( + QCoreApplication::translate("MainWindow", "Trophy Viewer", nullptr)); mw_searchbar->setPlaceholderText( QCoreApplication::translate("MainWindow", "Search...", nullptr)); menuFile->setTitle(QCoreApplication::translate("MainWindow", "File", nullptr)); diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 9c9d56076..20cba0378 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Viewer + + Select Game: + + Progress diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index bfa47e3cc..bed487605 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -104,8 +104,10 @@ void TrophyViewer::updateTableFilters() { } } -TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindow() { - this->setWindowTitle(tr("Trophy Viewer")); +TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath, QString gameName, + const QVector& allTrophyGames) + : QMainWindow(), allTrophyGames_(allTrophyGames), currentGameName_(gameName) { + this->setWindowTitle(tr("Trophy Viewer") + " - " + currentGameName_); this->setAttribute(Qt::WA_DeleteOnClose); tabWidget = new QTabWidget(this); @@ -127,11 +129,40 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo << "PID"; PopulateTrophyWidget(trophyPath); - QDockWidget* trophyInfoDock = new QDockWidget("", this); + trophyInfoDock = new QDockWidget("", this); QWidget* dockWidget = new QWidget(trophyInfoDock); QVBoxLayout* dockLayout = new QVBoxLayout(dockWidget); dockLayout->setAlignment(Qt::AlignTop); + // ComboBox for game selection + if (!allTrophyGames_.isEmpty()) { + QLabel* gameSelectionLabel = new QLabel(tr("Select Game:"), dockWidget); + dockLayout->addWidget(gameSelectionLabel); + + gameSelectionComboBox = new QComboBox(dockWidget); + for (const auto& game : allTrophyGames_) { + gameSelectionComboBox->addItem(game.name); + } + + // Select current game in ComboBox + if (!currentGameName_.isEmpty()) { + int index = gameSelectionComboBox->findText(currentGameName_); + if (index >= 0) { + gameSelectionComboBox->setCurrentIndex(index); + } + } + + dockLayout->addWidget(gameSelectionComboBox); + + connect(gameSelectionComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &TrophyViewer::onGameSelectionChanged); + + QFrame* line = new QFrame(dockWidget); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + dockLayout->addWidget(line); + } + trophyInfoLabel = new QLabel(tr("Progress") + ": 0% (0/0)", dockWidget); trophyInfoLabel->setStyleSheet( "font-weight: bold; font-size: 16px; color: white; background: #333; padding: 5px;"); @@ -162,7 +193,7 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo expandButton->setGeometry(80, 0, 27, 27); expandButton->hide(); - connect(expandButton, &QPushButton::clicked, this, [this, trophyInfoDock] { + connect(expandButton, &QPushButton::clicked, this, [this] { trophyInfoDock->setVisible(true); expandButton->hide(); }); @@ -184,13 +215,13 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo updateTrophyInfo(); updateTableFilters(); - connect(trophyInfoDock, &QDockWidget::topLevelChanged, this, [this, trophyInfoDock] { + connect(trophyInfoDock, &QDockWidget::topLevelChanged, this, [this] { if (!trophyInfoDock->isVisible()) { expandButton->show(); } }); - connect(trophyInfoDock, &QDockWidget::visibilityChanged, this, [this, trophyInfoDock] { + connect(trophyInfoDock, &QDockWidget::visibilityChanged, this, [this] { if (!trophyInfoDock->isVisible()) { expandButton->show(); } else { @@ -199,6 +230,29 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo }); } +void TrophyViewer::onGameSelectionChanged(int index) { + if (index < 0 || index >= allTrophyGames_.size()) { + return; + } + + while (tabWidget->count() > 0) { + QWidget* widget = tabWidget->widget(0); + tabWidget->removeTab(0); + delete widget; + } + + const TrophyGameInfo& selectedGame = allTrophyGames_[index]; + currentGameName_ = selectedGame.name; + gameTrpPath_ = selectedGame.gameTrpPath; + + this->setWindowTitle(tr("Trophy Viewer") + " - " + currentGameName_); + + PopulateTrophyWidget(selectedGame.trophyPath); + + updateTrophyInfo(); + updateTableFilters(); +} + void TrophyViewer::onDockClosed() { if (!trophyInfoDock->isVisible()) { reopenButton->setVisible(true); @@ -389,13 +443,15 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { tabWidget->addTab(tableWidget, tabName.insert(6, " ").replace(0, 1, tabName.at(0).toUpper())); - this->resize(width + 400, 720); - QSize mainWindowSize = QApplication::activeWindow()->size(); - this->resize(mainWindowSize.width() * 0.8, mainWindowSize.height() * 0.8); + if (!this->isMaximized()) { + this->resize(width + 400, 720); + QSize mainWindowSize = QApplication::activeWindow()->size(); + this->resize(mainWindowSize.width() * 0.8, mainWindowSize.height() * 0.8); + } this->show(); tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); - tableWidget->setColumnWidth(3, 650); + tableWidget->setColumnWidth(3, 500); } this->setCentralWidget(tabWidget); } diff --git a/src/qt_gui/trophy_viewer.h b/src/qt_gui/trophy_viewer.h index 75fb500e7..c63171774 100644 --- a/src/qt_gui/trophy_viewer.h +++ b/src/qt_gui/trophy_viewer.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -12,26 +13,38 @@ #include #include #include +#include #include #include #include #include +#include #include #include "common/types.h" #include "core/file_format/trp.h" +struct TrophyGameInfo { + QString name; + QString trophyPath; + QString gameTrpPath; +}; + class TrophyViewer : public QMainWindow { Q_OBJECT public: - explicit TrophyViewer(QString trophyPath, QString gameTrpPath); + explicit TrophyViewer( + QString trophyPath, QString gameTrpPath, QString gameName = "", + const QVector& allTrophyGames = QVector()); void updateTrophyInfo(); - void updateTableFilters(); void onDockClosed(); void reopenLeftDock(); +private slots: + void onGameSelectionChanged(int index); + private: void PopulateTrophyWidget(QString title); void SetTableItem(QTableWidget* parent, int row, int column, QString str); @@ -39,14 +52,17 @@ private: QTabWidget* tabWidget = nullptr; QStringList headers; QString gameTrpPath_; + QString currentGameName_; TRP trp; QLabel* trophyInfoLabel; QCheckBox* showEarnedCheck; QCheckBox* showNotEarnedCheck; QCheckBox* showHiddenCheck; + QComboBox* gameSelectionComboBox; QPushButton* expandButton; QDockWidget* trophyInfoDock; QPushButton* reopenButton; + QVector allTrophyGames_; std::string GetTrpType(const QChar trp_) { switch (trp_.toLatin1()) { diff --git a/src/shadps4.qrc b/src/shadps4.qrc index a1ff680ed..340756f5c 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -8,6 +8,7 @@ images/stop_icon.png images/utils_icon.png images/file_icon.png + images/trophy_icon.png images/folder_icon.png images/themes_icon.png images/iconsize_icon.png From 1908d26093fcdf409b74eef83cffbb812d447a3f Mon Sep 17 00:00:00 2001 From: mailwl Date: Mon, 24 Mar 2025 16:51:36 +0300 Subject: [PATCH 063/194] lseek: let the host OS set lseek errors (#2370) * Fix lseek(fd, -1, SEEK_SET) for XNA * be sure, if seek really return error * refactoring * let host os set lseek errors --- src/common/io_file.cpp | 14 -------------- src/core/libraries/kernel/file_system.cpp | 1 + 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp index 067010a26..3db78a145 100644 --- a/src/common/io_file.cpp +++ b/src/common/io_file.cpp @@ -377,20 +377,6 @@ bool IOFile::Seek(s64 offset, SeekOrigin origin) const { return false; } - if (False(file_access_mode & (FileAccessMode::Write | FileAccessMode::Append))) { - u64 size = GetSize(); - if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) { - LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); - return false; - } else if (origin == SeekOrigin::SetOrigin && (u64)offset > size) { - LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); - return false; - } else if (origin == SeekOrigin::End && offset > 0) { - LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); - return false; - } - } - errno = 0; const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0; diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index 0150c11f5..ef3c2fe70 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -289,6 +289,7 @@ size_t PS4_SYSV_ABI _writev(int fd, const SceKernelIovec* iov, int iovcn) { } s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) { + LOG_TRACE(Kernel_Fs, "called: offset {} whence {}", offset, whence); auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); if (file == nullptr) { From 5c72030fb8cc5227b877e2c84a633eea76ff25aa Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 25 Mar 2025 23:54:32 +0200 Subject: [PATCH 064/194] HLE discmap (#2686) * HLE discmap * improved parameters naming * fixed typo --- src/core/libraries/disc_map/disc_map.cpp | 22 +++++++++++----------- src/core/libraries/disc_map/disc_map.h | 10 ++++++---- src/core/libraries/libs.cpp | 1 + src/emulator.cpp | 3 +-- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/core/libraries/disc_map/disc_map.cpp b/src/core/libraries/disc_map/disc_map.cpp index bb566a149..e8b40e624 100644 --- a/src/core/libraries/disc_map/disc_map.cpp +++ b/src/core/libraries/disc_map/disc_map.cpp @@ -9,29 +9,29 @@ namespace Libraries::DiscMap { -int PS4_SYSV_ABI sceDiscMapGetPackageSize() { - LOG_WARNING(Lib_DiscMap, "(DUMMY) called"); +int PS4_SYSV_ABI sceDiscMapGetPackageSize(s64 fflags, int* ret1, int* ret2) { return ORBIS_DISC_MAP_ERROR_NO_BITMAP_INFO; } -int PS4_SYSV_ABI sceDiscMapIsRequestOnHDD() { - LOG_WARNING(Lib_DiscMap, "(DUMMY) called"); +int PS4_SYSV_ABI sceDiscMapIsRequestOnHDD(char* path, s64 offset, s64 nbytes, int* ret) { return ORBIS_DISC_MAP_ERROR_NO_BITMAP_INFO; } -int PS4_SYSV_ABI Func_7C980FFB0AA27E7A() { - LOG_ERROR(Lib_DiscMap, "(STUBBED) called"); +int PS4_SYSV_ABI Func_7C980FFB0AA27E7A(char* path, s64 offset, s64 nbytes, int* flags, int* ret1, + int* ret2) { + *flags = 0; + *ret1 = 0; + *ret2 = 0; return ORBIS_OK; } -int PS4_SYSV_ABI Func_8A828CAEE7EDD5E9() { - LOG_ERROR(Lib_DiscMap, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI Func_8A828CAEE7EDD5E9(char* path, s64 offset, s64 nbytes, int* flags, int* ret1, + int* ret2) { + return ORBIS_DISC_MAP_ERROR_NO_BITMAP_INFO; } int PS4_SYSV_ABI Func_E7EBCE96E92F91F8() { - LOG_ERROR(Lib_DiscMap, "(STUBBED) called"); - return ORBIS_OK; + return ORBIS_DISC_MAP_ERROR_NO_BITMAP_INFO; } void RegisterlibSceDiscMap(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/disc_map/disc_map.h b/src/core/libraries/disc_map/disc_map.h index 08abee632..dc8b875ac 100644 --- a/src/core/libraries/disc_map/disc_map.h +++ b/src/core/libraries/disc_map/disc_map.h @@ -10,10 +10,12 @@ class SymbolsResolver; } namespace Libraries::DiscMap { -int PS4_SYSV_ABI sceDiscMapGetPackageSize(); -int PS4_SYSV_ABI sceDiscMapIsRequestOnHDD(); -int PS4_SYSV_ABI Func_7C980FFB0AA27E7A(); -int PS4_SYSV_ABI Func_8A828CAEE7EDD5E9(); +int PS4_SYSV_ABI sceDiscMapGetPackageSize(s64 fflags, int* ret1, int* ret2); +int PS4_SYSV_ABI sceDiscMapIsRequestOnHDD(char* path, s64 offset, s64 nbytes, int* ret); +int PS4_SYSV_ABI Func_7C980FFB0AA27E7A(char* path, s64 offset, s64 nbytes, int* flags, int* ret1, + int* ret2); +int PS4_SYSV_ABI Func_8A828CAEE7EDD5E9(char* path, s64 offset, s64 nbytes, int* flags, int* ret1, + int* ret2); int PS4_SYSV_ABI Func_E7EBCE96E92F91F8(); void RegisterlibSceDiscMap(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 074cf524e..cd0fe650b 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -115,6 +115,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::NpParty::RegisterlibSceNpParty(sym); Libraries::Zlib::RegisterlibSceZlib(sym); Libraries::Hmd::RegisterlibSceHmd(sym); + Libraries::DiscMap::RegisterlibSceDiscMap(sym); } } // namespace Libraries diff --git a/src/emulator.cpp b/src/emulator.cpp index 4ec62995b..5f94f008a 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -289,13 +289,12 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector ModulesToLoad{ + constexpr std::array ModulesToLoad{ {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2}, {"libSceUlt.sprx", nullptr}, {"libSceJson.sprx", nullptr}, {"libSceJson2.sprx", nullptr}, {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterlibSceLibcInternal}, - {"libSceDiscMap.sprx", &Libraries::DiscMap::RegisterlibSceDiscMap}, {"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc}, {"libSceCesCs.sprx", nullptr}, {"libSceFont.sprx", nullptr}, From a1ec8b0a88f8a14fdecc0de83b8e20cbbc9b2e32 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Tue, 25 Mar 2025 23:01:21 +0100 Subject: [PATCH 065/194] Handle compute packets that are split between the ends of two command buffers (#2476) * Squashed initial implementation * Logging for checking if buffers are memory contiguous * Add check to see if first instruction is valid in the next buffer to avoid false positives * Oof * Replace old code with IndecisiveTurtle's new, better implementation * Add `unlikely` keyword to the split packet handling branches Co-authored-by: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> --------- Co-authored-by: IndecisiveTurtle <47210458+raphaelthegreat@users.noreply.github.com> --- src/video_core/amdgpu/liverpool.cpp | 55 +++++++++++++++++++---------- src/video_core/amdgpu/liverpool.h | 5 ++- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 246c8c947..bfe99c754 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -726,20 +726,39 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span -Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { +Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vqid) { FIBER_ENTER(acb_task_name[vqid]); - const auto& queue = asc_queues[{vqid}]; + auto& queue = asc_queues[{vqid}]; - auto base_addr = reinterpret_cast(acb.data()); - while (!acb.empty()) { - const auto* header = reinterpret_cast(acb.data()); - const u32 type = header->type; - if (type != 3) { - // No other types of packets were spotted so far - UNREACHABLE_MSG("Invalid PM4 type {}", type); + auto base_addr = reinterpret_cast(acb); + while (acb_dwords > 0) { + auto* header = reinterpret_cast(acb); + u32 next_dw_off = header->type3.NumWords() + 1; + + // If we have a buffered packet, use it. + if (queue.tmp_dwords > 0) [[unlikely]] { + header = reinterpret_cast(queue.tmp_packet.data()); + next_dw_off = header->type3.NumWords() + 1 - queue.tmp_dwords; + std::memcpy(queue.tmp_packet.data() + queue.tmp_dwords, acb, next_dw_off * sizeof(u32)); + queue.tmp_dwords = 0; + } + + // If the packet is split across ring boundary, buffer until next submission + if (next_dw_off > acb_dwords) [[unlikely]] { + std::memcpy(queue.tmp_packet.data(), acb, acb_dwords * sizeof(u32)); + queue.tmp_dwords = acb_dwords; + if constexpr (!is_indirect) { + *queue.read_addr += acb_dwords; + *queue.read_addr %= queue.ring_size_dw; + } + break; + } + + if (header->type != 3) { + // No other types of packets were spotted so far + UNREACHABLE_MSG("Invalid PM4 type {}", header->type.Value()); } - const u32 count = header->type3.NumWords(); const PM4ItOpcode opcode = header->type3.opcode; const auto* it_body = reinterpret_cast(header) + 1; switch (opcode) { @@ -749,8 +768,8 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } case PM4ItOpcode::IndirectBuffer: { const auto* indirect_buffer = reinterpret_cast(header); - auto task = ProcessCompute( - {indirect_buffer->Address(), indirect_buffer->ib_size}, vqid); + auto task = ProcessCompute(indirect_buffer->Address(), + indirect_buffer->ib_size, vqid); RESUME_ASC(task, vqid); while (!task.handle.done()) { @@ -800,7 +819,7 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } case PM4ItOpcode::SetShReg: { const auto* set_data = reinterpret_cast(header); - const auto set_size = (count - 1) * sizeof(u32); + const auto set_size = (header->type3.NumWords() - 1) * sizeof(u32); if (set_data->reg_offset >= 0x200 && set_data->reg_offset <= (0x200 + sizeof(ComputeProgram) / 4)) { @@ -895,14 +914,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } default: UNREACHABLE_MSG("Unknown PM4 type 3 opcode {:#x} with count {}", - static_cast(opcode), count); + static_cast(opcode), header->type3.NumWords()); } - const auto packet_size_dw = header->type3.NumWords() + 1; - acb = NextPacket(acb, packet_size_dw); + acb += next_dw_off; + acb_dwords -= next_dw_off; if constexpr (!is_indirect) { - *queue.read_addr += packet_size_dw; + *queue.read_addr += next_dw_off; *queue.read_addr %= queue.ring_size_dw; } } @@ -969,7 +988,7 @@ void Liverpool::SubmitAsc(u32 gnm_vqid, std::span acb) { auto& queue = mapped_queues[gnm_vqid]; const auto vqid = gnm_vqid - 1; - const auto& task = ProcessCompute(acb, vqid); + const auto& task = ProcessCompute(acb.data(), acb.size(), vqid); { std::scoped_lock lock{queue.m_access}; queue.submits.emplace(task.handle); diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index c18bcd57b..474c04ec2 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -1496,10 +1496,13 @@ public: } struct AscQueueInfo { + static constexpr size_t Pm4BufferSize = 1024; VAddr map_addr; u32* read_addr; u32 ring_size_dw; u32 pipe_id; + std::array tmp_packet; + u32 tmp_dwords; }; Common::SlotVector asc_queues{}; @@ -1541,7 +1544,7 @@ private: Task ProcessGraphics(std::span dcb, std::span ccb); Task ProcessCeUpdate(std::span ccb); template - Task ProcessCompute(std::span acb, u32 vqid); + Task ProcessCompute(const u32* acb, u32 acb_dwords, u32 vqid); void Process(std::stop_token stoken); From 7d0631cf26f983c035c1df9a071a69a28d619001 Mon Sep 17 00:00:00 2001 From: Ked <58560148+k3dr1@users.noreply.github.com> Date: Wed, 26 Mar 2025 20:35:43 +0800 Subject: [PATCH 066/194] Slightly changed how allInstallDirsDisabled is determined (#2688) --- src/qt_gui/main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index d70294e40..34e429368 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -158,8 +158,7 @@ int main(int argc, char* argv[]) { } bool allInstallDirsDisabled = - std::all_of(Config::getGameInstallDirsEnabled().begin(), - Config::getGameInstallDirsEnabled().end(), [](bool val) { return !val; }); + std::ranges::all_of(Config::getGameInstallDirsEnabled(), [](bool val) { return !val; }); // If no game directory is set and no command line argument, prompt for it if (Config::getGameInstallDirs().empty() && allInstallDirsDisabled && From d8204641fafe6d1d23c561434328d9fecdb194be Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 26 Mar 2025 11:03:35 -0500 Subject: [PATCH 067/194] libkernel: Filesystem code cleanup (#2554) * sceKernelOpen: Clean up flag handling * sceKernelOpen: fix params Based on decompilation, the second parameter of _open should be flags. Additionally swaps the return and parameter types to align with our current standards. * sceKernelOpen: Fix errors Based on POSIX spec, if part of the path is missing, ENOENT is the correct return. Additionally, decompilation suggests that open sets errno too. * Fix exports Fixes function exports to align with what they should be, based on what I've seen from decompilation and our module generator. * Proper errno behavior on open Since sceKernelOpen calls posix_open, errno should be set during this process. Simplest way to handle that is to move the actual open code to posix_open and adjust error cases accordingly. * Reorganize open calls, add error log * Improve close Removes the EPERM return, as it makes no sense, and swaps sceKernelClose with posix_close to properly emulate errno behavior. * Fix log on close * posix_open fixups * Readd hack in posix_close It's either this, or removing LLE DiscMap. Or shadow implements posix sockets. * Missing exports Commented out while I gradually work through them all * Remaining placeholder exports * Swap some stuff around I see nothing that suggests "open" only takes two parameters, so this should be completely safe. It's also more accurate to how these are handled in libkernel, and means I won't need to reorganize anything for readv and writev. * Update file_system.cpp * Implement write and posix_write * Oops * Implement posix_readv and sceKernelReadv Also fixes error behavior on readv, as that function shouldn't be returning any kernel error codes. * Move sceKernelUnlink Will deal with this one later, was just annoyed by how it's location doesn't align with the export order. * Cleanup readv * Implement posix_writev and sceKernelWritev Also fixes error behavior on writev, since it shouldn't ever return kernel errors (since our device files return those) * More cleanup on older functions * Swap around sceKernelLseek and posix_lseek This ensures that these have the correct error behavior, and makes their behavior align with the updated implementations for earlier functions. * Update file_system.cpp * Implement read Also fixes error behavior * Swap sceKernelMkdir and posix_mkdir Fixes errno behavior on kernel calls, also fixed some incorrect error returns. * Fix errno behavior on sceKernelRmdir Also reduces function logging to bring it closer to the level of logging seen in other filesystem functions. * Slight clean up of sceKernelStat Fixes error behavior and changes some of the old data types. * Refactor fstat Fixes errno behavior, implements fstat, and shifts exports around based on file position. Might reorganize function locations later though. * Implement posix_ftruncate Implements posix_ftruncate and fixes errno behavior for sceKernelFtruncate * Add missing error conversions for more device functions * Implement posix_rename, fix sceKernelRename errno behavior * Add posix_preadv and posix_pread Also fixes some incorrect error returns, fixes errno behavior, and removes an unnecessary hack. * Fix compile * Implement posix_getdents, getdirentries, and posix_getdirentries Also fixes errno behavior for the kernel variants of these functions. * Fix errno behavior of sceKernelFsync * Implement posix_pwrite and posix_unlink Also fixes errno behavior in related functions. * Update file_system.cpp * Remove SetPosixErrno Ideally, we've handled all possible error conditions before calling these functions, so treat errors in platform-specific code as IO errors and return POSIX_EIO instead. * Update header exports Not sure where these get used, but might as well keep them consistent with the rest of this. * Check if file exists before calling platform-specific code Bloodborne checks if a file doesn't exist using open, checking if it specifically failed with error code ENOENT. To avoid working with platform-specific errnos, add a proper error return for if the file doesn't exist. Fixes a regression in Bloodborne. * Clang Out of all the changes, this is apparently the only thing Clang-Format doesn't like. I'm honestly surprised. * Improve error checks on posix_unlink Just because a file isn't opened doesn't mean the file doesn't exist. Fixes the error returned if host_path.empty(), and removes the error return for when GetFile fails. * Fix the Bloodborne fix * Limit exports to tested functions * More confirmed working exports * Remaining stuff my games can test * FS exports from firmware tests * Bring back missing exports from main I don't have any bootable things that call these, but since they were working well enough on main, they should be fine to readd. * Add export for posix_pread Spotted in Dreams a while back, might as well add it. * Revert "Remove SetPosixErrno" This reverts commit bdfc0c246ce35cde015f3e48a284052ca4caf45e. * Revert SetPosixErrno changes shadow's using it for posix sockets, so significant modifications would introduce unnecessary merge conflicts. * Update comment * Add EACCES errno to SetPosixErrno Seen in Gravity Rush. Also reorganizes the switch case based on the posix errno value, since ordering by errno makes no sense on some OSes. * More export fixups Missed these during my initial pass through FS stuff because they were in kernel.cpp for some reason. * Symbols from FS tests Tested by messing around with firmware elfs, these atleast don't cause any crashes. * Remove inaccurate error behavior Seek can have offsets past the end of a file. Also add logging for two valid whence values that are unsupported on Windows. I'll need to verify that SEEK_HOLE and SEEK_DATA correspond to 3 and 4 respectively, I've yet to check source to verify. * Fix error log Oops * Clang Clang * Remove close hack Since LLE libSceDiscMap is no longer a concern, this hack shouldn't be needed. Since sockets are still stubbed, and close can be used on sockets, I've added a warning log just in case this still occurs in some titles. * Change SetPosixErrno unreachable to warning I changed it to an unreachable in an earlier commit to make testing easier. At this point, having an unreachable for this seems unnecessary, so change it to a warning instead. * Remove Bloodborne hack Games should be able to unlink files that aren't opened file descriptors. As far as I've tested, this doesn't break Bloodborne. --- src/common/io_file.cpp | 5 +- src/common/io_file.h | 2 + src/core/libraries/kernel/file_system.cpp | 862 ++++++++++++++-------- src/core/libraries/kernel/file_system.h | 8 +- src/core/libraries/kernel/kernel.cpp | 28 +- 5 files changed, 570 insertions(+), 335 deletions(-) diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp index 3db78a145..3efadc6ea 100644 --- a/src/common/io_file.cpp +++ b/src/common/io_file.cpp @@ -125,12 +125,15 @@ namespace { [[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) { switch (origin) { case SeekOrigin::SetOrigin: - default: return SEEK_SET; case SeekOrigin::CurrentPosition: return SEEK_CUR; case SeekOrigin::End: return SEEK_END; + default: + LOG_ERROR(Common_Filesystem, "Unsupported origin {}, defaulting to SEEK_SET", + static_cast(origin)); + return SEEK_SET; } } diff --git a/src/common/io_file.h b/src/common/io_file.h index 45787a092..fb20a2bc5 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -61,6 +61,8 @@ enum class SeekOrigin : u32 { SetOrigin, // Seeks from the start of the file. CurrentPosition, // Seeks from the current file pointer position. End, // Seeks from the end of the file. + SeekHole, // Seeks from the start of the next hole in the file. + SeekData, // Seeks from the start of the next non-hole region in the file. }; class IOFile final { diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index ef3c2fe70..3321559ed 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -18,6 +18,7 @@ #include "core/file_sys/fs.h" #include "core/libraries/kernel/file_system.h" #include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/kernel/posix_error.h" #include "core/libraries/libs.h" #include "core/memory.h" #include "kernel.h" @@ -57,7 +58,7 @@ static std::map available_device = { namespace Libraries::Kernel { -int PS4_SYSV_ABI sceKernelOpen(const char* raw_path, int flags, u16 mode) { +s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) { LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {}", raw_path, flags, mode); auto* h = Common::Singleton::Instance(); auto* mnt = Common::Singleton::Instance(); @@ -99,7 +100,8 @@ int PS4_SYSV_ABI sceKernelOpen(const char* raw_path, int flags, u16 mode) { file->m_host_name = mnt->GetHostPath(file->m_guest_name); if (!std::filesystem::is_directory(file->m_host_name)) { // directory doesn't exist h->DeleteHandle(handle); - return ORBIS_KERNEL_ERROR_ENOTDIR; + *__Error() = POSIX_ENOENT; + return -1; } else { if (create) { return handle; // dir already exists @@ -116,61 +118,87 @@ int PS4_SYSV_ABI sceKernelOpen(const char* raw_path, int flags, u16 mode) { } else { file->m_guest_name = path; file->m_host_name = mnt->GetHostPath(file->m_guest_name); + bool exists = std::filesystem::exists(file->m_host_name); int e = 0; - if (read) { - e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Read); - } else if (write && (create || truncate)) { - e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write); - } else if (write && create && append) { // CUSA04729 (appends app0/shaderlist.txt) - e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append); - } else if (rdwr) { - if (create) { // Create an empty file first. - Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Write); + + if (create) { + if (excl && exists) { + // Error if file exists + h->DeleteHandle(handle); + *__Error() = POSIX_EEXIST; + return -1; } - // RW, then scekernelWrite is called and savedata is written just fine now. - e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite); - } else if (write) { - e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write); - } else { - UNREACHABLE(); - } - if (e != 0) { + // Create file if it doesn't exist + Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Write); + } else if (!exists) { + // File to open doesn't exist, return ENOENT h->DeleteHandle(handle); - return ErrnoToSceKernelError(e); + *__Error() = POSIX_ENOENT; + return -1; + } + + if (read) { + // Read only + e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Read); + } else if (write) { + // Write only + if (append) { + e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append); + } else { + e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write); + } + } else if (rdwr) { + // Read and write + if (append) { + e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append); + } else { + e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite); + } + } else { + // Invalid flags + *__Error() = POSIX_EINVAL; + return -1; + } + + if (truncate && e == 0) { + // If the file was opened successfully and truncate was enabled, reduce size to 0 + file->f.SetSize(0); + } + + if (e != 0) { + // Open failed in platform-specific code, errno needs to be converted. + h->DeleteHandle(handle); + SetPosixErrno(e); + return -1; } } file->is_opened = true; return handle; } -int PS4_SYSV_ABI posix_open(const char* path, int flags, /* SceKernelMode*/ u16 mode) { - LOG_INFO(Kernel_Fs, "posix open redirect to sceKernelOpen"); - int result = sceKernelOpen(path, flags, mode); - // Posix calls different only for their return values +s32 PS4_SYSV_ABI posix_open(const char* filename, s32 flags, u16 mode) { + return open(filename, flags, mode); +} + +s32 PS4_SYSV_ABI sceKernelOpen(const char* path, s32 flags, /* SceKernelMode*/ u16 mode) { + s32 result = open(path, flags, mode); if (result < 0) { - ErrSceToPosix(result); - return -1; + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } return result; } -int PS4_SYSV_ABI open(const char* filename, const char* mode) { - LOG_INFO(Kernel_Fs, "open redirect to sceKernelOpen"); - int result = sceKernelOpen(filename, ORBIS_KERNEL_O_RDWR, 0); - if (result < 0) { - return -1; - } - return result; -} - -int PS4_SYSV_ABI sceKernelClose(int d) { - if (d < 3) { // d probably hold an error code - return ORBIS_KERNEL_ERROR_EPERM; +s32 PS4_SYSV_ABI close(s32 fd) { + if (fd < 3) { + // This is technically possible, but it's usually caused by some stubbed function instead. + LOG_WARNING(Kernel_Fs, "called on an std handle, fd = {}", fd); } auto* h = Common::Singleton::Instance(); - auto* file = h->GetFile(d); + auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } if (file->type == Core::FileSys::FileType::Regular) { file->f.Close(); @@ -178,63 +206,54 @@ int PS4_SYSV_ABI sceKernelClose(int d) { file->is_opened = false; LOG_INFO(Kernel_Fs, "Closing {}", file->m_guest_name); // FIXME: Lock file mutex before deleting it? - h->DeleteHandle(d); + h->DeleteHandle(fd); return ORBIS_OK; } -int PS4_SYSV_ABI posix_close(int d) { - int result = sceKernelClose(d); +s32 PS4_SYSV_ABI posix_close(s32 fd) { + return close(fd); +} + +s32 PS4_SYSV_ABI sceKernelClose(s32 fd) { + s32 result = close(fd); if (result < 0) { - LOG_ERROR(Kernel_Pthread, "posix_close: error = {}", result); - ErrSceToPosix(result); - return -1; + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } return result; } -s64 PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes) { +s64 PS4_SYSV_ABI write(s32 fd, const void* buf, size_t nbytes) { auto* h = Common::Singleton::Instance(); - auto* file = h->GetFile(d); + auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { - return file->device->write(buf, nbytes); + s64 result = file->device->write(buf, nbytes); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; } return file->f.WriteRaw(buf, nbytes); } -int PS4_SYSV_ABI sceKernelUnlink(const char* path) { - if (path == nullptr) { - return ORBIS_KERNEL_ERROR_EINVAL; +s64 PS4_SYSV_ABI posix_write(s32 fd, const void* buf, size_t nbytes) { + return write(fd, buf, nbytes); +} + +s64 PS4_SYSV_ABI sceKernelWrite(s32 fd, const void* buf, size_t nbytes) { + s64 result = write(fd, buf, nbytes); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } - - auto* h = Common::Singleton::Instance(); - auto* mnt = Common::Singleton::Instance(); - - bool ro = false; - const auto host_path = mnt->GetHostPath(path, &ro); - if (host_path.empty()) { - return ORBIS_KERNEL_ERROR_EACCES; - } - - if (ro) { - return ORBIS_KERNEL_ERROR_EROFS; - } - - if (std::filesystem::is_directory(host_path)) { - return ORBIS_KERNEL_ERROR_EPERM; - } - - auto* file = h->GetFile(host_path); - if (file != nullptr) { - file->f.Unlink(); - } - - LOG_INFO(Kernel_Fs, "Unlinked {}", path); - return ORBIS_OK; + return result; } size_t ReadFile(Common::FS::IOFile& file, void* buf, size_t nbytes) { @@ -246,59 +265,97 @@ size_t ReadFile(Common::FS::IOFile& file, void* buf, size_t nbytes) { return file.ReadRaw(buf, nbytes); } -size_t PS4_SYSV_ABI _readv(int d, const SceKernelIovec* iov, int iovcnt) { +size_t PS4_SYSV_ABI readv(s32 fd, const SceKernelIovec* iov, s32 iovcnt) { auto* h = Common::Singleton::Instance(); - auto* file = h->GetFile(d); + auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { - int r = file->device->readv(iov, iovcnt); - if (r < 0) { - ErrSceToPosix(r); + size_t result = file->device->readv(iov, iovcnt); + if (result < 0) { + ErrSceToPosix(result); return -1; } - return r; + return result; } size_t total_read = 0; - for (int i = 0; i < iovcnt; i++) { + for (s32 i = 0; i < iovcnt; i++) { total_read += ReadFile(file->f, iov[i].iov_base, iov[i].iov_len); } return total_read; } -size_t PS4_SYSV_ABI _writev(int fd, const SceKernelIovec* iov, int iovcn) { +size_t PS4_SYSV_ABI posix_readv(s32 fd, const SceKernelIovec* iov, s32 iovcnt) { + return readv(fd, iov, iovcnt); +} + +size_t PS4_SYSV_ABI sceKernelReadv(s32 fd, const SceKernelIovec* iov, s32 iovcnt) { + size_t result = readv(fd, iov, iovcnt); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} + +size_t PS4_SYSV_ABI writev(s32 fd, const SceKernelIovec* iov, s32 iovcnt) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { - return file->device->writev(iov, iovcn); + size_t result = file->device->writev(iov, iovcnt); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; } size_t total_written = 0; - for (int i = 0; i < iovcn; i++) { + for (s32 i = 0; i < iovcnt; i++) { total_written += file->f.WriteRaw(iov[i].iov_base, iov[i].iov_len); } return total_written; } -s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) { - LOG_TRACE(Kernel_Fs, "called: offset {} whence {}", offset, whence); +size_t PS4_SYSV_ABI posix_writev(s32 fd, const SceKernelIovec* iov, s32 iovcnt) { + return writev(fd, iov, iovcnt); +} + +size_t PS4_SYSV_ABI sceKernelWritev(s32 fd, const SceKernelIovec* iov, s32 iovcnt) { + size_t result = writev(fd, iov, iovcnt); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} + +s64 PS4_SYSV_ABI posix_lseek(s32 fd, s64 offset, s32 whence) { auto* h = Common::Singleton::Instance(); - auto* file = h->GetFile(d); + auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { - return file->device->lseek(offset, whence); + s64 result = file->device->lseek(offset, whence); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; } Common::FS::SeekOrigin origin{}; @@ -308,53 +365,82 @@ s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) { origin = Common::FS::SeekOrigin::CurrentPosition; } else if (whence == 2) { origin = Common::FS::SeekOrigin::End; + } else if (whence == 3) { + origin = Common::FS::SeekOrigin::SeekHole; + } else if (whence == 4) { + origin = Common::FS::SeekOrigin::SeekData; + } else { + // whence parameter is invalid + *__Error() = POSIX_EINVAL; + return -1; } if (!file->f.Seek(offset, origin)) { - LOG_CRITICAL(Kernel_Fs, "sceKernelLseek: failed to seek"); - return ORBIS_KERNEL_ERROR_EINVAL; + if (errno != 0) { + // Seek failed in platform-specific code, errno needs to be converted. + SetPosixErrno(errno); + return -1; + } + // Shouldn't be possible, but just in case. + return -1; } - return file->f.Tell(); -} -s64 PS4_SYSV_ABI posix_lseek(int d, s64 offset, int whence) { - s64 result = sceKernelLseek(d, offset, whence); + s64 result = file->f.Tell(); if (result < 0) { - LOG_ERROR(Kernel_Pthread, "posix_lseek: error = {}", result); - ErrSceToPosix(result); + // Tell failed in platform-specific code, errno needs to be converted. + SetPosixErrno(errno); return -1; } return result; } -s64 PS4_SYSV_ABI sceKernelRead(int d, void* buf, size_t nbytes) { +s64 PS4_SYSV_ABI sceKernelLseek(s32 fd, s64 offset, s32 whence) { + s64 result = posix_lseek(fd, offset, whence); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} + +s64 PS4_SYSV_ABI read(s32 fd, void* buf, size_t nbytes) { auto* h = Common::Singleton::Instance(); - auto* file = h->GetFile(d); + auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { - return file->device->read(buf, nbytes); + s64 result = file->device->read(buf, nbytes); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; } return ReadFile(file->f, buf, nbytes); } -int PS4_SYSV_ABI posix_read(int d, void* buf, size_t nbytes) { - int result = sceKernelRead(d, buf, nbytes); +s64 PS4_SYSV_ABI posix_read(s32 fd, void* buf, size_t nbytes) { + return read(fd, buf, nbytes); +} + +s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, size_t nbytes) { + s64 result = read(fd, buf, nbytes); if (result < 0) { - LOG_ERROR(Kernel_Pthread, "posix_read: error = {}", result); - ErrSceToPosix(result); - return -1; + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } return result; } -int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) { +s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) { LOG_INFO(Kernel_Fs, "path = {} mode = {}", path, mode); if (path == nullptr) { - return ORBIS_KERNEL_ERROR_EINVAL; + *__Error() = POSIX_ENOTDIR; + return -1; } auto* mnt = Common::Singleton::Instance(); @@ -362,88 +448,79 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) { const auto dir_name = mnt->GetHostPath(path, &ro); if (std::filesystem::exists(dir_name)) { - return ORBIS_KERNEL_ERROR_EEXIST; + *__Error() = POSIX_EEXIST; + return -1; } if (ro) { - return ORBIS_KERNEL_ERROR_EROFS; + *__Error() = POSIX_EROFS; + return -1; } // CUSA02456: path = /aotl after sceSaveDataMount(mode = 1) std::error_code ec; if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) { - return ORBIS_KERNEL_ERROR_EIO; + *__Error() = POSIX_EIO; + return -1; } if (!std::filesystem::exists(dir_name)) { - return ORBIS_KERNEL_ERROR_ENOENT; + *__Error() = POSIX_ENOENT; + return -1; } return ORBIS_OK; } -int PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) { - int result = sceKernelMkdir(path, mode); +s32 PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) { + s32 result = posix_mkdir(path, mode); if (result < 0) { - LOG_ERROR(Kernel_Pthread, "posix_mkdir: error = {}", result); - ErrSceToPosix(result); - return -1; + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } return result; } -int PS4_SYSV_ABI sceKernelRmdir(const char* path) { +s32 PS4_SYSV_ABI posix_rmdir(const char* path) { auto* mnt = Common::Singleton::Instance(); bool ro = false; const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro); - if (dir_name.empty()) { - LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, permission denied", - fmt::UTF(dir_name.u8string())); - return ORBIS_KERNEL_ERROR_EACCES; + if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) { + *__Error() = POSIX_ENOTDIR; + return -1; } if (ro) { - LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, directory is read only", - fmt::UTF(dir_name.u8string())); - return ORBIS_KERNEL_ERROR_EROFS; - } - - if (!std::filesystem::is_directory(dir_name)) { - LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, path is not a directory", - fmt::UTF(dir_name.u8string())); - return ORBIS_KERNEL_ERROR_ENOTDIR; + *__Error() = POSIX_EROFS; + return -1; } if (!std::filesystem::exists(dir_name)) { - LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, no such file or directory", - fmt::UTF(dir_name.u8string())); - return ORBIS_KERNEL_ERROR_ENOENT; + *__Error() = POSIX_ENOENT; + return -1; } std::error_code ec; - int result = std::filesystem::remove_all(dir_name, ec); + s32 result = std::filesystem::remove_all(dir_name, ec); - if (!ec) { - LOG_INFO(Kernel_Fs, "Removed directory: {}", fmt::UTF(dir_name.u8string())); - return ORBIS_OK; + if (ec) { + *__Error() = POSIX_EIO; + return -1; } - LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, error_code={}", - fmt::UTF(dir_name.u8string()), ec.message()); - return ErrnoToSceKernelError(ec.value()); + return ORBIS_OK; } -int PS4_SYSV_ABI posix_rmdir(const char* path) { - int result = sceKernelRmdir(path); +s32 PS4_SYSV_ABI sceKernelRmdir(const char* path) { + s32 result = posix_rmdir(path); if (result < 0) { - LOG_ERROR(Kernel_Pthread, "posix_rmdir: error = {}", result); - ErrSceToPosix(result); - return -1; + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } return result; } -int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { +s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) { LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path); auto* mnt = Common::Singleton::Instance(); bool ro = false; @@ -452,7 +529,8 @@ int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { const bool is_dir = std::filesystem::is_directory(path_name); const bool is_file = std::filesystem::is_regular_file(path_name); if (!is_dir && !is_file) { - return ORBIS_KERNEL_ERROR_ENOENT; + *__Error() = POSIX_ENOENT; + return -1; } if (std::filesystem::is_directory(path_name)) { sb->st_mode = 0000777u | 0040000u; @@ -462,7 +540,7 @@ int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { // TODO incomplete } else { sb->st_mode = 0000777u | 0100000u; - sb->st_size = static_cast(std::filesystem::file_size(path_name)); + sb->st_size = static_cast(std::filesystem::file_size(path_name)); sb->st_blksize = 512; sb->st_blocks = (sb->st_size + 511) / 512; // TODO incomplete @@ -474,17 +552,16 @@ int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { return ORBIS_OK; } -int PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) { - int result = sceKernelStat(path, sb); +s32 PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { + s32 result = posix_stat(path, sb); if (result < 0) { - LOG_ERROR(Kernel_Pthread, "posix_stat: error = {}", result); - ErrSceToPosix(result); - return -1; + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } return result; } -int PS4_SYSV_ABI sceKernelCheckReachability(const char* path) { +s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) { auto* mnt = Common::Singleton::Instance(); std::string_view guest_path{path}; for (const auto& prefix : available_device | std::views::keys) { @@ -499,23 +576,165 @@ int PS4_SYSV_ABI sceKernelCheckReachability(const char* path) { return ORBIS_OK; } -s64 PS4_SYSV_ABI sceKernelPreadv(int d, SceKernelIovec* iov, int iovcnt, s64 offset) { - if (d < 3) { - return ORBIS_KERNEL_ERROR_EPERM; +s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) { + LOG_INFO(Kernel_Fs, "(PARTIAL) fd = {}", fd); + if (sb == nullptr) { + *__Error() = POSIX_EFAULT; + return -1; } + auto* h = Common::Singleton::Instance(); + auto* file = h->GetFile(fd); + if (file == nullptr) { + *__Error() = POSIX_EBADF; + return -1; + } + std::memset(sb, 0, sizeof(OrbisKernelStat)); + + switch (file->type) { + case Core::FileSys::FileType::Device: { + s32 result = file->device->fstat(sb); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; + } + case Core::FileSys::FileType::Regular: { + sb->st_mode = 0000777u | 0100000u; + sb->st_size = file->f.GetSize(); + sb->st_blksize = 512; + sb->st_blocks = (sb->st_size + 511) / 512; + // TODO incomplete + break; + } + case Core::FileSys::FileType::Directory: { + sb->st_mode = 0000777u | 0040000u; + sb->st_size = 0; + sb->st_blksize = 512; + sb->st_blocks = 0; + // TODO incomplete + break; + } + default: + UNREACHABLE(); + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI posix_fstat(s32 fd, OrbisKernelStat* sb) { + return fstat(fd, sb); +} + +s32 PS4_SYSV_ABI sceKernelFstat(s32 fd, OrbisKernelStat* sb) { + s32 result = fstat(fd, sb); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} + +s32 PS4_SYSV_ABI posix_ftruncate(s32 fd, s64 length) { + auto* h = Common::Singleton::Instance(); + auto* file = h->GetFile(fd); + + if (file == nullptr) { + *__Error() = POSIX_EBADF; + return -1; + } + + if (file->type == Core::FileSys::FileType::Device) { + s32 result = file->device->ftruncate(length); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; + } + + if (file->m_host_name.empty()) { + *__Error() = POSIX_EACCES; + return -1; + } + + file->f.SetSize(length); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceKernelFtruncate(s32 fd, s64 length) { + s32 result = posix_ftruncate(fd, length); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} + +s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) { + auto* mnt = Common::Singleton::Instance(); + bool ro = false; + const auto src_path = mnt->GetHostPath(from, &ro); + if (!std::filesystem::exists(src_path)) { + *__Error() = POSIX_ENOENT; + return -1; + } + if (ro) { + *__Error() = POSIX_EROFS; + return -1; + } + const auto dst_path = mnt->GetHostPath(to, &ro); + if (ro) { + *__Error() = POSIX_EROFS; + return -1; + } + const bool src_is_dir = std::filesystem::is_directory(src_path); + const bool dst_is_dir = std::filesystem::is_directory(dst_path); + if (src_is_dir && !dst_is_dir) { + *__Error() = POSIX_ENOTDIR; + return -1; + } + if (!src_is_dir && dst_is_dir) { + *__Error() = POSIX_EISDIR; + return -1; + } + if (dst_is_dir && !std::filesystem::is_empty(dst_path)) { + *__Error() = POSIX_ENOTEMPTY; + return -1; + } + std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) { + s32 result = posix_rename(from, to); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} + +s64 PS4_SYSV_ABI posix_preadv(s32 fd, SceKernelIovec* iov, s32 iovcnt, s64 offset) { if (offset < 0) { - return ORBIS_KERNEL_ERROR_EINVAL; + *__Error() = POSIX_EINVAL; + return -1; } auto* h = Common::Singleton::Instance(); - auto* file = h->GetFile(d); + auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { - return file->device->preadv(iov, iovcnt, offset); + s64 result = file->device->preadv(iov, iovcnt, offset); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; } const s64 pos = file->f.Tell(); @@ -523,8 +742,8 @@ s64 PS4_SYSV_ABI sceKernelPreadv(int d, SceKernelIovec* iov, int iovcnt, s64 off file->f.Seek(pos); }; if (!file->f.Seek(offset)) { - LOG_CRITICAL(Kernel_Fs, "failed to seek"); - return ORBIS_KERNEL_ERROR_EINVAL; + *__Error() = POSIX_EIO; + return -1; } size_t total_read = 0; for (int i = 0; i < iovcnt; i++) { @@ -533,118 +752,72 @@ s64 PS4_SYSV_ABI sceKernelPreadv(int d, SceKernelIovec* iov, int iovcnt, s64 off return total_read; } -s64 PS4_SYSV_ABI sceKernelPread(int d, void* buf, size_t nbytes, s64 offset) { - SceKernelIovec iovec{buf, nbytes}; - return sceKernelPreadv(d, &iovec, 1, offset); -} - -int PS4_SYSV_ABI sceKernelFStat(int fd, OrbisKernelStat* sb) { - LOG_INFO(Kernel_Fs, "(PARTIAL) fd = {}", fd); - if (fd < 3) { - return ORBIS_KERNEL_ERROR_EPERM; - } - if (sb == nullptr) { - return ORBIS_KERNEL_ERROR_EFAULT; - } - auto* h = Common::Singleton::Instance(); - auto* file = h->GetFile(fd); - if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; - } - std::memset(sb, 0, sizeof(OrbisKernelStat)); - - switch (file->type) { - case Core::FileSys::FileType::Device: - return file->device->fstat(sb); - case Core::FileSys::FileType::Regular: - sb->st_mode = 0000777u | 0100000u; - sb->st_size = file->f.GetSize(); - sb->st_blksize = 512; - sb->st_blocks = (sb->st_size + 511) / 512; - // TODO incomplete - break; - case Core::FileSys::FileType::Directory: - sb->st_mode = 0000777u | 0040000u; - sb->st_size = 0; - sb->st_blksize = 512; - sb->st_blocks = 0; - // TODO incomplete - break; - default: - UNREACHABLE(); - } - return ORBIS_OK; -} - -int PS4_SYSV_ABI posix_fstat(int fd, OrbisKernelStat* sb) { - int result = sceKernelFStat(fd, sb); +s64 PS4_SYSV_ABI sceKernelPreadv(s32 fd, SceKernelIovec* iov, s32 iovcnt, s64 offset) { + s64 result = posix_preadv(fd, iov, iovcnt, offset); if (result < 0) { - LOG_ERROR(Kernel_Pthread, "posix_fstat: error = {}", result); - ErrSceToPosix(result); - return -1; + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } return result; } -s32 PS4_SYSV_ABI sceKernelFsync(int fd) { +s64 PS4_SYSV_ABI posix_pread(s32 fd, void* buf, size_t nbytes, s64 offset) { + SceKernelIovec iovec{buf, nbytes}; + return posix_preadv(fd, &iovec, 1, offset); +} + +s64 PS4_SYSV_ABI sceKernelPread(s32 fd, void* buf, size_t nbytes, s64 offset) { + SceKernelIovec iovec{buf, nbytes}; + return sceKernelPreadv(fd, &iovec, 1, offset); +} + +s32 PS4_SYSV_ABI posix_fsync(s32 fd) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } if (file->type == Core::FileSys::FileType::Device) { - return file->device->fsync(); + s32 result = file->device->fsync(); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; } file->f.Flush(); return ORBIS_OK; } -s32 PS4_SYSV_ABI posix_fsync(int fd) { - s32 result = sceKernelFsync(fd); +s32 PS4_SYSV_ABI sceKernelFsync(s32 fd) { + s32 result = posix_fsync(fd); if (result < 0) { - LOG_ERROR(Kernel_Pthread, "posix_fsync: error = {}", result); - ErrSceToPosix(result); - return -1; + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } return result; } -int PS4_SYSV_ABI sceKernelFtruncate(int fd, s64 length) { - auto* h = Common::Singleton::Instance(); - auto* file = h->GetFile(fd); - - if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; - } - - if (file->type == Core::FileSys::FileType::Device) { - return file->device->ftruncate(length); - } - - if (file->m_host_name.empty()) { - return ORBIS_KERNEL_ERROR_EACCES; - } - - file->f.SetSize(length); - return ORBIS_OK; -} - -static int GetDents(int fd, char* buf, int nbytes, s64* basep) { - if (fd < 3) { - return ORBIS_KERNEL_ERROR_EBADF; - } - +static s32 GetDents(s32 fd, char* buf, s32 nbytes, s64* basep) { if (buf == nullptr) { - return ORBIS_KERNEL_ERROR_EFAULT; + *__Error() = POSIX_EFAULT; + return -1; } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } if (file->type == Core::FileSys::FileType::Device) { - return file->device->getdents(buf, nbytes, basep); + s32 result = file->device->getdents(buf, nbytes, basep); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; } if (file->dirents_index == file->dirents.size()) { @@ -652,7 +825,8 @@ static int GetDents(int fd, char* buf, int nbytes, s64* basep) { } if (file->type != Core::FileSys::FileType::Directory || nbytes < 512 || file->dirents_index > file->dirents.size()) { - return ORBIS_KERNEL_ERROR_EINVAL; + *__Error() = POSIX_EINVAL; + return -1; } const auto& entry = file->dirents.at(file->dirents_index++); auto str = entry.name; @@ -673,118 +847,178 @@ static int GetDents(int fd, char* buf, int nbytes, s64* basep) { return sizeof(OrbisKernelDirent); } -int PS4_SYSV_ABI sceKernelGetdents(int fd, char* buf, int nbytes) { +s32 PS4_SYSV_ABI posix_getdents(s32 fd, char* buf, s32 nbytes) { return GetDents(fd, buf, nbytes, nullptr); } -int PS4_SYSV_ABI sceKernelGetdirentries(int fd, char* buf, int nbytes, s64* basep) { +s32 PS4_SYSV_ABI sceKernelGetdents(s32 fd, char* buf, s32 nbytes) { + s32 result = GetDents(fd, buf, nbytes, nullptr); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} + +s32 PS4_SYSV_ABI getdirentries(s32 fd, char* buf, s32 nbytes, s64* basep) { return GetDents(fd, buf, nbytes, basep); } -s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) { - if (d < 3) { - return ORBIS_KERNEL_ERROR_EPERM; +s32 PS4_SYSV_ABI posix_getdirentries(s32 fd, char* buf, s32 nbytes, s64* basep) { + return GetDents(fd, buf, nbytes, basep); +} + +s32 PS4_SYSV_ABI sceKernelGetdirentries(s32 fd, char* buf, s32 nbytes, s64* basep) { + s32 result = GetDents(fd, buf, nbytes, basep); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); } + return result; +} + +s64 PS4_SYSV_ABI posix_pwrite(s32 fd, void* buf, size_t nbytes, s64 offset) { if (offset < 0) { - return ORBIS_KERNEL_ERROR_EINVAL; + *__Error() = POSIX_EINVAL; + return -1; } auto* h = Common::Singleton::Instance(); - auto* file = h->GetFile(d); + auto* file = h->GetFile(fd); if (file == nullptr) { - return ORBIS_KERNEL_ERROR_EBADF; + *__Error() = POSIX_EBADF; + return -1; } std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { - return file->device->pwrite(buf, nbytes, offset); + s64 result = file->device->pwrite(buf, nbytes, offset); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; } const s64 pos = file->f.Tell(); SCOPE_EXIT { file->f.Seek(pos); }; if (!file->f.Seek(offset)) { - LOG_CRITICAL(Kernel_Fs, "sceKernelPwrite: failed to seek"); - return ORBIS_KERNEL_ERROR_EINVAL; + *__Error() = POSIX_EIO; + return -1; } return file->f.WriteRaw(buf, nbytes); } -s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) { +s64 PS4_SYSV_ABI sceKernelPwrite(s32 fd, void* buf, size_t nbytes, s64 offset) { + s64 result = posix_pwrite(fd, buf, nbytes, offset); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} + +s32 PS4_SYSV_ABI posix_unlink(const char* path) { + if (path == nullptr) { + *__Error() = POSIX_EINVAL; + return -1; + } + + auto* h = Common::Singleton::Instance(); auto* mnt = Common::Singleton::Instance(); + bool ro = false; - const auto src_path = mnt->GetHostPath(from, &ro); - if (!std::filesystem::exists(src_path)) { - return ORBIS_KERNEL_ERROR_ENOENT; + const auto host_path = mnt->GetHostPath(path, &ro); + if (host_path.empty()) { + *__Error() = POSIX_ENOENT; + return -1; } + if (ro) { - return ORBIS_KERNEL_ERROR_EROFS; + *__Error() = POSIX_EROFS; + return -1; } - const auto dst_path = mnt->GetHostPath(to, &ro); - if (ro) { - return ORBIS_KERNEL_ERROR_EROFS; + + if (std::filesystem::is_directory(host_path)) { + *__Error() = POSIX_EPERM; + return -1; } - const bool src_is_dir = std::filesystem::is_directory(src_path); - const bool dst_is_dir = std::filesystem::is_directory(dst_path); - if (src_is_dir && !dst_is_dir) { - return ORBIS_KERNEL_ERROR_ENOTDIR; + + auto* file = h->GetFile(host_path); + if (file == nullptr) { + // File to unlink hasn't been opened, manually open and unlink it. + Common::FS::IOFile file(host_path, Common::FS::FileAccessMode::ReadWrite); + file.Unlink(); + } else { + file->f.Unlink(); } - if (!src_is_dir && dst_is_dir) { - return ORBIS_KERNEL_ERROR_EISDIR; - } - if (dst_is_dir && !std::filesystem::is_empty(dst_path)) { - return ORBIS_KERNEL_ERROR_ENOTEMPTY; - } - std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing); + + LOG_INFO(Kernel_Fs, "Unlinked {}", path); return ORBIS_OK; } -void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("1G3lF1Gg1k8", "libkernel", 1, "libkernel", 1, 1, sceKernelOpen); - LIB_FUNCTION("wuCroIGjt2g", "libScePosix", 1, "libkernel", 1, 1, posix_open); - LIB_FUNCTION("wuCroIGjt2g", "libkernel", 1, "libkernel", 1, 1, open); - LIB_FUNCTION("UK2Tl2DWUns", "libkernel", 1, "libkernel", 1, 1, sceKernelClose); - LIB_FUNCTION("bY-PO6JhzhQ", "libkernel", 1, "libkernel", 1, 1, posix_close); - LIB_FUNCTION("bY-PO6JhzhQ", "libScePosix", 1, "libkernel", 1, 1, posix_close); - LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite); +s32 PS4_SYSV_ABI sceKernelUnlink(const char* path) { + s32 result = posix_unlink(path); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} - LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, _readv); - LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, _writev); - LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 1, "libkernel", 1, 1, posix_lseek); +void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("6c3rCVE-fTU", "libkernel", 1, "libkernel", 1, 1, open); + LIB_FUNCTION("wuCroIGjt2g", "libScePosix", 1, "libkernel", 1, 1, posix_open); + LIB_FUNCTION("wuCroIGjt2g", "libkernel", 1, "libkernel", 1, 1, posix_open); + LIB_FUNCTION("1G3lF1Gg1k8", "libkernel", 1, "libkernel", 1, 1, sceKernelOpen); + LIB_FUNCTION("NNtFaKJbPt0", "libkernel", 1, "libkernel", 1, 1, close); + LIB_FUNCTION("bY-PO6JhzhQ", "libScePosix", 1, "libkernel", 1, 1, posix_close); + LIB_FUNCTION("bY-PO6JhzhQ", "libkernel", 1, "libkernel", 1, 1, posix_close); + LIB_FUNCTION("UK2Tl2DWUns", "libkernel", 1, "libkernel", 1, 1, sceKernelClose); + LIB_FUNCTION("FxVZqBAA7ks", "libkernel", 1, "libkernel", 1, 1, write); + LIB_FUNCTION("FN4gaPmuFV8", "libScePosix", 1, "libkernel", 1, 1, posix_write); + LIB_FUNCTION("FN4gaPmuFV8", "libkernel", 1, "libkernel", 1, 1, posix_write); + LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite); + LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, readv); + LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, writev); LIB_FUNCTION("Oy6IpwgtYOk", "libScePosix", 1, "libkernel", 1, 1, posix_lseek); + LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 1, "libkernel", 1, 1, posix_lseek); LIB_FUNCTION("oib76F-12fk", "libkernel", 1, "libkernel", 1, 1, sceKernelLseek); - LIB_FUNCTION("Cg4srZ6TKbU", "libkernel", 1, "libkernel", 1, 1, sceKernelRead); + LIB_FUNCTION("DRuBt2pvICk", "libkernel", 1, "libkernel", 1, 1, read); LIB_FUNCTION("AqBioC2vF3I", "libScePosix", 1, "libkernel", 1, 1, posix_read); - LIB_FUNCTION("1-LFLmRFxxM", "libkernel", 1, "libkernel", 1, 1, sceKernelMkdir); + LIB_FUNCTION("AqBioC2vF3I", "libkernel", 1, "libkernel", 1, 1, posix_read); + LIB_FUNCTION("Cg4srZ6TKbU", "libkernel", 1, "libkernel", 1, 1, sceKernelRead); LIB_FUNCTION("JGMio+21L4c", "libScePosix", 1, "libkernel", 1, 1, posix_mkdir); LIB_FUNCTION("JGMio+21L4c", "libkernel", 1, "libkernel", 1, 1, posix_mkdir); - LIB_FUNCTION("naInUjYt3so", "libkernel", 1, "libkernel", 1, 1, sceKernelRmdir); + LIB_FUNCTION("1-LFLmRFxxM", "libkernel", 1, "libkernel", 1, 1, sceKernelMkdir); LIB_FUNCTION("c7ZnT7V1B98", "libScePosix", 1, "libkernel", 1, 1, posix_rmdir); LIB_FUNCTION("c7ZnT7V1B98", "libkernel", 1, "libkernel", 1, 1, posix_rmdir); - LIB_FUNCTION("eV9wAD2riIA", "libkernel", 1, "libkernel", 1, 1, sceKernelStat); - LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFStat); - LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat); - LIB_FUNCTION("mqQMh1zPPT8", "libkernel", 1, "libkernel", 1, 1, posix_fstat); - LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate); - LIB_FUNCTION("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename); - + LIB_FUNCTION("naInUjYt3so", "libkernel", 1, "libkernel", 1, 1, sceKernelRmdir); LIB_FUNCTION("E6ao34wPw+U", "libScePosix", 1, "libkernel", 1, 1, posix_stat); LIB_FUNCTION("E6ao34wPw+U", "libkernel", 1, "libkernel", 1, 1, posix_stat); - LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread); - LIB_FUNCTION("yTj62I7kw4s", "libkernel", 1, "libkernel", 1, 1, sceKernelPreadv); + LIB_FUNCTION("eV9wAD2riIA", "libkernel", 1, "libkernel", 1, 1, sceKernelStat); LIB_FUNCTION("uWyW3v98sU4", "libkernel", 1, "libkernel", 1, 1, sceKernelCheckReachability); - LIB_FUNCTION("fTx66l5iWIA", "libkernel", 1, "libkernel", 1, 1, sceKernelFsync); - LIB_FUNCTION("juWbTNM+8hw", "libkernel", 1, "libkernel", 1, 1, posix_fsync); + LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat); + LIB_FUNCTION("mqQMh1zPPT8", "libkernel", 1, "libkernel", 1, 1, posix_fstat); + LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFstat); + LIB_FUNCTION("ih4CD9-gghM", "libkernel", 1, "libkernel", 1, 1, posix_ftruncate); + LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate); + LIB_FUNCTION("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename); + LIB_FUNCTION("yTj62I7kw4s", "libkernel", 1, "libkernel", 1, 1, sceKernelPreadv); + LIB_FUNCTION("ezv-RSBNKqI", "libScePosix", 1, "libkernel", 1, 1, posix_pread); + LIB_FUNCTION("ezv-RSBNKqI", "libkernel", 1, "libkernel", 1, 1, posix_pread); + LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread); LIB_FUNCTION("juWbTNM+8hw", "libScePosix", 1, "libkernel", 1, 1, posix_fsync); + LIB_FUNCTION("juWbTNM+8hw", "libkernel", 1, "libkernel", 1, 1, posix_fsync); + LIB_FUNCTION("fTx66l5iWIA", "libkernel", 1, "libkernel", 1, 1, sceKernelFsync); LIB_FUNCTION("j2AIqSqJP0w", "libkernel", 1, "libkernel", 1, 1, sceKernelGetdents); + LIB_FUNCTION("sfKygSjIbI8", "libkernel", 1, "libkernel", 1, 1, getdirentries); LIB_FUNCTION("taRWhTJFTgE", "libkernel", 1, "libkernel", 1, 1, sceKernelGetdirentries); + LIB_FUNCTION("C2kJ-byS5rM", "libkernel", 1, "libkernel", 1, 1, posix_pwrite); LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPwrite); LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", 1, 1, sceKernelUnlink); - - // openOrbis (to check if it is valid out of OpenOrbis - LIB_FUNCTION("6c3rCVE-fTU", "libkernel", 1, "libkernel", 1, 1, - posix_open); // _open should be equal to open function } } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/file_system.h b/src/core/libraries/kernel/file_system.h index 1838df2fe..77ce3ec3d 100644 --- a/src/core/libraries/kernel/file_system.h +++ b/src/core/libraries/kernel/file_system.h @@ -65,10 +65,10 @@ constexpr int ORBIS_KERNEL_O_DSYNC = 0x1000; constexpr int ORBIS_KERNEL_O_DIRECT = 0x00010000; constexpr int ORBIS_KERNEL_O_DIRECTORY = 0x00020000; -s64 PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes); -s64 PS4_SYSV_ABI sceKernelRead(int d, void* buf, size_t nbytes); -s64 PS4_SYSV_ABI sceKernelPread(int d, void* buf, size_t nbytes, s64 offset); -s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset); +s64 PS4_SYSV_ABI sceKernelWrite(s32 fd, const void* buf, size_t nbytes); +s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, size_t nbytes); +s64 PS4_SYSV_ABI sceKernelPread(s32 fd, void* buf, size_t nbytes, s64 offset); +s64 PS4_SYSV_ABI sceKernelPwrite(s32 fd, void* buf, size_t nbytes, s64 offset); void RegisterFileSystem(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index 2b7735219..9227cf45a 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -85,17 +85,23 @@ int ErrnoToSceKernelError(int error) { } void SetPosixErrno(int e) { - // Some error numbers are different between supported OSes or the PS4 + // Some error numbers are different between supported OSes switch (e) { case EPERM: g_posix_errno = POSIX_EPERM; break; - case EAGAIN: - g_posix_errno = POSIX_EAGAIN; + case ENOENT: + g_posix_errno = POSIX_ENOENT; + break; + case EDEADLK: + g_posix_errno = POSIX_EDEADLK; break; case ENOMEM: g_posix_errno = POSIX_ENOMEM; break; + case EACCES: + g_posix_errno = POSIX_EACCES; + break; case EINVAL: g_posix_errno = POSIX_EINVAL; break; @@ -105,13 +111,14 @@ void SetPosixErrno(int e) { case ERANGE: g_posix_errno = POSIX_ERANGE; break; - case EDEADLK: - g_posix_errno = POSIX_EDEADLK; + case EAGAIN: + g_posix_errno = POSIX_EAGAIN; break; case ETIMEDOUT: g_posix_errno = POSIX_ETIMEDOUT; break; default: + LOG_WARNING(Kernel, "Unhandled errno {}", e); g_posix_errno = e; } } @@ -133,14 +140,6 @@ void PS4_SYSV_ABI sceLibcHeapGetTraceInfo(HeapInfoInfo* info) { info->getSegmentInfo = 0; } -s64 PS4_SYSV_ABI ps4__write(int d, const char* buf, std::size_t nbytes) { - return sceKernelWrite(d, buf, nbytes); -} - -s64 PS4_SYSV_ABI ps4__read(int d, void* buf, u64 nbytes) { - return sceKernelRead(d, buf, nbytes); -} - struct OrbisKernelUuid { u32 timeLow; u16 timeMid; @@ -229,13 +228,10 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("Xjoosiw+XPI", "libkernel", 1, "libkernel", 1, 1, sceKernelUuidCreate); LIB_FUNCTION("Ou3iL1abvng", "libkernel", 1, "libkernel", 1, 1, stack_chk_fail); LIB_FUNCTION("9BcDykPmo1I", "libkernel", 1, "libkernel", 1, 1, __Error); - LIB_FUNCTION("DRuBt2pvICk", "libkernel", 1, "libkernel", 1, 1, ps4__read); LIB_FUNCTION("k+AXqu2-eBc", "libkernel", 1, "libkernel", 1, 1, posix_getpagesize); LIB_FUNCTION("k+AXqu2-eBc", "libScePosix", 1, "libkernel", 1, 1, posix_getpagesize); LIB_FUNCTION("NWtTN10cJzE", "libSceLibcInternalExt", 1, "libSceLibcInternal", 1, 1, sceLibcHeapGetTraceInfo); - LIB_FUNCTION("FxVZqBAA7ks", "libkernel", 1, "libkernel", 1, 1, ps4__write); - LIB_FUNCTION("FN4gaPmuFV8", "libScePosix", 1, "libkernel", 1, 1, ps4__write); } } // namespace Libraries::Kernel From 69cb4d5787a25c98ec7e89a6c17f0c2acef1b1c8 Mon Sep 17 00:00:00 2001 From: Ked <58560148+k3dr1@users.noreply.github.com> Date: Thu, 27 Mar 2025 00:04:05 +0800 Subject: [PATCH 068/194] Show a dialog only if no game directories are set (#2690) * Slightly changed how allInstallDirsDisabled is determined * Show a dialog only if no game directories are set * Changed a comment * Fixed formatting --- src/qt_gui/main.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 34e429368..bd9dca6ce 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -157,12 +157,8 @@ int main(int argc, char* argv[]) { } } - bool allInstallDirsDisabled = - std::ranges::all_of(Config::getGameInstallDirsEnabled(), [](bool val) { return !val; }); - - // If no game directory is set and no command line argument, prompt for it - if (Config::getGameInstallDirs().empty() && allInstallDirsDisabled && - !has_command_line_argument) { + // If no game directories are set and no command line argument, prompt for it + if (Config::getGameInstallDirsEnabled().empty() && !has_command_line_argument) { GameInstallDialog dlg; dlg.exec(); } From 90b949b8ceded997d3066201fe07d6e04fd439fb Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 26 Mar 2025 18:04:49 +0200 Subject: [PATCH 069/194] New Crowdin updates (#2679) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Russian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Swedish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Arabic) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Arabic) * New translations en_us.ts (Turkish) * New translations en_us.ts (Turkish) * New translations en_us.ts (Arabic) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Albanian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Arabic) * New translations en_us.ts (Arabic) * New translations en_us.ts (Polish) * New translations en_us.ts (Polish) * New translations en_us.ts (Arabic) --- src/qt_gui/translations/ar_SA.ts | 382 ++++++++++++++++--------------- src/qt_gui/translations/da_DK.ts | 12 + src/qt_gui/translations/de_DE.ts | 12 + src/qt_gui/translations/el_GR.ts | 12 + src/qt_gui/translations/es_ES.ts | 12 + src/qt_gui/translations/fa_IR.ts | 12 + src/qt_gui/translations/fi_FI.ts | 12 + src/qt_gui/translations/fr_FR.ts | 12 + src/qt_gui/translations/hu_HU.ts | 12 + src/qt_gui/translations/id_ID.ts | 12 + src/qt_gui/translations/it_IT.ts | 12 + src/qt_gui/translations/ja_JP.ts | 12 + src/qt_gui/translations/ko_KR.ts | 12 + src/qt_gui/translations/lt_LT.ts | 34 ++- src/qt_gui/translations/nb_NO.ts | 12 + src/qt_gui/translations/nl_NL.ts | 12 + src/qt_gui/translations/pl_PL.ts | 166 +++++++------- src/qt_gui/translations/pt_BR.ts | 12 + src/qt_gui/translations/pt_PT.ts | 98 ++++---- src/qt_gui/translations/ro_RO.ts | 12 + src/qt_gui/translations/ru_RU.ts | 12 + src/qt_gui/translations/sq_AL.ts | 12 + src/qt_gui/translations/sv_SE.ts | 12 + src/qt_gui/translations/tr_TR.ts | 12 + src/qt_gui/translations/uk_UA.ts | 12 + src/qt_gui/translations/vi_VN.ts | 12 + src/qt_gui/translations/zh_CN.ts | 12 + src/qt_gui/translations/zh_TW.ts | 12 + 28 files changed, 652 insertions(+), 316 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 090cd4c26..ac6920ea0 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -22,7 +22,7 @@ CheatsPatches Cheats / Patches for - Cheats / Patches for + الغِشّ / التصحيحات Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n @@ -407,194 +407,194 @@ ControlSettings Configure Controls - Configure Controls + تعديل عناصر التحكم D-Pad - D-Pad + الأسهم+عصا التحكم Up - Up + فوق Left - Left + يسار Right - Right + يمين Down - Down + تحت Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + مدى تسجيل الإدخال للعصا اليسرى (التلقائي:2 حد أقصى:127) Left Deadzone - Left Deadzone + إعدادات مدى تسجيل الإدخال لعصا التحكم اليسرى Left Stick - Left Stick + عصا التحكم اليسرى Config Selection - Config Selection + تحديد الإعدادات Common Config - Common Config + إعدادات عامة Use per-game configs - Use per-game configs + استخدام إعدادات كل لُعْبَة L1 / LB - L1 / LB + L1 / LB L2 / LT - L2 / LT + L2 / LT Back - Back + رجوع R1 / RB - R1 / RB + R1 / RB R2 / RT - R2 / RT + R2 / RT L3 - L3 + L3 Options / Start - Options / Start + الخيارات / البَدْء R3 - R3 + R3 Face Buttons - Face Buttons + الأزرار Triangle / Y - Triangle / Y + مثلث / Y Square / X - Square / X + مربع / X Circle / B - Circle / B + دائرة / B Cross / A - Cross / A + إكس / A Right Stick Deadzone (def:2, max:127) - Right Stick Deadzone (def:2, max:127) + مدى تسجيل الإدخال للعصا اليمنى (التلقائي:2 حد أقصى:127) Right Deadzone - Right Deadzone + إعدادات مدى تسجيل الإدخال لعصا التحكم اليمنى Right Stick - Right Stick + عصا التحكم اليمنى Color Adjustment - Color Adjustment + تعديل الألوان R: - R: + أحمر: G: - G: + أخضر: B: - B: + أزرق: Override Lightbar Color - Override Lightbar Color + تجاوز لون شريط الإضاءة Override Color - Override Color + تجاوز اللون Unable to Save - Unable to Save + غير قادر على الحفظ Cannot bind axis values more than once - Cannot bind axis values more than once + لا يمكن ربط قيم المحور أكثر من مرة Save - Save + حفظ Apply - Apply + تطبيق Restore Defaults - Restore Defaults + استعادة الإعدادات الافتراضية Cancel - Cancel + إلغاء EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + تحرير أزرار الإدخال للوحة المفاتيح و الفأرة ووحدة التحكم Use Per-Game configs - Use Per-Game configs + استخدام إعدادات كل لُعْبَة Error - Error + خطأ Could not open the file for reading - Could not open the file for reading + تعذر فتح المِلَفّ للقراءة Could not open the file for writing - Could not open the file for writing + تعذر فتح المِلَفّ للكتابة Save Changes - Save Changes + حفظ التغييرات Do you want to save changes? - Do you want to save changes? + هل تريد حفظ التغييرات؟ Help @@ -602,15 +602,15 @@ Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + هل تريد إعادة تعيين الإعدادات الافتراضية المخصصة الخاصة بك إلى الإعدادات الافتراضية الأصلية؟ Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + هل تريد إعادة تعيين هذا الإعداد إلى الإعداد الافتراضي المخصص لك؟ Reset to Default - Reset to Default + إعادة تعيين إلى الافتراضي @@ -702,43 +702,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 + يمكن الانتهاء من اللعبة مع الأداء القابل للتشغيل و لا توجد قلتشات كبيرة Click to see details on github @@ -753,23 +753,23 @@ GameListUtils B - B + بايت KB - KB + كيلو بايت MB - MB + ميغابايت GB - GB + جيجابايت TB - TB + تيرابايت @@ -820,11 +820,11 @@ Copy Version - Copy Version + إصدار النسخة Copy Size - Copy Size + حجم النسخة Copy All @@ -832,39 +832,39 @@ Delete... - Delete... + حذف... Delete Game - Delete Game + حذف اللعبة Delete Update - Delete Update + حذف التحديث Delete DLC - Delete DLC + حذف DLC Delete Trophy - Delete Trophy + حذف الكؤوس Compatibility... - Compatibility... + التوافق... Update database - Update database + تحديث قاعدة البيانات View report - View report + عرض التقرير Submit a report - Submit a report + إرسال بلاغ Shortcut creation @@ -888,94 +888,94 @@ Game - Game + اللعبة This game has no update to delete! - This game has no update to delete! + لا تحتوي اللعبة على تحديث لحذفه! Update - Update + تحديث This game has no DLC to delete! - This game has no DLC to delete! + لا تحتوي اللعبة على DLC لحذفه! DLC - DLC + DLC Delete %1 - Delete %1 + حذف %1 Are you sure you want to delete %1's %2 directory? - Are you sure you want to delete %1's %2 directory? + هل أنت متأكد من أنك تريد حذف دليل %1's %2؟ Open Update Folder - Open Update Folder + فتح مجلد التحديث Delete Save Data - Delete Save Data + حذف التخزينه This game has no update folder to open! - This game has no update folder to open! + لا تحتوي اللعبة على تحديث لفتحه! No log file found for this game! - No log file found for this game! + لم يتم العثور على ملف سجل لهذه اللعبة! Failed to convert icon. - Failed to convert icon. + فشل تحويل الأيقونة. This game has no save data to delete! - This game has no save data to delete! + هذه اللعبة لا تحتوي على أي تخزينات لحذفها! This game has no saved trophies to delete! - This game has no saved trophies to delete! + هذه اللعبة ليس لديها كؤوس محفوظة للحذف! Save Data - Save Data + حفظ البيانات Trophy - Trophy + الكؤوس SFO Viewer for - SFO Viewer for + عارض SFO لـ HelpDialog Quickstart - Quickstart + التشغيل السريع FAQ - FAQ + الأسئلة الأكثر شيوعاً Syntax - Syntax + الصّيغة Special Bindings - Special Bindings + إدخالات خاصة Keybindings - Keybindings + أزرار التحكم @@ -986,178 +986,178 @@ Select which directory you want to install to. - Select which directory you want to install to. + حدد الدليل الذي تريد تثبيت إليه. Install All Queued to Selected Folder - Install All Queued to Selected Folder + تثبيت كل قائمة الانتظار إلى المجلد المحدد Delete PKG File on Install - Delete PKG File on Install + حذف مِلَفّ PKG عند التثبيت KBMSettings Configure Controls - Configure Controls + تعديل عناصر التحكم D-Pad - D-Pad + الأسهم+عصا التحكم Up - Up + أعلى unmapped - unmapped + غير معين Left - Left + يسار Right - Right + يمين Down - Down + أسفل Left Analog Halfmode - Left Analog Halfmode + تقليل سرعة عصا التحكم اليسرى للنصف hold to move left stick at half-speed - hold to move left stick at half-speed + الاستمرار للتحرك إلى اليسار بنصف السرعة Left Stick - Left Stick + عصا التحكم اليسرى Config Selection - Config Selection + تحديد الإعدادات Common Config - Common Config + إعدادات عامة Use per-game configs - Use per-game configs + استخدام إعدادات كل لُعْبَة L1 - L1 + L1 L2 - L2 + L2 Text Editor - Text Editor + محرر النص Help - Help + المساعدة R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click - Touchpad Click + النقر على لوحة اللمس Mouse to Joystick - Mouse to Joystick + الفأرة إلى عصا التحكم *press F7 ingame to activate - *press F7 ingame to activate + * اضغط على F7 للتفعيل R3 - R3 + R3 Options - Options + الخيارات Mouse Movement Parameters - Mouse Movement Parameters + معطيات حركة الفأرة note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + ملاحظة: انقر فوق زر المساعدة/روابط المفاتيح الخاصة للحصول على مزيد من المعلومات Face Buttons - Face Buttons + أزرار الوجه Triangle - Triangle + مثلث Square - Square + مربع Circle - Circle + دائرة Cross - Cross + اكس Right Analog Halfmode - Right Analog Halfmode + تقليل سرعة عصا التحكم اليمنى للنصف hold to move right stick at half-speed - hold to move right stick at half-speed + الضغط باستمرار لتحريك العصا اليمنى بنصف السرعة Right Stick - Right Stick + عصا التحكم اليمنى Speed Offset (def 0.125): - Speed Offset (def 0.125): + إزاحة السرعة (تلقائي 0.125): Copy from Common Config - Copy from Common Config + نسخ من الإعدادات الشائعة Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + معدل مضاعفة السرعة (التلقائي 1.0): Common Config Selected - Common Config Selected + الإعدادات الشائعة محدده This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. @@ -1165,7 +1165,7 @@ Copy values from Common Config - Copy values from Common Config + نسخ من الإعدادات الشائعة Do you want to overwrite existing mappings with the mappings from the Common Config? @@ -1173,39 +1173,39 @@ Unable to Save - Unable to Save + غير قادر على الحفظ Cannot bind any unique input more than once - Cannot bind any unique input more than once + لا يمكن ربط أي إدخال فريد أكثر من مرة Press a key - Press a key + اضغط على مفتاح Cannot set mapping - Cannot set mapping + لا يمكن تعيين الأزرار Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + عجلة الفأرة لا يمكن تعيينها لعصا التحكم Save - Save + حفظ Apply - Apply + تطبيق Restore Defaults - Restore Defaults + استعادة الإعدادات الافتراضية Cancel - Cancel + إلغاء @@ -1244,7 +1244,7 @@ Open shadPS4 Folder - Open shadPS4 Folder + فتح مجلد shadPS4 Exit @@ -1306,6 +1306,14 @@ Dump Game List تفريغ قائمة الألعاب + + Trophy Viewer + عارض الجوائز + + + No games found. Please add your games to your library first. + لم يتم العثور على ألعاب. الرجاء إضافة ألعابك إلى مكتبتك أولاً. + PKG Viewer عارض PKG @@ -1484,27 +1492,27 @@ Run Game - Run Game + تشغيل اللعبة Eboot.bin file not found - Eboot.bin file not found + لم يتم العثور على ملف Eboot.bin PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) + ملف PKG (*.PKG *.pkg) PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! + PKG هو تصحيح أو DLC، يرجى تثبيت اللعبة أولاً! Game is already running! - Game is already running! + اللعبة قيد التشغيل بالفعل! shadPS4 - shadPS4 + shadPS4 @@ -1527,7 +1535,7 @@ Installed - Installed + مثبت Size @@ -1535,15 +1543,15 @@ Category - Category + الفئة Type - Type + النوع App Ver - App Ver + إصدار FW @@ -1630,7 +1638,7 @@ Trophy - Trophy + الكؤوس Open the custom trophy images/sounds folder @@ -2094,23 +2102,23 @@ Display Mode - Display Mode + طريقة العرض Windowed - Windowed + نافذة Fullscreen - Fullscreen + شاشة كاملة Fullscreen (Borderless) - Fullscreen (Borderless) + شاشة كاملة (دون حدود) Window Size - Window Size + حجم النافذة W: @@ -2122,7 +2130,7 @@ Separate Log Files - Separate Log Files + ملفات السجل المنفصل Separate Log Files:\nWrites a separate logfile for each game. @@ -2130,35 +2138,35 @@ Trophy Notification Position - Trophy Notification Position + موقع إشعار الكأس Left - Left + يسار Right - Right + يمين Top - Top + في الأعلى Bottom - Bottom + الأسفل Notification Duration - Notification Duration + مدة الإشعار Portable User Folder - Portable User Folder + مجلد المستخدم المتنقل Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + إنشاء مجلد مستخدم المتنقل من مجلد المستخدم الشائع Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. @@ -2166,11 +2174,11 @@ Cannot create portable user folder - Cannot create portable user folder + لا يمكن إنشاء مجلد المستخدم المتنقل %1 already exists - %1 already exists + %1 موجود مسبقاً Portable user folder created @@ -2178,7 +2186,7 @@ %1 successfully created. - %1 successfully created. + تم إنشاء %1 بنجاح. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. @@ -2191,21 +2199,25 @@ Trophy Viewer عارض الجوائز + + Select Game: + اختر الُعْبَه: + Progress - Progress + مقدار التقدُّم Show Earned Trophies - Show Earned Trophies + عرض الكؤوس المكتسبة Show Not Earned Trophies - Show Not Earned Trophies + عرض الكؤوس غير المكتسبة Show Hidden Trophies - Show Hidden Trophies + عرض الكؤوس المخفية diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 113d13019..1835ba84c 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Viewer + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 7f395c1c8..6717a93ef 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -1306,6 +1306,14 @@ Dump Game List Spielliste ausgeben + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG-Anschauer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophäenansicht + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index a61d84022..6e1adaac9 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Viewer + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index be3c701a9..24844d0a2 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -1306,6 +1306,14 @@ Dump Game List Volcar Lista de Juegos + + Trophy Viewer + Expositor de Trofeos + + + No games found. Please add your games to your library first. + No se encontraron juegos. Por favor, añade tus juegos a tu biblioteca primero. + PKG Viewer Vista PKG @@ -2191,6 +2199,10 @@ Trophy Viewer Expositor de Trofeos + + Select Game: + Selecciona un Juego: + Progress Progreso diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 6984b29f8..6b7af042e 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -1306,6 +1306,14 @@ Dump Game List استخراج لیست بازی ها + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG مشاهده گر @@ -2191,6 +2199,10 @@ Trophy Viewer مشاهده جوایز + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 81274ae80..324cb6c49 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -1306,6 +1306,14 @@ Dump Game List Kirjoita Pelilista Tiedostoon + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Selain @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Selain + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index ff1646f9e..ec3f9f8b5 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -1306,6 +1306,14 @@ Dump Game List Dumper la liste des jeux + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer Visionneuse PKG @@ -2191,6 +2199,10 @@ Trophy Viewer Visionneuse de trophées + + Select Game: + Select Game: + Progress Progression diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index c22d74257..6672337a6 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -1306,6 +1306,14 @@ Dump Game List Játéklista Dumpolása + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Nézegető @@ -2191,6 +2199,10 @@ Trophy Viewer Trófeák Megtekintése + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index 1a8b085cf..e43d31976 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Viewer + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 5f57efca3..e63da05b8 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1306,6 +1306,14 @@ Dump Game List Scarica Lista Giochi + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer Visualizzatore PKG @@ -2191,6 +2199,10 @@ Trophy Viewer Visualizzatore Trofei + + Select Game: + Select Game: + Progress Progresso diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index d93e36770..7cf9fc5c2 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -1306,6 +1306,14 @@ Dump Game List ゲームリストをダンプ + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKGビューアー @@ -2191,6 +2199,10 @@ Trophy Viewer トロフィービューアー + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index dc5b61038..d5289ace9 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Viewer + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 2f4b6e59b..17133da35 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -7,7 +7,7 @@ AboutDialog About shadPS4 - About shadPS4 + Apie shadPS4 shadPS4 is an experimental open-source emulator for the PlayStation 4. @@ -15,7 +15,7 @@ This software should not be used to play games you have not legally obtained. - This software should not be used to play games you have not legally obtained. + Ši programa neturėtų būti naudojama žaidimams kurių neturite legaliai įsigiję. @@ -463,7 +463,7 @@ Back - Back + Atgal R1 / RB @@ -519,7 +519,7 @@ Color Adjustment - Color Adjustment + Spalvų Reguliavimas R: @@ -543,7 +543,7 @@ Unable to Save - Unable to Save + Nepavyko Išsaugoti Cannot bind axis values more than once @@ -563,7 +563,7 @@ Cancel - Cancel + Atšaukti @@ -590,15 +590,15 @@ Save Changes - Save Changes + Išsaugoti Pakeitimus Do you want to save changes? - Do you want to save changes? + Ar norite išsaugoti pakeitimus? Help - Help + Pagalba Do you want to reset your custom default config to the original default config? @@ -876,7 +876,7 @@ Error - Error + Klaida Error creating shortcut! @@ -888,7 +888,7 @@ Game - Game + Žaidimas This game has no update to delete! @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Viewer + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index b257b548b..e8ce99f90 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump spilliste + + Trophy Viewer + Troféviser + + + No games found. Please add your games to your library first. + Fant ingen spill. Legg til spillene dine i biblioteket først. + PKG Viewer PKG-viser @@ -2191,6 +2199,10 @@ Trophy Viewer Troféviser + + Select Game: + Velg spill: + Progress Fremdrift diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index f6c062da3..5c1725bd5 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Viewer + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 4c4a33ec2..033412efa 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -543,38 +543,38 @@ Unable to Save - Unable to Save + Zapisywanie nie powiodło się Cannot bind axis values more than once - Cannot bind axis values more than once + Nie można powiązać wartości osi więcej niż raz Save - Save + Zapisz Apply - Apply + Zastosuj Restore Defaults - Restore Defaults + Przywróć ustawienia domyślne Cancel - Cancel + Anuluj EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Edytuj przypisanie klawiszy klawiatury + myszy oraz kontrolera Use Per-Game configs - Use Per-Game configs + Użyj osobnej konfiguracji dla każdej gry Error @@ -582,11 +582,11 @@ Could not open the file for reading - Could not open the file for reading + Nie można otworzyć pliku do odczytu Could not open the file for writing - Could not open the file for writing + Nie można otworzyć pliku do zapisu Save Changes @@ -602,11 +602,11 @@ Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Czy chcesz zresetować Twoją domyślną konfigurację do oryginalnej domyślnej konfiguracji? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Czy chcesz zresetować tę konfigurację do Twojej domyślnej konfiguracji? Reset to Default @@ -959,23 +959,23 @@ HelpDialog Quickstart - Quickstart + Szybki start FAQ - FAQ + Najczęściej zadawane pytania Syntax - Syntax + Składnia Special Bindings - Special Bindings + Specjalne wiązania Keybindings - Keybindings + Przypisanie klawiszy @@ -1001,211 +1001,211 @@ KBMSettings Configure Controls - Configure Controls + Skonfiguruj sterowanie D-Pad - D-Pad + Krzyżak Up - Up + Strzałka w górę unmapped - unmapped + nieprzypisane Left - Left + Strzałka w lewo Right - Right + Strzałka w prawo Down - Down + Strzałka w dół Left Analog Halfmode - Left Analog Halfmode + Połowiczny tryb lewego drążka hold to move left stick at half-speed - hold to move left stick at half-speed + przytrzymaj, aby przesuwać lewy drążek dwa razy wolniej Left Stick - Left Stick + Lewy drążek Config Selection - Config Selection + Wybór konfiguracji Common Config - Common Config + Typowa konfiguracja Use per-game configs - Use per-game configs + Użyj osobnej konfiguracji dla każdej gry L1 - L1 + L1 L2 - L2 + L2 Text Editor - Text Editor + Edytor tekstu Help - Help + Pomoc R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click - Touchpad Click + Kliknięcie Touchpada Mouse to Joystick - Mouse to Joystick + Mysz na Joystick *press F7 ingame to activate - *press F7 ingame to activate + *naciśnij F7 w grze aby aktywować R3 - R3 + R3 Options - Options + Opcje Mouse Movement Parameters - Mouse Movement Parameters + Parametry ruchu myszy note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + uwaga: kliknij przycisk Pomoc/Specjalne skróty klawiszowe, aby uzyskać więcej informacji Face Buttons - Face Buttons + Przednie przyciski Triangle - Triangle + Trójkąt Square - Square + Kwadrat Circle - Circle + Kółko Cross - Cross + Krzyżyk Right Analog Halfmode - Right Analog Halfmode + Połowiczny tryb prawego drążka hold to move right stick at half-speed - hold to move right stick at half-speed + przytrzymaj, aby przesuwać prawy drążek dwa razy wolniej Right Stick - Right Stick + Prawy drążek Speed Offset (def 0.125): - Speed Offset (def 0.125): + Offset prędkości (def 0,125): Copy from Common Config - Copy from Common Config + Kopiuj z typowej konfiguracji Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Offset martwych stref (def 0,50): Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Mnożnik prędkości (def1.0): Common Config Selected - Common Config Selected + Wybrano typową konfigurację This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Przycisk ten kopiuje mapowanie z typowej konfiguracji do aktualnie wybranego profilu, i nie może być użyty, gdy aktualnie wybranym profilem jest typowa konfiguracja. Copy values from Common Config - Copy values from Common Config + Kopiuj z typowej konfiguracji Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + Czy chcesz nadpisać istniejące mapowania mapowaniem z typowej konfiguracji? Unable to Save - Unable to Save + Zapisywanie nie powiodło się Cannot bind any unique input more than once - Cannot bind any unique input more than once + Nie można powiązać żadnych unikalnych danych wejściowych więcej niż raz Press a key - Press a key + Naciśnij klawisz Cannot set mapping - Cannot set mapping + Nie można ustawić mapowania Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + Kółko myszy nie może być przypisane do sterowania drążkiem Save - Save + Zapisz Apply - Apply + Zastosuj Restore Defaults - Restore Defaults + Przywróć ustawienia domyślne Cancel - Cancel + Anuluj @@ -1306,6 +1306,14 @@ Dump Game List Zgraj listę gier + + Trophy Viewer + Menedżer trofeów + + + No games found. Please add your games to your library first. + Nie znaleziono gier. Najpierw dodaj swoje gry do swojej biblioteki. + PKG Viewer Menedżer plików PKG @@ -2154,35 +2162,35 @@ Portable User Folder - Portable User Folder + Przenośny folder użytkownika Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Utwórz przenośny folder użytkownika ze zwykłego folderu użytkownika Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Przenośny folder użytkownika:\nPrzechowuje ustawienia shadPS4 i dane, które zostaną zastosowane tylko do kompilacji shadPS4 znajdującej się w bieżącym folderze. Uruchom ponownie aplikację po utworzeniu przenośnego folderu użytkownika, aby zacząć z niego korzystać. Cannot create portable user folder - Cannot create portable user folder + Nie można utworzyć przenośnego folderu użytkownika %1 already exists - %1 already exists + %1 już istnieje Portable user folder created - Portable user folder created + Utworzono przenośny folder użytkownika %1 successfully created. - %1 successfully created. + %1 prawidłowo utworzony. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Otwórz niestandardowy folder obrazów/dźwięków:\nMożesz dodać własne obrazy dla trofeów i ich dźwięki.\nDodaj pliki do custom_trophy o następujących nazwach:\ntrophy.wav LUB trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nUwaga: Dźwięki działają tylko w wersji QT. @@ -2191,6 +2199,10 @@ Trophy Viewer Menedżer trofeów + + Select Game: + Wybierz grę: + Progress Postęp diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index e37a3fe96..d44efce5d 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -1306,6 +1306,14 @@ Dump Game List Exportar Lista de Jogos + + Trophy Viewer + Visualizador de Troféus + + + No games found. Please add your games to your library first. + Nenhum jogo encontrado. Adicione seus jogos à sua biblioteca primeiro. + PKG Viewer Visualizador de PKG @@ -2191,6 +2199,10 @@ Trophy Viewer Visualizador de Troféus + + Select Game: + Selecionar Jogo: + Progress Progresso diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index dc0059b86..455955fad 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -543,74 +543,74 @@ Unable to Save - Unable to Save + Não é possível salvar Cannot bind axis values more than once - Cannot bind axis values more than once + Não foi possível atribuir os valores do eixo X ou Y mais de uma vez Save - Save + Salvar Apply - Apply + Aplicar Restore Defaults - Restore Defaults + Restaurar o Padrão Cancel - Cancel + Cancelar EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Editar comandos do Teclado + Mouse e do Controle Use Per-Game configs - Use Per-Game configs + Use uma configuração para cada jogo Error - Error + Erro Could not open the file for reading - Could not open the file for reading + Não foi possível abrir o arquivo para ler Could not open the file for writing - Could not open the file for writing + Não foi possível abrir o arquivo para escrever Save Changes - Save Changes + Salvar mudanças Do you want to save changes? - Do you want to save changes? + Salvar as mudanças? Help - Help + Ajuda Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Restaurar a configuração customizada padrão para a configuração original padrão? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Deseja redefinir esta configuração para a configuração padrão personalizada? Reset to Default - Reset to Default + Resetar ao Padrão @@ -959,23 +959,23 @@ HelpDialog Quickstart - Quickstart + Início Rápido FAQ - FAQ + Perguntas Frequentes Syntax - Syntax + Sintaxe Special Bindings - Special Bindings + Atalhos Especiais Keybindings - Keybindings + Combinações de Teclas @@ -1145,67 +1145,67 @@ Copy from Common Config - Copy from Common Config + Copiar da Configuração Comum Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Deslocamento da Zona Morta (def 0,50): Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Multiplicador de Velocidade (def 1,0): Common Config Selected - Common Config Selected + Configuração Comum Selecionada This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + Este botão copia mapeamentos da Configuração Comum para o perfil atualmente selecionado, e não pode ser usado quando o perfil atualmente selecionado é a Configuração Comum. Copy values from Common Config - Copy values from Common Config + Copiar valores da Configuração Comum Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + Substituir mapeamentos existentes com os mapeamentos da Configuração Comum? Unable to Save - Unable to Save + Não é possível salvar Cannot bind any unique input more than once - Cannot bind any unique input more than once + Não é possível vincular qualquer entrada única mais de uma vez Press a key - Press a key + Pressione uma tecla Cannot set mapping - Cannot set mapping + Não é possível definir o mapeamento Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + Roda do rato não pode ser mapeada para saídas empates Save - Save + Salvar Apply - Apply + Aplicar Restore Defaults - Restore Defaults + Restaurar Definições Cancel - Cancel + Cancelar @@ -1306,6 +1306,14 @@ Dump Game List Exportar Lista de Jogos + + Trophy Viewer + Visualizador de Troféus + + + No games found. Please add your games to your library first. + Nenhum jogo encontrado. Por favor, adicione os seus jogos à sua biblioteca primeiro. + PKG Viewer Visualizador PKG @@ -2166,23 +2174,23 @@ Cannot create portable user folder - Cannot create portable user folder + Não é possível criar pasta de utilizador portátil %1 already exists - %1 already exists + %1 já existe Portable user folder created - Portable user folder created + Pasta de utilizador portátil criada %1 successfully created. - %1 successfully created. + %1 criado com sucesso. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Abra a pasta de imagens/sons de troféus personalizados:\nPoderá adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os arquivos na pasta custom_trophy com os seguintes nomes:\ntrophy.mp3 ou trophy.wav, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas nas versões Qt. @@ -2191,6 +2199,10 @@ Trophy Viewer Visualizador de Troféus + + Select Game: + Escolha o Jogo: + Progress Progresso diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 4261bf9e2..9c7720e17 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Viewer + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 6ca16121f..68eaabc34 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1306,6 +1306,14 @@ Dump Game List Дамп списка игр + + Trophy Viewer + Просмотр трофеев + + + No games found. Please add your games to your library first. + Не найдено ни одной игры. Пожалуйста, сначала добавьте игры в библиотеку. + PKG Viewer Просмотр PKG @@ -2191,6 +2199,10 @@ Trophy Viewer Просмотр трофеев + + Select Game: + Выберите игру: + Progress Прогресс diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index bf58dc60d..657f78d0d 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1306,6 +1306,14 @@ Dump Game List Zbraz Listën e Lojërave + + Trophy Viewer + Shikuesi i Trofeve + + + No games found. Please add your games to your library first. + Nuk u gjetën lojëra. Shto lojërat në librarinë tënde fillimisht. + PKG Viewer Shikuesi i PKG @@ -2191,6 +2199,10 @@ Trophy Viewer Shikuesi i Trofeve + + Select Game: + Zgjidh Lojën: + Progress Ecuria diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index d631859d4..a002b150a 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -1306,6 +1306,14 @@ Dump Game List Dumpa spellista + + Trophy Viewer + Trofévisare + + + No games found. Please add your games to your library first. + Inga spel hittades. Lägg till dina spel till biblioteket först. + PKG Viewer PKG-visare @@ -2191,6 +2199,10 @@ Trophy Viewer Trofé-visare + + Select Game: + Välj spel: + Progress Förlopp diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 394704274..48ce4254d 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -1306,6 +1306,14 @@ Dump Game List Oyun Listesini Kaydet + + Trophy Viewer + Kupa Görüntüleyici + + + No games found. Please add your games to your library first. + Oyun bulunamadı. Oyunlarınızı lütfen önce kütüphanenize ekleyin. + PKG Viewer PKG Görüntüleyici @@ -2191,6 +2199,10 @@ Trophy Viewer Kupa Görüntüleyici + + Select Game: + Oyun Seç: + Progress İlerleme diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 6083577fa..06c88428b 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -1306,6 +1306,14 @@ Dump Game List Дамп списку ігор + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer Перегляд PKG @@ -2191,6 +2199,10 @@ Trophy Viewer Трофеї + + Select Game: + Select Game: + Progress Прогрес diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 8fa0889bc..c16604b85 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trình xem chiến tích + + Select Game: + Select Game: + Progress Progress diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 6e1fcdc95..6364ae1d6 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1306,6 +1306,14 @@ Dump Game List 导出游戏列表 + + Trophy Viewer + 奖杯查看器 + + + No games found. Please add your games to your library first. + 未找到游戏。请先将您的游戏添加到您的资料库。 + PKG Viewer PKG 查看器 @@ -2191,6 +2199,10 @@ Trophy Viewer 奖杯查看器 + + Select Game: + 选择游戏: + Progress 进度 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 2950f541f..bd546380a 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -1306,6 +1306,14 @@ Dump Game List Dump Game List + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + PKG Viewer PKG Viewer @@ -2191,6 +2199,10 @@ Trophy Viewer Trophy Viewer + + Select Game: + Select Game: + Progress Progress From 3abe5b0d5774e2d7b959d85f51657144b3d822c7 Mon Sep 17 00:00:00 2001 From: Lizardy <6063922+lzardy@users.noreply.github.com> Date: Wed, 26 Mar 2025 16:42:49 +0000 Subject: [PATCH 070/194] [Libs] Ngs2 (#1604) * [libSceNgs2] Logging & Structs * clang * clang * stdarg incl * proper logs * ngs2 latest * [libSceNgs2] Logging & Structs * clang * latest * fix includes * clang * clang --------- Co-authored-by: microsoftv <6063922+microsoftv@users.noreply.github.com> --- CMakeLists.txt | 18 + src/core/libraries/ngs2/ngs2.cpp | 682 ++++++++++++++------- src/core/libraries/ngs2/ngs2.h | 283 +++++++-- src/core/libraries/ngs2/ngs2_custom.cpp | 12 + src/core/libraries/ngs2/ngs2_custom.h | 444 ++++++++++++++ src/core/libraries/ngs2/ngs2_eq.cpp | 12 + src/core/libraries/ngs2/ngs2_eq.h | 41 ++ src/core/libraries/ngs2/ngs2_geom.cpp | 12 + src/core/libraries/ngs2/ngs2_geom.h | 80 +++ src/core/libraries/ngs2/ngs2_impl.cpp | 208 ++++--- src/core/libraries/ngs2/ngs2_impl.h | 179 +++++- src/core/libraries/ngs2/ngs2_mastering.cpp | 12 + src/core/libraries/ngs2/ngs2_mastering.h | 81 +++ src/core/libraries/ngs2/ngs2_pan.cpp | 12 + src/core/libraries/ngs2/ngs2_pan.h | 25 + src/core/libraries/ngs2/ngs2_report.cpp | 12 + src/core/libraries/ngs2/ngs2_report.h | 78 +++ src/core/libraries/ngs2/ngs2_reverb.cpp | 12 + src/core/libraries/ngs2/ngs2_reverb.h | 61 ++ src/core/libraries/ngs2/ngs2_sampler.cpp | 12 + src/core/libraries/ngs2/ngs2_sampler.h | 162 +++++ src/core/libraries/ngs2/ngs2_submixer.cpp | 12 + src/core/libraries/ngs2/ngs2_submixer.h | 126 ++++ 23 files changed, 2206 insertions(+), 370 deletions(-) create mode 100644 src/core/libraries/ngs2/ngs2_custom.cpp create mode 100644 src/core/libraries/ngs2/ngs2_custom.h create mode 100644 src/core/libraries/ngs2/ngs2_eq.cpp create mode 100644 src/core/libraries/ngs2/ngs2_eq.h create mode 100644 src/core/libraries/ngs2/ngs2_geom.cpp create mode 100644 src/core/libraries/ngs2/ngs2_geom.h create mode 100644 src/core/libraries/ngs2/ngs2_mastering.cpp create mode 100644 src/core/libraries/ngs2/ngs2_mastering.h create mode 100644 src/core/libraries/ngs2/ngs2_pan.cpp create mode 100644 src/core/libraries/ngs2/ngs2_pan.h create mode 100644 src/core/libraries/ngs2/ngs2_report.cpp create mode 100644 src/core/libraries/ngs2/ngs2_report.h create mode 100644 src/core/libraries/ngs2/ngs2_reverb.cpp create mode 100644 src/core/libraries/ngs2/ngs2_reverb.h create mode 100644 src/core/libraries/ngs2/ngs2_sampler.cpp create mode 100644 src/core/libraries/ngs2/ngs2_sampler.h create mode 100644 src/core/libraries/ngs2/ngs2_submixer.cpp create mode 100644 src/core/libraries/ngs2/ngs2_submixer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 185205221..1c0932b5c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -378,6 +378,24 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/ngs2/ngs2_error.h src/core/libraries/ngs2/ngs2_impl.cpp src/core/libraries/ngs2/ngs2_impl.h + src/core/libraries/ngs2/ngs2_custom.cpp + src/core/libraries/ngs2/ngs2_custom.h + src/core/libraries/ngs2/ngs2_reverb.cpp + src/core/libraries/ngs2/ngs2_reverb.h + src/core/libraries/ngs2/ngs2_geom.cpp + src/core/libraries/ngs2/ngs2_geom.h + src/core/libraries/ngs2/ngs2_pan.cpp + src/core/libraries/ngs2/ngs2_pan.h + src/core/libraries/ngs2/ngs2_report.cpp + src/core/libraries/ngs2/ngs2_report.h + src/core/libraries/ngs2/ngs2_eq.cpp + src/core/libraries/ngs2/ngs2_eq.h + src/core/libraries/ngs2/ngs2_mastering.cpp + src/core/libraries/ngs2/ngs2_mastering.h + src/core/libraries/ngs2/ngs2_sampler.cpp + src/core/libraries/ngs2/ngs2_sampler.h + src/core/libraries/ngs2/ngs2_submixer.cpp + src/core/libraries/ngs2/ngs2_submixer.h src/core/libraries/ajm/ajm_error.h src/core/libraries/audio3d/audio3d.cpp src/core/libraries/audio3d/audio3d.h diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp index 7eb663413..0b42e2471 100644 --- a/src/core/libraries/ngs2/ngs2.cpp +++ b/src/core/libraries/ngs2/ngs2.cpp @@ -5,21 +5,480 @@ #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/ngs2/ngs2.h" +#include "core/libraries/ngs2/ngs2_custom.h" #include "core/libraries/ngs2/ngs2_error.h" +#include "core/libraries/ngs2/ngs2_geom.h" #include "core/libraries/ngs2/ngs2_impl.h" +#include "core/libraries/ngs2/ngs2_pan.h" +#include "core/libraries/ngs2/ngs2_report.h" namespace Libraries::Ngs2 { -int PS4_SYSV_ABI sceNgs2CalcWaveformBlock() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); +// Ngs2 + +s32 PS4_SYSV_ABI sceNgs2CalcWaveformBlock(const OrbisNgs2WaveformFormat* format, u32 samplePos, + u32 numSamples, OrbisNgs2WaveformBlock* outBlock) { + LOG_INFO(Lib_Ngs2, "samplePos = {}, numSamples = {}", samplePos, numSamples); return ORBIS_OK; } -int PS4_SYSV_ABI sceNgs2CustomRackGetModuleInfo() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNgs2GetWaveformFrameInfo(const OrbisNgs2WaveformFormat* format, + u32* outFrameSize, u32* outNumFrameSamples, + u32* outUnitsPerFrame, u32* outNumDelaySamples) { + LOG_INFO(Lib_Ngs2, "called"); return ORBIS_OK; } +s32 PS4_SYSV_ABI sceNgs2ParseWaveformData(const void* data, size_t dataSize, + OrbisNgs2WaveformInfo* outInfo) { + LOG_INFO(Lib_Ngs2, "dataSize = {}", dataSize); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2ParseWaveformFile(const char* path, u64 offset, + OrbisNgs2WaveformInfo* outInfo) { + LOG_INFO(Lib_Ngs2, "path = {}, offset = {}", path, offset); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2ParseWaveformUser(OrbisNgs2ParseReadHandler handler, uintptr_t userData, + OrbisNgs2WaveformInfo* outInfo) { + LOG_INFO(Lib_Ngs2, "userData = {}", userData); + if (!handler) { + LOG_ERROR(Lib_Ngs2, "handler is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackCreate(OrbisNgs2Handle systemHandle, u32 rackId, + const OrbisNgs2RackOption* option, + const OrbisNgs2ContextBufferInfo* bufferInfo, + OrbisNgs2Handle* outHandle) { + LOG_INFO(Lib_Ngs2, "rackId = {}", rackId); + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackCreateWithAllocator(OrbisNgs2Handle systemHandle, u32 rackId, + const OrbisNgs2RackOption* option, + const OrbisNgs2BufferAllocator* allocator, + OrbisNgs2Handle* outHandle) { + LOG_INFO(Lib_Ngs2, "rackId = {}", rackId); + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackDestroy(OrbisNgs2Handle rackHandle, + OrbisNgs2ContextBufferInfo* outBufferInfo) { + if (!rackHandle) { + LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackGetInfo(OrbisNgs2Handle rackHandle, OrbisNgs2RackInfo* outInfo, + size_t infoSize) { + LOG_INFO(Lib_Ngs2, "infoSize = {}", infoSize); + if (!rackHandle) { + LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackGetUserData(OrbisNgs2Handle rackHandle, uintptr_t* outUserData) { + if (!rackHandle) { + LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackGetVoiceHandle(OrbisNgs2Handle rackHandle, u32 voiceIndex, + OrbisNgs2Handle* outHandle) { + LOG_INFO(Lib_Ngs2, "voiceIndex = {}", voiceIndex); + if (!rackHandle) { + LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackLock(OrbisNgs2Handle rackHandle) { + if (!rackHandle) { + LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackQueryBufferSize(u32 rackId, const OrbisNgs2RackOption* option, + OrbisNgs2ContextBufferInfo* outBufferInfo) { + LOG_INFO(Lib_Ngs2, "rackId = {}", rackId); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackSetUserData(OrbisNgs2Handle rackHandle, uintptr_t userData) { + LOG_INFO(Lib_Ngs2, "userData = {}", userData); + if (!rackHandle) { + LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2RackUnlock(OrbisNgs2Handle rackHandle) { + if (!rackHandle) { + LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemCreate(const OrbisNgs2SystemOption* option, + const OrbisNgs2ContextBufferInfo* bufferInfo, + OrbisNgs2Handle* outHandle) { + s32 result; + OrbisNgs2ContextBufferInfo localInfo; + if (!bufferInfo || !outHandle) { + if (!bufferInfo) { + result = ORBIS_NGS2_ERROR_INVALID_BUFFER_INFO; + LOG_ERROR(Lib_Ngs2, "Invalid system buffer info {}", (void*)bufferInfo); + } else { + result = ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; + LOG_ERROR(Lib_Ngs2, "Invalid system handle address {}", (void*)outHandle); + } + + // TODO: Report errors? + } else { + // Make bufferInfo copy + localInfo.hostBuffer = bufferInfo->hostBuffer; + localInfo.hostBufferSize = bufferInfo->hostBufferSize; + for (int i = 0; i < 5; i++) { + localInfo.reserved[i] = bufferInfo->reserved[i]; + } + localInfo.userData = bufferInfo->userData; + + result = SystemSetup(option, &localInfo, 0, outHandle); + } + + // TODO: API reporting? + + LOG_INFO(Lib_Ngs2, "called"); + return result; +} + +s32 PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator(const OrbisNgs2SystemOption* option, + const OrbisNgs2BufferAllocator* allocator, + OrbisNgs2Handle* outHandle) { + s32 result; + if (allocator && allocator->allocHandler != 0) { + OrbisNgs2BufferAllocHandler hostAlloc = allocator->allocHandler; + if (outHandle) { + OrbisNgs2BufferFreeHandler hostFree = allocator->freeHandler; + OrbisNgs2ContextBufferInfo* bufferInfo = 0; + result = SystemSetup(option, bufferInfo, 0, 0); + if (result >= 0) { + uintptr_t sysUserData = allocator->userData; + result = hostAlloc(bufferInfo); + if (result >= 0) { + OrbisNgs2Handle* handleCopy = outHandle; + result = SystemSetup(option, bufferInfo, hostFree, handleCopy); + if (result < 0) { + if (hostFree) { + hostFree(bufferInfo); + } + } + } + } + } else { + result = ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; + LOG_ERROR(Lib_Ngs2, "Invalid system handle address {}", (void*)outHandle); + } + } else { + result = ORBIS_NGS2_ERROR_INVALID_BUFFER_ALLOCATOR; + LOG_ERROR(Lib_Ngs2, "Invalid system buffer allocator {}", (void*)allocator); + } + LOG_INFO(Lib_Ngs2, "called"); + return result; +} + +s32 PS4_SYSV_ABI sceNgs2SystemDestroy(OrbisNgs2Handle systemHandle, + OrbisNgs2ContextBufferInfo* outBufferInfo) { + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemEnumHandles(OrbisNgs2Handle* aOutHandle, u32 maxHandles) { + LOG_INFO(Lib_Ngs2, "maxHandles = {}", maxHandles); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemEnumRackHandles(OrbisNgs2Handle systemHandle, + OrbisNgs2Handle* aOutHandle, u32 maxHandles) { + LOG_INFO(Lib_Ngs2, "maxHandles = {}", maxHandles); + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemGetInfo(OrbisNgs2Handle rackHandle, OrbisNgs2SystemInfo* outInfo, + size_t infoSize) { + LOG_INFO(Lib_Ngs2, "infoSize = {}", infoSize); + if (!rackHandle) { + LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemGetUserData(OrbisNgs2Handle systemHandle, uintptr_t* outUserData) { + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemLock(OrbisNgs2Handle systemHandle) { + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemQueryBufferSize(const OrbisNgs2SystemOption* option, + OrbisNgs2ContextBufferInfo* outBufferInfo) { + s32 result; + if (outBufferInfo) { + result = SystemSetup(option, outBufferInfo, 0, 0); + LOG_INFO(Lib_Ngs2, "called"); + } else { + result = ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; + LOG_ERROR(Lib_Ngs2, "Invalid system buffer info {}", (void*)outBufferInfo); + } + + return result; +} + +s32 PS4_SYSV_ABI sceNgs2SystemRender(OrbisNgs2Handle systemHandle, + const OrbisNgs2RenderBufferInfo* aBufferInfo, + u32 numBufferInfo) { + LOG_INFO(Lib_Ngs2, "numBufferInfo = {}", numBufferInfo); + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + return ORBIS_OK; +} + +static s32 PS4_SYSV_ABI sceNgs2SystemResetOption(OrbisNgs2SystemOption* outOption) { + static const OrbisNgs2SystemOption option = { + sizeof(OrbisNgs2SystemOption), "", 0, 512, 256, 48000, {0}}; + + if (!outOption) { + LOG_ERROR(Lib_Ngs2, "Invalid system option address {}", (void*)outOption); + return ORBIS_NGS2_ERROR_INVALID_OPTION_ADDRESS; + } + *outOption = option; + + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemSetGrainSamples(OrbisNgs2Handle systemHandle, u32 numSamples) { + LOG_INFO(Lib_Ngs2, "numSamples = {}", numSamples); + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemSetSampleRate(OrbisNgs2Handle systemHandle, u32 sampleRate) { + LOG_INFO(Lib_Ngs2, "sampleRate = {}", sampleRate); + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemSetUserData(OrbisNgs2Handle systemHandle, uintptr_t userData) { + LOG_INFO(Lib_Ngs2, "userData = {}", userData); + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2SystemUnlock(OrbisNgs2Handle systemHandle) { + if (!systemHandle) { + LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2VoiceControl(OrbisNgs2Handle voiceHandle, + const OrbisNgs2VoiceParamHeader* paramList) { + if (!voiceHandle) { + LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2VoiceGetMatrixInfo(OrbisNgs2Handle voiceHandle, u32 matrixId, + OrbisNgs2VoiceMatrixInfo* outInfo, size_t outInfoSize) { + LOG_INFO(Lib_Ngs2, "matrixId = {}, outInfoSize = {}", matrixId, outInfoSize); + if (!voiceHandle) { + LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2VoiceGetOwner(OrbisNgs2Handle voiceHandle, OrbisNgs2Handle* outRackHandle, + u32* outVoiceId) { + if (!voiceHandle) { + LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2VoiceGetPortInfo(OrbisNgs2Handle voiceHandle, u32 port, + OrbisNgs2VoicePortInfo* outInfo, size_t outInfoSize) { + LOG_INFO(Lib_Ngs2, "port = {}, outInfoSize = {}", port, outInfoSize); + if (!voiceHandle) { + LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2VoiceGetState(OrbisNgs2Handle voiceHandle, OrbisNgs2VoiceState* outState, + size_t stateSize) { + LOG_INFO(Lib_Ngs2, "stateSize = {}", stateSize); + if (!voiceHandle) { + LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2VoiceGetStateFlags(OrbisNgs2Handle voiceHandle, u32* outStateFlags) { + if (!voiceHandle) { + LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +// Ngs2Custom + +s32 PS4_SYSV_ABI sceNgs2CustomRackGetModuleInfo(OrbisNgs2Handle rackHandle, u32 moduleIndex, + OrbisNgs2CustomModuleInfo* outInfo, + size_t infoSize) { + LOG_INFO(Lib_Ngs2, "moduleIndex = {}, infoSize = {}", moduleIndex, infoSize); + if (!rackHandle) { + LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + return ORBIS_OK; +} + +// Ngs2Geom + +s32 PS4_SYSV_ABI sceNgs2GeomResetListenerParam(OrbisNgs2GeomListenerParam* outListenerParam) { + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2GeomResetSourceParam(OrbisNgs2GeomSourceParam* outSourceParam) { + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2GeomCalcListener(const OrbisNgs2GeomListenerParam* param, + OrbisNgs2GeomListenerWork* outWork, u32 flags) { + LOG_INFO(Lib_Ngs2, "flags = {}", flags); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2GeomApply(const OrbisNgs2GeomListenerWork* listener, + const OrbisNgs2GeomSourceParam* source, + OrbisNgs2GeomAttribute* outAttrib, u32 flags) { + LOG_INFO(Lib_Ngs2, "flags = {}", flags); + return ORBIS_OK; +} + +// Ngs2Pan + +s32 PS4_SYSV_ABI sceNgs2PanInit(OrbisNgs2PanWork* work, const float* aSpeakerAngle, float unitAngle, + u32 numSpeakers) { + LOG_INFO(Lib_Ngs2, "aSpeakerAngle = {}, unitAngle = {}, numSpeakers = {}", *aSpeakerAngle, + unitAngle, numSpeakers); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2PanGetVolumeMatrix(OrbisNgs2PanWork* work, const OrbisNgs2PanParam* aParam, + u32 numParams, u32 matrixFormat, + float* outVolumeMatrix) { + LOG_INFO(Lib_Ngs2, "numParams = {}, matrixFormat = {}", numParams, matrixFormat); + return ORBIS_OK; +} + +// Ngs2Report + +s32 PS4_SYSV_ABI sceNgs2ReportRegisterHandler(u32 reportType, OrbisNgs2ReportHandler handler, + uintptr_t userData, OrbisNgs2Handle* outHandle) { + LOG_INFO(Lib_Ngs2, "reportType = {}, userData = {}", reportType, userData); + if (!handler) { + LOG_ERROR(Lib_Ngs2, "handler is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_REPORT_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNgs2ReportUnregisterHandler(OrbisNgs2Handle reportHandle) { + if (!reportHandle) { + LOG_ERROR(Lib_Ngs2, "reportHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_REPORT_HANDLE; + } + LOG_INFO(Lib_Ngs2, "called"); + return ORBIS_OK; +} + +// Unknown + int PS4_SYSV_ABI sceNgs2FftInit() { LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); return ORBIS_OK; @@ -35,31 +494,6 @@ int PS4_SYSV_ABI sceNgs2FftQuerySize() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNgs2GeomApply() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2GeomCalcListener() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2GeomResetListenerParam() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2GeomResetSourceParam() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2GetWaveformFrameInfo() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNgs2JobSchedulerResetOption() { LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); return ORBIS_OK; @@ -80,71 +514,6 @@ int PS4_SYSV_ABI sceNgs2ModuleQueueEnumItems() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNgs2PanGetVolumeMatrix() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2PanInit() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2ParseWaveformData() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2ParseWaveformFile() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2ParseWaveformUser() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2RackCreate() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2RackCreateWithAllocator() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2RackDestroy() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2RackGetInfo() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2RackGetUserData() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2RackGetVoiceHandle() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2RackLock() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2RackQueryBufferSize() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNgs2RackQueryInfo() { LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); return ORBIS_OK; @@ -155,116 +524,21 @@ int PS4_SYSV_ABI sceNgs2RackRunCommands() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNgs2RackSetUserData() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2RackUnlock() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2ReportRegisterHandler() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2ReportUnregisterHandler() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemCreate() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemDestroy() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemEnumHandles() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemEnumRackHandles() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemGetInfo() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemGetUserData() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemLock() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemQueryBufferSize() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNgs2SystemQueryInfo() { LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNgs2SystemRender() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemResetOption() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNgs2SystemRunCommands() { LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNgs2SystemSetGrainSamples() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNgs2SystemSetLoudThreshold() { LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNgs2SystemSetSampleRate() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemSetUserData() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2SystemUnlock() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNgs2StreamCreate() { LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); return ORBIS_OK; @@ -300,36 +574,6 @@ int PS4_SYSV_ABI sceNgs2StreamRunCommands() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNgs2VoiceControl() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2VoiceGetMatrixInfo() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2VoiceGetOwner() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2VoiceGetPortInfo() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2VoiceGetState() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNgs2VoiceGetStateFlags() { - LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNgs2VoiceQueryInfo() { LOG_ERROR(Lib_Ngs2, "(STUBBED) called"); return ORBIS_OK; diff --git a/src/core/libraries/ngs2/ngs2.h b/src/core/libraries/ngs2/ngs2.h index a5f1f52a6..a34bf21d4 100644 --- a/src/core/libraries/ngs2/ngs2.h +++ b/src/core/libraries/ngs2/ngs2.h @@ -3,7 +3,11 @@ #pragma once +#include "core/libraries/ngs2/ngs2_impl.h" + #include +#include +#include #include "common/types.h" namespace Core::Loader { @@ -12,60 +16,253 @@ class SymbolsResolver; namespace Libraries::Ngs2 { -class Ngs2; +typedef s32 (*OrbisNgs2ParseReadHandler)(uintptr_t userData, u32 offset, void* data, size_t size); -using SceNgs2Handle = Ngs2*; - -enum class SceNgs2HandleType : u32 { - System = 0, +enum class OrbisNgs2HandleType : u32 { + Invalid = 0, + System = 1, + Rack = 2, + Voice = 3, + VoiceControl = 6 }; -struct Ngs2Handle { - void* selfPointer; - void* dataPointer; - std::atomic* atomicPtr; - u32 handleType; - u32 flags_unk; +static const int ORBIS_NGS2_MAX_VOICE_CHANNELS = 8; +static const int ORBIS_NGS2_WAVEFORM_INFO_MAX_BLOCKS = 4; +static const int ORBIS_NGS2_MAX_MATRIX_LEVELS = + (ORBIS_NGS2_MAX_VOICE_CHANNELS * ORBIS_NGS2_MAX_VOICE_CHANNELS); - u32 uid; - u16 maxGrainSamples; - u16 minGrainSamples; - u16 currentGrainSamples; - u16 numGrainSamples; - u16 unknown2; +struct OrbisNgs2WaveformFormat { + u32 waveformType; + u32 numChannels; u32 sampleRate; - u32 unknown3; - - void* flushMutex; - u32 flushMutexInitialized; - void* processMutex; - u32 processMutexInitialized; - - // Linked list pointers for system list - Ngs2Handle* prev; - Ngs2Handle* next; + u32 configData; + u32 frameOffset; + u32 frameMargin; }; -struct SystemOptions { - char padding[6]; - s32 maxGrainSamples; - s32 numGrainSamples; - s32 sampleRate; +struct OrbisNgs2WaveformBlock { + u32 dataOffset; + u32 dataSize; + u32 numRepeats; + u32 numSkipSamples; + u32 numSamples; + u32 reserved; + uintptr_t userData; }; -struct SystemState { - // TODO +struct OrbisNgs2WaveformInfo { + OrbisNgs2WaveformFormat format; + + u32 dataOffset; + u32 dataSize; + + u32 loopBeginPosition; + u32 loopEndPosition; + u32 numSamples; + + u32 audioUnitSize; + u32 numAudioUnitSamples; + u32 numAudioUnitPerFrame; + + u32 audioFrameSize; + u32 numAudioFrameSamples; + + u32 numDelaySamples; + + u32 numBlocks; + OrbisNgs2WaveformBlock aBlock[ORBIS_NGS2_WAVEFORM_INFO_MAX_BLOCKS]; }; -struct StackBuffer { - void** top; - void* base; - void* curr; - size_t usedSize; - size_t totalSize; - size_t alignment; - char isVerifyEnabled; - char padding[7]; +struct OrbisNgs2EnvelopePoint { + u32 curve; + u32 duration; + float height; +}; + +struct OrbisNgs2UserFxProcessContext { + float** aChannelData; + uintptr_t userData0; + uintptr_t userData1; + uintptr_t userData2; + u32 flags; + u32 numChannels; + u32 numGrainSamples; + u32 sampleRate; +}; + +typedef s32 (*OrbisNgs2UserFxProcessHandler)(OrbisNgs2UserFxProcessContext* context); + +struct OrbisNgs2UserFx2SetupContext { + void* common; + void* param; + void* work; + uintptr_t userData; + u32 maxVoices; + u32 voiceIndex; + u64 reserved[4]; +}; + +typedef s32 (*OrbisNgs2UserFx2SetupHandler)(OrbisNgs2UserFx2SetupContext* context); + +struct OrbisNgs2UserFx2CleanupContext { + void* common; + void* param; + void* work; + uintptr_t userData; + u32 maxVoices; + u32 voiceIndex; + u64 reserved[4]; +}; + +typedef s32 (*OrbisNgs2UserFx2CleanupHandler)(OrbisNgs2UserFx2CleanupContext* context); + +struct OrbisNgs2UserFx2ControlContext { + const void* data; + size_t dataSize; + void* common; + void* param; + uintptr_t userData; + u64 reserved[4]; +}; + +typedef s32 (*OrbisNgs2UserFx2ControlHandler)(OrbisNgs2UserFx2ControlContext* context); + +struct OrbisNgs2UserFx2ProcessContext { + float** aChannelData; + void* common; + const void* param; + void* work; + void* state; + uintptr_t userData; + u32 flags; + u32 numInputChannels; + u32 numOutputChannels; + u32 numGrainSamples; + u32 sampleRate; + u32 reserved; + u64 reserved2[4]; +}; + +typedef s32 (*OrbisNgs2UserFx2ProcessHandler)(OrbisNgs2UserFx2ProcessContext* context); + +struct OrbisNgs2BufferAllocator { + OrbisNgs2BufferAllocHandler allocHandler; + OrbisNgs2BufferFreeHandler freeHandler; + uintptr_t userData; +}; + +struct OrbisNgs2RenderBufferInfo { + void* buffer; + size_t bufferSize; + u32 waveformType; + u32 numChannels; +}; + +struct OrbisNgs2RackOption { + size_t size; + char name[ORBIS_NGS2_RACK_NAME_LENGTH]; + + u32 flags; + u32 maxGrainSamples; + u32 maxVoices; + u32 maxInputDelayBlocks; + u32 maxMatrices; + u32 maxPorts; + u32 aReserved[20]; +}; + +struct OrbisNgs2VoiceParamHeader { + u16 size; + s16 next; + u32 id; +}; + +struct OrbisNgs2VoiceMatrixLevelsParam { + OrbisNgs2VoiceParamHeader header; + + u32 matrixId; + u32 numLevels; + const float* aLevel; +}; + +struct OrbisNgs2VoicePortMatrixParam { + OrbisNgs2VoiceParamHeader header; + + u32 port; + s32 matrixId; +}; + +struct OrbisNgs2VoicePortVolumeParam { + OrbisNgs2VoiceParamHeader header; + + u32 port; + float level; +}; + +struct OrbisNgs2VoicePortDelayParam { + OrbisNgs2VoiceParamHeader header; + + u32 port; + u32 numSamples; +}; + +struct OrbisNgs2VoicePatchParam { + OrbisNgs2VoiceParamHeader header; + + u32 port; + u32 destInputId; + OrbisNgs2Handle destHandle; +}; + +struct OrbisNgs2VoiceEventParam { + OrbisNgs2VoiceParamHeader header; + + u32 eventId; +}; + +struct OrbisNgs2VoiceCallbackInfo { + uintptr_t callbackData; + OrbisNgs2Handle voiceHandle; + u32 flag; + u32 reserved; + union { + struct { + uintptr_t userData; + const void* data; + u32 dataSize; + u32 repeatedCount; + u32 attributeFlags; + u32 reserved2; + } waveformBlock; + } param; +}; + +typedef void (*OrbisNgs2VoiceCallbackHandler)(const OrbisNgs2VoiceCallbackInfo* info); + +struct OrbisNgs2VoiceCallbackParam { + OrbisNgs2VoiceParamHeader header; + OrbisNgs2VoiceCallbackHandler callbackHandler; + + uintptr_t callbackData; + u32 flags; + u32 reserved; +}; + +struct OrbisNgs2VoicePortInfo { + s32 matrixId; + float volume; + u32 numDelaySamples; + u32 destInputId; + OrbisNgs2Handle destHandle; +}; + +struct OrbisNgs2VoiceMatrixInfo { + u32 numLevels; + float aLevel[ORBIS_NGS2_MAX_MATRIX_LEVELS]; +}; + +struct OrbisNgs2VoiceState { + u32 stateFlags; }; void RegisterlibSceNgs2(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/ngs2/ngs2_custom.cpp b/src/core/libraries/ngs2/ngs2_custom.cpp new file mode 100644 index 000000000..8c82e4e49 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_custom.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ngs2_error.h" +#include "ngs2_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Ngs2 {} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_custom.h b/src/core/libraries/ngs2/ngs2_custom.h new file mode 100644 index 000000000..0c45a5d81 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_custom.h @@ -0,0 +1,444 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" +#include "ngs2_reverb.h" + +namespace Libraries::Ngs2 { + +class Ngs2Custom; + +static const int ORBIS_NGS2_CUSTOM_MAX_MODULES = 24; +static const int ORBIS_NGS2_CUSTOM_MAX_PORTS = 16; +static const int ORBIS_NGS2_CUSTOM_DELAY_MAX_TAPS = 8; + +struct OrbisNgs2CustomModuleOption { + u32 size; +}; + +struct OrbisNgs2CustomEnvelopeModuleOption { + OrbisNgs2CustomModuleOption customModuleOption; + + u32 maxPoints; + u32 reserved; +}; + +struct OrbisNgs2CustomReverbModuleOption { + OrbisNgs2CustomModuleOption customModuleOption; + + u32 reverbSize; + u32 reserved; +}; + +struct OrbisNgs2CustomChorusModuleOption { + OrbisNgs2CustomModuleOption customModuleOption; + + u32 maxPhases; + u32 reserved; +} OrbisNgs2CustomChorusModuleOption; + +struct OrbisNgs2CustomPeakMeterModuleOption { + OrbisNgs2CustomModuleOption customModuleOption; + u32 numBlocks; + u32 reserved; +}; + +struct OrbisNgs2CustomDelayModuleOption { + OrbisNgs2CustomModuleOption customModuleOption; + + u32 type; + u32 maxTaps; + float maxLength; + u32 reserved; +}; + +struct OrbisNgs2CustomPitchShiftModuleOption { + OrbisNgs2CustomModuleOption customModuleOption; + + u32 quality; +}; + +struct OrbisNgs2CustomUserFx2ModuleOption { + OrbisNgs2CustomModuleOption customModuleOption; + + OrbisNgs2UserFx2SetupHandler setupHandler; + OrbisNgs2UserFx2CleanupHandler cleanupHandler; + OrbisNgs2UserFx2ControlHandler controlHandler; + OrbisNgs2UserFx2ProcessHandler processHandler; + + size_t commonSize; + size_t paramSize; + size_t workSize; + uintptr_t userData; +}; + +struct OrbisNgs2CustomRackModuleInfo { + const OrbisNgs2CustomModuleOption* option; + + u32 moduleId; + u32 sourceBufferId; + u32 extraBufferId; + u32 destBufferId; + u32 stateOffset; + u32 stateSize; + u32 reserved; + u32 reserved2; +}; + +struct OrbisNgs2CustomRackPortInfo { + u32 sourceBufferId; + u32 reserved; +}; + +struct OrbisNgs2CustomRackOption { + OrbisNgs2RackOption rackOption; + u32 stateSize; + u32 numBuffers; + u32 numModules; + u32 reserved; + OrbisNgs2CustomRackModuleInfo aModule[ORBIS_NGS2_CUSTOM_MAX_MODULES]; + OrbisNgs2CustomRackPortInfo aPort[ORBIS_NGS2_CUSTOM_MAX_PORTS]; +}; + +struct OrbisNgs2CustomSamplerRackOption { + OrbisNgs2CustomRackOption customRackOption; + + u32 maxChannelWorks; + u32 maxWaveformBlocks; + u32 maxAtrac9Decoders; + u32 maxAtrac9ChannelWorks; + u32 maxAjmAtrac9Decoders; + u32 maxCodecCaches; +}; + +struct OrbisNgs2CustomSubmixerRackOption { + OrbisNgs2CustomRackOption customRackOption; + + u32 maxChannels; + u32 maxInputs; +}; + +struct OrbisNgs2CustomMasteringRackOption { + OrbisNgs2CustomRackOption customRackOption; + + u32 maxChannels; + u32 maxInputs; +}; + +struct OrbisNgs2CustomSamplerVoiceSetupParam { + OrbisNgs2VoiceParamHeader header; + OrbisNgs2WaveformFormat format; + u32 flags; + u32 reserved; +}; + +struct OrbisNgs2CustomSamplerVoiceWaveformBlocksParam { + OrbisNgs2VoiceParamHeader header; + const void* data; + u32 flags; + u32 numBlocks; + const OrbisNgs2WaveformBlock* aBlock; +}; + +struct OrbisNgs2CustomSamplerVoiceWaveformAddressParam { + OrbisNgs2VoiceParamHeader header; + const void* from; + const void* to; +}; + +struct OrbisNgs2CustomSamplerVoiceWaveformFrameOffsetParam { + OrbisNgs2VoiceParamHeader header; + u32 frameOffset; + u32 reserved; +}; + +struct OrbisNgs2CustomSamplerVoiceExitLoopParam { + OrbisNgs2VoiceParamHeader header; +}; + +struct OrbisNgs2CustomSamplerVoicePitchParam { + OrbisNgs2VoiceParamHeader header; + float ratio; + u32 reserved; +}; + +struct OrbisNgs2CustomSamplerVoiceState { + OrbisNgs2VoiceState voiceState; + char padding[32]; + const void* waveformData; + u64 numDecodedSamples; + u64 decodedDataSize; + u64 userData; + u32 reserved; + u32 reserved2; +}; + +struct OrbisNgs2CustomSubmixerVoiceSetupParam { + OrbisNgs2VoiceParamHeader header; + u32 numInputChannels; + u32 numOutputChannels; + u32 flags; + u32 reserved; +}; + +struct OrbisNgs2CustomSubmixerVoiceState { + OrbisNgs2VoiceState voiceState; // Voice state + u32 reserved; + u32 reserved2; +}; + +struct OrbisNgs2CustomMasteringVoiceSetupParam { + OrbisNgs2VoiceParamHeader header; + u32 numInputChannels; + u32 flags; +}; + +struct OrbisNgs2CustomMasteringVoiceOutputParam { + OrbisNgs2VoiceParamHeader header; + u32 outputId; + u32 reserved; +}; + +struct OrbisNgs2CustomMasteringVoiceState { + OrbisNgs2VoiceState voiceState; + u32 reserved; + u32 reserved2; +}; + +struct OrbisNgs2CustomVoiceEnvelopeParam { + OrbisNgs2VoiceParamHeader header; + u32 numForwardPoints; + u32 numReleasePoints; + const OrbisNgs2EnvelopePoint* aPoint; +}; + +struct OrbisNgs2CustomVoiceDistortionParam { + OrbisNgs2VoiceParamHeader header; + u32 flags; + float a; + float b; + float clip; + float gate; + float wetLevel; + float dryLevel; + u32 reserved; +}; + +struct OrbisNgs2CustomVoiceCompressorParam { + OrbisNgs2VoiceParamHeader header; + u32 flags; + float threshold; + float ratio; + float knee; + float attackTime; + float releaseTime; + float level; + u32 reserved; +}; + +struct OrbisNgs2CustomVoiceFilterParam { + OrbisNgs2VoiceParamHeader header; + u32 type; + u32 channelMask; + union { + struct { + float i0; + float i1; + float i2; + float o1; + float o2; + } direct; + struct { + float fc; + float q; + float level; + u32 reserved; + u32 reserved2; + } fcq; + } param; + u32 reserved3; +}; + +struct OrbisNgs2CustomVoiceLfeFilterParam { + OrbisNgs2VoiceParamHeader header; + u32 enableFlag; + u32 fc; +}; + +struct OrbisNgs2CustomVoiceGainParam { + OrbisNgs2VoiceParamHeader header; + float aLevel[ORBIS_NGS2_MAX_VOICE_CHANNELS]; +}; + +struct OrbisNgs2CustomVoiceMixerParam { + OrbisNgs2VoiceParamHeader header; + float aSourceLevel[ORBIS_NGS2_MAX_VOICE_CHANNELS]; + float aDestLevel[ORBIS_NGS2_MAX_VOICE_CHANNELS]; +}; + +struct OrbisNgs2CustomVoiceChannelMixerParam { + OrbisNgs2VoiceParamHeader header; + float aLevel[ORBIS_NGS2_MAX_VOICE_CHANNELS][ORBIS_NGS2_MAX_VOICE_CHANNELS]; +}; + +struct OrbisNgs2CustomVoiceUserFxParam { + OrbisNgs2VoiceParamHeader header; + OrbisNgs2UserFxProcessHandler handler; + + uintptr_t userData0; + uintptr_t userData1; + uintptr_t userData2; +}; + +struct OrbisNgs2CustomVoiceUserFx2Param { + OrbisNgs2VoiceParamHeader header; + const void* data; + size_t dataSize; +}; + +struct OrbisNgs2CustomVoiceOutputParam { + OrbisNgs2VoiceParamHeader header; + u32 outputId; + u32 reserved; +}; + +struct OrbisNgs2CustomVoicePeakMeterParam { + OrbisNgs2VoiceParamHeader header; + u32 enableFlag; + u32 reserved; +} OrbisNgs2CustomVoicePeakMeterParam; + +struct OrbisNgs2CustomVoiceReverbParam { + OrbisNgs2VoiceParamHeader header; + OrbisNgs2ReverbI3DL2Param i3dl2; +}; + +struct OrbisNgs2CustomVoiceChorusParam { + OrbisNgs2VoiceParamHeader header; + u32 flags; + u32 numPhases; + u32 channelMask; + float inputLevel; + float delayTime; + float modulationRatio; + float modulationDepth; + float feedbackLevel; + float wetLevel; + float dryLevel; +}; + +struct OrbisNgs2DelayTapInfo { + float tapLevel; + float delayTime; +}; + +struct OrbisNgs2CustomVoiceDelayParam { + OrbisNgs2VoiceParamHeader header; + float dryLevel; + float wetLevel; + float inputLevel; + float feedbackLevel; + float lowpassFc; + u32 numTaps; + OrbisNgs2DelayTapInfo aTap[ORBIS_NGS2_CUSTOM_DELAY_MAX_TAPS]; + float aInputMixLevel[ORBIS_NGS2_MAX_VOICE_CHANNELS]; + u32 channelMask; + u32 flags; +}; + +struct OrbisNgs2CustomVoiceNoiseGateParam { + OrbisNgs2VoiceParamHeader header; + u32 flags; + float threshold; + float attackTime; + float releaseTime; +}; + +struct OrbisNgs2CustomVoicePitchShiftParam { + OrbisNgs2VoiceParamHeader header; + s32 cent; +}; + +struct OrbisNgs2CustomEnvelopeModuleState { + float height; + u32 reserved; +}; + +struct OrbisNgs2CustomCompressorModuleState { + float peakHeight; + float compressorHeight; +}; + +struct OrbisNgs2CustomPeakMeterModuleState { + float peak; + float aChannelPeak[ORBIS_NGS2_MAX_VOICE_CHANNELS]; + u32 reserved; +}; + +struct OrbisNgs2CustomNoiseGateModuleState { + float gateHeight; +}; + +struct OrbisNgs2CustomRackInfo { + OrbisNgs2RackInfo rackInfo; + u32 stateSize; + u32 numBuffers; + u32 numModules; + u32 reserved; + OrbisNgs2CustomRackModuleInfo aModule[ORBIS_NGS2_CUSTOM_MAX_MODULES]; + OrbisNgs2CustomRackPortInfo aPort[ORBIS_NGS2_CUSTOM_MAX_PORTS]; +}; + +struct OrbisNgs2CustomSamplerRackInfo { + OrbisNgs2CustomRackInfo customRackInfo; + + u32 maxChannelWorks; + u32 maxWaveformBlocks; + u32 maxAtrac9Decoders; + u32 maxAtrac9ChannelWorks; + u32 maxAjmAtrac9Decoders; + u32 maxCodecCaches; +}; + +struct OrbisNgs2CustomSubmixerRackInfo { + OrbisNgs2CustomRackInfo customRackInfo; + + u32 maxChannels; + u32 maxInputs; +}; + +struct OrbisNgs2CustomMasteringRackInfo { + OrbisNgs2CustomRackInfo customRackInfo; + + u32 maxChannels; + u32 maxInputs; +}; + +struct OrbisNgs2CustomModuleInfo { + u32 moduleId; + u32 sourceBufferId; + u32 extraBufferId; + u32 destBufferId; + u32 stateOffset; + u32 stateSize; + u32 reserved; + u32 reserved2; +}; + +struct OrbisNgs2CustomEnvelopeModuleInfo { + OrbisNgs2CustomModuleInfo moduleInfo; + + u32 maxPoints; + u32 reserved; +}; + +struct OrbisNgs2CustomReverbModuleInfo { + OrbisNgs2CustomModuleInfo moduleInfo; + + u32 reverbSize; + u32 reserved; +}; + +} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_eq.cpp b/src/core/libraries/ngs2/ngs2_eq.cpp new file mode 100644 index 000000000..8c82e4e49 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_eq.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ngs2_error.h" +#include "ngs2_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Ngs2 {} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_eq.h b/src/core/libraries/ngs2/ngs2_eq.h new file mode 100644 index 000000000..99688f24e --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_eq.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" + +namespace Libraries::Ngs2 { + +class Ngs2Eq; + +struct OrbisNgs2EqVoiceSetupParam { + u32 numChannels; +}; + +struct OrbisNgs2EqVoiceFilterParam { + u32 type; + u32 channelMask; + union { + struct { + float i0; + float i1; + float i2; + float o1; + float o2; + } direct; + struct { + float fc; + float q; + float level; + u32 reserved; + u32 reserved2; + } fcq; + } param; +}; + +struct OrbisNgs2EqVoiceState { + u32 stateFlags; +}; + +} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_geom.cpp b/src/core/libraries/ngs2/ngs2_geom.cpp new file mode 100644 index 000000000..8c82e4e49 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_geom.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ngs2_error.h" +#include "ngs2_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Ngs2 {} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_geom.h b/src/core/libraries/ngs2/ngs2_geom.h new file mode 100644 index 000000000..93af99d8d --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_geom.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" + +namespace Libraries::Ngs2 { + +class Ngs2Geom; + +struct OrbisNgs2GeomVector { + float x; + float y; + float z; +}; + +struct OrbisNgs2GeomCone { + float innerLevel; + float innerAngle; + float outerLevel; + float outerAngle; +}; + +struct OrbisNgs2GeomRolloff { + u32 model; + float maxDistance; + float rolloffFactor; + float referenceDistance; +}; + +struct OrbisNgs2GeomListenerParam { + OrbisNgs2GeomVector position; + OrbisNgs2GeomVector orientFront; + OrbisNgs2GeomVector orientUp; + OrbisNgs2GeomVector velocity; + float soundSpeed; + u32 reserved[2]; +}; + +struct OrbisNgs2GeomListenerWork { + float matrix[4][4]; + OrbisNgs2GeomVector velocity; + float soundSpeed; + u32 coordinate; + u32 reserved[3]; +}; + +struct OrbisNgs2GeomSourceParam { + OrbisNgs2GeomVector position; + OrbisNgs2GeomVector velocity; + OrbisNgs2GeomVector direction; + OrbisNgs2GeomCone cone; + OrbisNgs2GeomRolloff rolloff; + float dopplerFactor; + float fbwLevel; + float lfeLevel; + float maxLevel; + float minLevel; + float radius; + u32 numSpeakers; + u32 matrixFormat; + u32 reserved[2]; +}; + +struct OrbisNgs2GeomA3dAttribute { + OrbisNgs2GeomVector position; + float volume; + u32 reserved[4]; +}; + +struct OrbisNgs2GeomAttribute { + float pitchRatio; + float aLevel[ORBIS_NGS2_MAX_VOICE_CHANNELS * ORBIS_NGS2_MAX_VOICE_CHANNELS]; + + OrbisNgs2GeomA3dAttribute a3dAttrib; + u32 reserved[4]; +}; + +} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_impl.cpp b/src/core/libraries/ngs2/ngs2_impl.cpp index b358a05f7..1248f76d7 100644 --- a/src/core/libraries/ngs2/ngs2_impl.cpp +++ b/src/core/libraries/ngs2/ngs2_impl.cpp @@ -12,153 +12,171 @@ using namespace Libraries::Kernel; namespace Libraries::Ngs2 { -s32 Ngs2::ReportInvalid(Ngs2Handle* handle, u32 handle_type) const { - uintptr_t hAddress = reinterpret_cast(handle); - switch (handle_type) { +s32 HandleReportInvalid(OrbisNgs2Handle handle, u32 handleType) { + switch (handleType) { case 1: - LOG_ERROR(Lib_Ngs2, "Invalid system handle {}", hAddress); + LOG_ERROR(Lib_Ngs2, "Invalid system handle {}", handle); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; case 2: - LOG_ERROR(Lib_Ngs2, "Invalid rack handle {}", hAddress); + LOG_ERROR(Lib_Ngs2, "Invalid rack handle {}", handle); return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; case 4: - LOG_ERROR(Lib_Ngs2, "Invalid voice handle {}", hAddress); + LOG_ERROR(Lib_Ngs2, "Invalid voice handle {}", handle); return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; case 8: - LOG_ERROR(Lib_Ngs2, "Invalid report handle {}", hAddress); + LOG_ERROR(Lib_Ngs2, "Invalid report handle {}", handle); return ORBIS_NGS2_ERROR_INVALID_REPORT_HANDLE; default: - LOG_ERROR(Lib_Ngs2, "Invalid handle {}", hAddress); + LOG_ERROR(Lib_Ngs2, "Invalid handle {}", handle); return ORBIS_NGS2_ERROR_INVALID_HANDLE; } } -s32 Ngs2::HandleSetup(Ngs2Handle* handle, void* data, std::atomic* atomic, u32 type, - u32 flags) { - handle->dataPointer = data; - handle->atomicPtr = atomic; - handle->handleType = type; - handle->flags_unk = flags; - return ORBIS_OK; +void* MemoryClear(void* buffer, size_t size) { + return memset(buffer, 0, size); } -s32 Ngs2::HandleCleanup(Ngs2Handle* handle, u32 hType, void* dataOut) { - if (handle && handle->selfPointer == handle) { - std::atomic* tmp_atomic = handle->atomicPtr; - if (tmp_atomic && handle->handleType == hType) { - while (tmp_atomic->load() != 0) { - u32 expected = 1; - if (tmp_atomic->compare_exchange_strong(expected, 0)) { - if (dataOut) { - dataOut = handle->dataPointer; - } - // sceNgs2MemoryClear(handle, 32); - return ORBIS_OK; - } - tmp_atomic = handle->atomicPtr; - } - } - } - return this->ReportInvalid(handle, hType); -} - -s32 Ngs2::HandleEnter(Ngs2Handle* handle, u32 hType, Ngs2Handle* handleOut) { - if (!handle) { - return this->ReportInvalid(handle, 0); - } - - if (handle->selfPointer != handle || !handle->atomicPtr || !handle->dataPointer || - (~hType & handle->handleType)) { - return this->ReportInvalid(handle, handle->handleType); - } - - std::atomic* atomic = handle->atomicPtr; - while (true) { - u32 i = atomic->load(); - if (i == 0) { - return this->ReportInvalid(handle, handle->handleType); - } - if (atomic->compare_exchange_strong(i, i + 1)) { - break; - } - } - - if (handleOut) { - handleOut = handle; +s32 StackBufferClose(StackBuffer* stackBuffer, size_t* outTotalSize) { + if (outTotalSize) { + *outTotalSize = stackBuffer->usedSize + stackBuffer->alignment; } return ORBIS_OK; } -s32 Ngs2::HandleLeave(Ngs2Handle* handle) { - std::atomic* tmp_atomic; - u32 i; - do { - tmp_atomic = handle->atomicPtr; - i = tmp_atomic->load(); - } while (!tmp_atomic->compare_exchange_strong(i, i - 1)); - return ORBIS_OK; -} +s32 StackBufferOpen(StackBuffer* stackBuffer, void* bufferStart, size_t bufferSize, + void** outBuffer, u8 flags) { + stackBuffer->top = outBuffer; + stackBuffer->base = bufferStart; + stackBuffer->size = (size_t)bufferStart; + stackBuffer->currentOffset = (size_t)bufferStart; + stackBuffer->usedSize = 0; + stackBuffer->totalSize = bufferSize; + stackBuffer->alignment = 8; // this is a fixed value + stackBuffer->flags = flags; -s32 Ngs2::StackBufferOpen(StackBuffer* buf, void* base_addr, size_t size, void** stackTop, - bool verify) { - buf->top = stackTop; - buf->base = base_addr; - buf->curr = base_addr; - buf->usedSize = 0; - buf->totalSize = size; - buf->alignment = 8; - buf->isVerifyEnabled = verify; - - if (stackTop) { - *stackTop = nullptr; + if (outBuffer != NULL) { + *outBuffer = NULL; } return ORBIS_OK; } -s32 Ngs2::StackBufferClose(StackBuffer* buf, size_t* usedSize) { - if (usedSize) { - *usedSize = buf->usedSize + buf->alignment; +s32 SystemCleanup(OrbisNgs2Handle systemHandle, OrbisNgs2ContextBufferInfo* outInfo) { + if (!systemHandle) { + return ORBIS_NGS2_ERROR_INVALID_HANDLE; } + // TODO + return ORBIS_OK; } -s32 Ngs2::SystemSetupCore(StackBuffer* buf, SystemOptions* options, Ngs2Handle** sysOut) { +s32 SystemSetupCore(StackBuffer* stackBuffer, const OrbisNgs2SystemOption* option, + SystemInternal* outSystem) { u32 maxGrainSamples = 512; u32 numGrainSamples = 256; u32 sampleRate = 48000; - if (options) { - maxGrainSamples = options->maxGrainSamples; - numGrainSamples = options->numGrainSamples; - sampleRate = options->sampleRate; + if (option) { + sampleRate = option->sampleRate; + maxGrainSamples = option->maxGrainSamples; + numGrainSamples = option->numGrainSamples; } - // Validate maxGrainSamples - if (maxGrainSamples < 64 || maxGrainSamples > 1024 || (maxGrainSamples & 0x3F) != 0) { + if (maxGrainSamples < 64 || maxGrainSamples > 1024 || (maxGrainSamples & 63) != 0) { LOG_ERROR(Lib_Ngs2, "Invalid system option (maxGrainSamples={},x64)", maxGrainSamples); return ORBIS_NGS2_ERROR_INVALID_MAX_GRAIN_SAMPLES; } - // Validate numGrainSamples - if (numGrainSamples < 64 || numGrainSamples > 1024 || (numGrainSamples & 0x3F) != 0) { + if (numGrainSamples < 64 || numGrainSamples > 1024 || (numGrainSamples & 63) != 0) { LOG_ERROR(Lib_Ngs2, "Invalid system option (numGrainSamples={},x64)", numGrainSamples); return ORBIS_NGS2_ERROR_INVALID_NUM_GRAIN_SAMPLES; } - // Validate sampleRate if (sampleRate != 11025 && sampleRate != 12000 && sampleRate != 22050 && sampleRate != 24000 && - sampleRate != 44100 && sampleRate != 48000 && sampleRate != 88200 && sampleRate != 96000) { + sampleRate != 44100 && sampleRate != 48000 && sampleRate != 88200 && sampleRate != 96000 && + sampleRate != 176400 && sampleRate != 192000) { LOG_ERROR(Lib_Ngs2, "Invalid system option(sampleRate={}:44.1/48kHz series)", sampleRate); return ORBIS_NGS2_ERROR_INVALID_SAMPLE_RATE; } - int result = ORBIS_OK; + return ORBIS_OK; +} +s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* hostBufferInfo, + OrbisNgs2BufferFreeHandler hostFree, OrbisNgs2Handle* outHandle) { + u8 optionFlags = 0; + StackBuffer stackBuffer; + SystemInternal setupResult; + void* systemList = NULL; + size_t requiredBufferSize = 0; + u32 result = ORBIS_NGS2_ERROR_INVALID_BUFFER_SIZE; + + if (option) { + if (option->size != 64) { + LOG_ERROR(Lib_Ngs2, "Invalid system option size ({})", option->size); + return ORBIS_NGS2_ERROR_INVALID_OPTION_SIZE; + } + optionFlags = option->flags >> 31; + } + + // Init + StackBufferOpen(&stackBuffer, NULL, 0, NULL, optionFlags); + result = SystemSetupCore(&stackBuffer, option, 0); + + if (result < 0) { + return result; + } + + StackBufferClose(&stackBuffer, &requiredBufferSize); + + // outHandle unprovided + if (!outHandle) { + hostBufferInfo->hostBuffer = NULL; + hostBufferInfo->hostBufferSize = requiredBufferSize; + MemoryClear(&hostBufferInfo->reserved, sizeof(hostBufferInfo->reserved)); + return ORBIS_OK; + } + + if (!hostBufferInfo->hostBuffer) { + LOG_ERROR(Lib_Ngs2, "Invalid system buffer address ({})", hostBufferInfo->hostBuffer); + return ORBIS_NGS2_ERROR_INVALID_BUFFER_ADDRESS; + } + + if (hostBufferInfo->hostBufferSize < requiredBufferSize) { + LOG_ERROR(Lib_Ngs2, "Invalid system buffer size ({}<{}[byte])", + hostBufferInfo->hostBufferSize, requiredBufferSize); + return ORBIS_NGS2_ERROR_INVALID_BUFFER_SIZE; + } + + // Setup + StackBufferOpen(&stackBuffer, hostBufferInfo->hostBuffer, hostBufferInfo->hostBufferSize, + &systemList, optionFlags); + result = SystemSetupCore(&stackBuffer, option, &setupResult); + + if (result < 0) { + return result; + } + + StackBufferClose(&stackBuffer, &requiredBufferSize); + + // Copy buffer results + setupResult.bufferInfo = *hostBufferInfo; + setupResult.hostFree = hostFree; // TODO + // setupResult.systemList = systemList; - return result; // Success + OrbisNgs2Handle systemHandle = setupResult.systemHandle; + if (hostBufferInfo->hostBufferSize >= requiredBufferSize) { + *outHandle = systemHandle; + return ORBIS_OK; + } + + SystemCleanup(systemHandle, 0); + + LOG_ERROR(Lib_Ngs2, "Invalid system buffer size ({}<{}[byte])", hostBufferInfo->hostBufferSize, + requiredBufferSize); + return ORBIS_NGS2_ERROR_INVALID_BUFFER_SIZE; } } // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_impl.h b/src/core/libraries/ngs2/ngs2_impl.h index fea87c51c..7be0f89cc 100644 --- a/src/core/libraries/ngs2/ngs2_impl.h +++ b/src/core/libraries/ngs2/ngs2_impl.h @@ -3,23 +3,176 @@ #pragma once -#include "ngs2.h" +#include "core/libraries/kernel/threads/pthread.h" namespace Libraries::Ngs2 { -class Ngs2 { -public: - s32 ReportInvalid(Ngs2Handle* handle, u32 handle_type) const; - s32 HandleSetup(Ngs2Handle* handle, void* data, std::atomic* atomic, u32 type, u32 flags); - s32 HandleCleanup(Ngs2Handle* handle, u32 hType, void* dataOut); - s32 HandleEnter(Ngs2Handle* handle, u32 hType, Ngs2Handle* handleOut); - s32 HandleLeave(Ngs2Handle* handle); - s32 StackBufferOpen(StackBuffer* buf, void* base_addr, size_t size, void** stackTop, - bool verify); - s32 StackBufferClose(StackBuffer* buf, size_t* usedSize); - s32 SystemSetupCore(StackBuffer* buf, SystemOptions* options, Ngs2Handle** sysOut); +static const int ORBIS_NGS2_SYSTEM_NAME_LENGTH = 16; +static const int ORBIS_NGS2_RACK_NAME_LENGTH = 16; -private: +typedef uintptr_t OrbisNgs2Handle; + +struct OrbisNgs2ContextBufferInfo { + void* hostBuffer; + size_t hostBufferSize; + uintptr_t reserved[5]; + uintptr_t userData; }; +struct OrbisNgs2SystemOption { + size_t size; + char name[ORBIS_NGS2_SYSTEM_NAME_LENGTH]; + + u32 flags; + u32 maxGrainSamples; + u32 numGrainSamples; + u32 sampleRate; + u32 aReserved[6]; +}; + +typedef s32 (*OrbisNgs2BufferAllocHandler)(OrbisNgs2ContextBufferInfo* ioBufferInfo); +typedef s32 (*OrbisNgs2BufferFreeHandler)(OrbisNgs2ContextBufferInfo* ioBufferInfo); + +struct OrbisNgs2SystemInfo { + char name[ORBIS_NGS2_SYSTEM_NAME_LENGTH]; // 0 + + OrbisNgs2Handle systemHandle; // 16 + OrbisNgs2ContextBufferInfo bufferInfo; // 24 + + u32 uid; // 88 + u32 minGrainSamples; // 92 + u32 maxGrainSamples; // 96 + + u32 stateFlags; // 100 + u32 rackCount; // 104 + float lastRenderRatio; // 108 + s64 lastRenderTick; // 112 + s64 renderCount; // 120 + u32 sampleRate; // 128 + u32 numGrainSamples; // 132 +}; + +struct OrbisNgs2RackInfo { + char name[ORBIS_NGS2_RACK_NAME_LENGTH]; // 0 + + OrbisNgs2Handle rackHandle; // 16 + OrbisNgs2ContextBufferInfo bufferInfo; // 24 + + OrbisNgs2Handle ownerSystemHandle; // 88 + + u32 type; // 96 + u32 rackId; // 100 + u32 uid; // 104 + u32 minGrainSamples; // 108 + u32 maxGrainSamples; // 112 + u32 maxVoices; // 116 + u32 maxChannelWorks; // 120 + u32 maxInputs; // 124 + u32 maxMatrices; // 128 + u32 maxPorts; // 132 + + u32 stateFlags; // 136 + float lastProcessRatio; // 140 + u64 lastProcessTick; // 144 + u64 renderCount; // 152 + u32 activeVoiceCount; // 160 + u32 activeChannelWorkCount; // 164 +}; + +struct StackBuffer { + void** top; + void* base; + size_t size; + size_t currentOffset; + size_t usedSize; + size_t totalSize; + size_t alignment; + u8 flags; + char padding[7]; +}; + +struct SystemInternal { + // setup init + char name[ORBIS_NGS2_SYSTEM_NAME_LENGTH]; // 0 + OrbisNgs2ContextBufferInfo bufferInfo; // 16 + OrbisNgs2BufferFreeHandler hostFree; // 80 + OrbisNgs2Handle systemHandle; // 88 + void* unknown1; // 96 + void* unknown2; // 104 + OrbisNgs2Handle rackHandle; // 112 + uintptr_t* userData; // 120 + SystemInternal* systemList; // 128 + StackBuffer* stackBuffer; // 136 + OrbisNgs2SystemInfo ownerSystemInfo; // 144 + + struct rackList { + void* prev; + void* next; + void* unknown; + }; + + rackList rackListPreset; // 152 + rackList rackListNormal; // 176 + rackList rackListMaster; // 200 + + void* unknown3; // 208 + void* systemListPrev; // 216 + void* unknown4; // 224 + void* systemListNext; // 232 + void* rackFunction; // 240 + + Kernel::PthreadMutex processLock; // 248 + u32 hasProcessMutex; // 256 + u32 unknown5; // 260 + Kernel::PthreadMutex flushLock; // 264 + u32 hasFlushMutex; // 272 + u32 unknown6; // 276 + + // info + u64 lastRenderTick; // 280 + u64 renderCount; // 288 + u32 isActive; // 296 + std::atomic lockCount; // 300 + u32 uid; // 304 + u32 systemType; // 308 + + struct { + u8 isBufferValid : 1; + u8 isRendering : 1; + u8 isSorted : 1; + u8 isFlushReady : 1; + } flags; // 312 + + u16 currentMaxGrainSamples; // 316 + u16 minGrainSamples; // 318 + u16 maxGrainSamples; // 320 + u16 numGrainSamples; // 322 + u32 currentNumGrainSamples; // 324 + u32 sampleRate; // 328 + u32 currentSampleRate; // 332 + u32 rackCount; // 336 + float lastRenderRatio; // 340 + float cpuLoad; // 344 +}; + +struct HandleInternal { + HandleInternal* selfPtr; // 0 + SystemInternal* systemData; // 8 + std::atomic refCount; // 16 + u32 handleType; // 24 + u32 handleID; // 28 +}; + +s32 StackBufferClose(StackBuffer* stackBuffer, size_t* outTotalSize); +s32 StackBufferOpen(StackBuffer* stackBuffer, void* buffer, size_t bufferSize, void** outBuffer, + u8 flags); +s32 SystemSetupCore(StackBuffer* stackBuffer, const OrbisNgs2SystemOption* option, + SystemInternal* outSystem); + +s32 HandleReportInvalid(OrbisNgs2Handle handle, u32 handleType); +void* MemoryClear(void* buffer, size_t size); +s32 SystemCleanup(OrbisNgs2Handle systemHandle, OrbisNgs2ContextBufferInfo* outInfo); +s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* hostBufferInfo, + OrbisNgs2BufferFreeHandler hostFree, OrbisNgs2Handle* outHandle); + } // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_mastering.cpp b/src/core/libraries/ngs2/ngs2_mastering.cpp new file mode 100644 index 000000000..8c82e4e49 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_mastering.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ngs2_error.h" +#include "ngs2_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Ngs2 {} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_mastering.h b/src/core/libraries/ngs2/ngs2_mastering.h new file mode 100644 index 000000000..e0ba478c3 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_mastering.h @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" + +namespace Libraries::Ngs2 { + +class Ngs2Mastering; + +struct OrbisNgs2MasteringRackOption { + OrbisNgs2RackOption rackOption; + u32 maxChannels; + u32 numPeakMeterBlocks; +}; + +struct OrbisNgs2MasteringVoiceSetupParam { + OrbisNgs2VoiceParamHeader header; + + u32 numInputChannels; + u32 flags; +}; + +struct OrbisNgs2MasteringVoiceMatrixParam { + OrbisNgs2VoiceParamHeader header; + + u32 type; + u32 numLevels; + const float* aLevel; +}; + +struct OrbisNgs2MasteringVoiceLfeParam { + OrbisNgs2VoiceParamHeader header; + + u32 enableFlag; + u32 fc; +}; + +struct OrbisNgs2MasteringVoiceLimiterParam { + OrbisNgs2VoiceParamHeader header; + + u32 enableFlag; + float threshold; +}; + +struct OrbisNgs2MasteringVoiceGainParam { + OrbisNgs2VoiceParamHeader header; + + float fbwLevel; + float lfeLevel; +}; + +struct OrbisNgs2MasteringVoiceOutputParam { + OrbisNgs2VoiceParamHeader header; + + u32 outputId; + u32 reserved; +}; + +struct OrbisNgs2MasteringVoicePeakMeterParam { + OrbisNgs2VoiceParamHeader header; + u32 enableFlag; + u32 reserved; +}; + +struct OrbisNgs2MasteringVoiceState { + OrbisNgs2VoiceState voiceState; + float limiterPeakLevel; + float limiterPressLevel; + float aInputPeakHeight[ORBIS_NGS2_MAX_VOICE_CHANNELS]; + float aOutputPeakHeight[ORBIS_NGS2_MAX_VOICE_CHANNELS]; +}; + +struct OrbisNgs2MasteringRackInfo { + OrbisNgs2RackInfo rackInfo; + u32 maxChannels; + u32 reserved; +}; + +} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_pan.cpp b/src/core/libraries/ngs2/ngs2_pan.cpp new file mode 100644 index 000000000..8c82e4e49 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_pan.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ngs2_error.h" +#include "ngs2_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Ngs2 {} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_pan.h b/src/core/libraries/ngs2/ngs2_pan.h new file mode 100644 index 000000000..d39ec67cd --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_pan.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" + +namespace Libraries::Ngs2 { + +class Ngs2Pan; + +struct OrbisNgs2PanParam { + float angle; + float distance; + float fbwLevel; + float lfeLevel; +}; + +struct OrbisNgs2PanWork { + float aSpeakerAngle[ORBIS_NGS2_MAX_VOICE_CHANNELS]; + float unitAngle; + u32 numSpeakers; +}; + +} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_report.cpp b/src/core/libraries/ngs2/ngs2_report.cpp new file mode 100644 index 000000000..8c82e4e49 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_report.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ngs2_error.h" +#include "ngs2_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Ngs2 {} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_report.h b/src/core/libraries/ngs2/ngs2_report.h new file mode 100644 index 000000000..88f6d1df0 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_report.h @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" + +#include // va_list + +namespace Libraries::Ngs2 { + +class Ngs2Report; + +struct OrbisNgs2ReportDataHeader { + size_t size; + OrbisNgs2Handle handle; + u32 type; + s32 result; +}; + +typedef void (*OrbisNgs2ReportHandler)(const OrbisNgs2ReportDataHeader* data, uintptr_t userData); + +struct OrbisNgs2ReportMessageData { + OrbisNgs2ReportDataHeader header; + const char* message; +}; + +struct OrbisNgs2ReportApiData { + OrbisNgs2ReportDataHeader header; + const char* functionName; + const char* format; + va_list argument; +}; + +struct OrbisNgs2ReportControlData { + OrbisNgs2ReportDataHeader header; + const OrbisNgs2VoiceParamHeader* param; +}; + +struct OrbisNgs2ReportOutputData { + OrbisNgs2ReportDataHeader header; + const OrbisNgs2RenderBufferInfo* bufferInfo; + + u32 bufferIndex; + u32 sampleRate; + u32 numGrainSamples; + u32 reserved; +}; + +struct OrbisNgs2ReportCpuLoadData { + OrbisNgs2ReportDataHeader header; + float totalRatio; + float flushRatio; + float processRatio; + float feedbackRatio; +}; + +struct OrbisNgs2ReportRenderStateData { + OrbisNgs2ReportDataHeader header; + u32 state; + u32 reserved; +}; + +struct OrbisNgs2ReportVoiceWaveformData { + OrbisNgs2ReportDataHeader header; + u32 location; + u32 waveformType; + u32 numChannels; + u32 sampleRate; + u32 numGrainSamples; + u32 reserved; + void* const* aData; +}; + +s32 PS4_SYSV_ABI sceNgs2ReportRegisterHandler(u32 reportType, OrbisNgs2ReportHandler handler, + uintptr_t userData, OrbisNgs2Handle* outHandle); + +} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_reverb.cpp b/src/core/libraries/ngs2/ngs2_reverb.cpp new file mode 100644 index 000000000..8c82e4e49 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_reverb.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ngs2_error.h" +#include "ngs2_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Ngs2 {} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_reverb.h b/src/core/libraries/ngs2/ngs2_reverb.h new file mode 100644 index 000000000..715d7480a --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_reverb.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" + +namespace Libraries::Ngs2 { + +class Ngs2Reverb; + +struct OrbisNgs2ReverbRackOption { + OrbisNgs2RackOption rackOption; + u32 maxChannels; + u32 reverbSize; +}; + +struct OrbisNgs2ReverbI3DL2Param { + float wet; + float dry; + s32 room; + s32 roomHF; + u32 reflectionPattern; + float decayTime; + float decayHFRatio; + s32 reflections; + float reflectionsDelay; + s32 reverb; + float reverbDelay; + float diffusion; + float density; + float HFReference; + u32 reserve[8]; +}; + +struct OrbisNgs2ReverbVoiceSetupParam { + OrbisNgs2VoiceParamHeader header; + + u32 numInputChannels; + u32 numOutputChannels; + u32 flags; + u32 reserved; +}; + +struct OrbisNgs2ReverbVoiceI3DL2Param { + OrbisNgs2VoiceParamHeader header; + + OrbisNgs2ReverbI3DL2Param i3dl2; +}; + +struct OrbisNgs2ReverbVoiceState { + OrbisNgs2VoiceState voiceState; +}; + +struct OrbisNgs2ReverbRackInfo { + OrbisNgs2RackInfo rackInfo; + u32 maxChannels; + u32 reverbSize; +}; + +} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_sampler.cpp b/src/core/libraries/ngs2/ngs2_sampler.cpp new file mode 100644 index 000000000..8c82e4e49 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_sampler.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ngs2_error.h" +#include "ngs2_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Ngs2 {} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_sampler.h b/src/core/libraries/ngs2/ngs2_sampler.h new file mode 100644 index 000000000..0842b9cb2 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_sampler.h @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" + +namespace Libraries::Ngs2 { + +class Ngs2Sampler; + +struct OrbisNgs2SamplerRackOption { + OrbisNgs2RackOption rackOption; + u32 maxChannelWorks; + u32 maxCodecCaches; + u32 maxWaveformBlocks; + u32 maxEnvelopePoints; + u32 maxFilters; + u32 maxAtrac9Decoders; + u32 maxAtrac9ChannelWorks; + u32 maxAjmAtrac9Decoders; + u32 numPeakMeterBlocks; +}; + +struct OrbisNgs2SamplerVoiceSetupParam { + OrbisNgs2VoiceParamHeader header; + + OrbisNgs2WaveformFormat format; + u32 flags; + u32 reserved; +}; + +struct OrbisNgs2SamplerVoiceWaveformBlocksParam { + OrbisNgs2VoiceParamHeader header; + + const void* data; + u32 flags; + u32 numBlocks; + const OrbisNgs2WaveformBlock* aBlock; + // Blocks +}; + +struct OrbisNgs2SamplerVoiceWaveformAddressParam { + OrbisNgs2VoiceParamHeader header; + + const void* from; + const void* to; +}; + +struct OrbisNgs2SamplerVoiceWaveformFrameOffsetParam { + OrbisNgs2VoiceParamHeader header; + + u32 frameOffset; + u32 reserved; +}; + +struct OrbisNgs2SamplerVoiceExitLoopParam { + OrbisNgs2VoiceParamHeader header; +}; + +struct OrbisNgs2SamplerVoicePitchParam { + OrbisNgs2VoiceParamHeader header; + + float ratio; + u32 reserved; +}; + +struct OrbisNgs2SamplerVoiceEnvelopeParam { + OrbisNgs2VoiceParamHeader header; + + u32 numForwardPoints; + u32 numReleasePoints; + const OrbisNgs2EnvelopePoint* aPoint; +}; + +struct OrbisNgs2SamplerVoiceDistortionParam { + OrbisNgs2VoiceParamHeader header; + + u32 flags; + float a; + float b; + float clip; + float gate; + float wetLevel; + float dryLevel; + u32 reserved; +}; + +struct OrbisNgs2SamplerVoiceUserFxParam { + OrbisNgs2VoiceParamHeader header; + + OrbisNgs2UserFxProcessHandler handler; + + uintptr_t userData0; + uintptr_t userData1; + uintptr_t userData2; +}; + +struct OrbisNgs2SamplerVoicePeakMeterParam { + OrbisNgs2VoiceParamHeader header; + + u32 enableFlag; + u32 reserved; +}; + +struct OrbisNgs2SamplerVoiceFilterParam { + OrbisNgs2VoiceParamHeader header; + + u32 index; + u32 location; + u32 type; + u32 channelMask; + union { + struct { + float i0; + float i1; + float i2; + float o1; + float o2; + } direct; + struct { + float fc; + float q; + float level; + u32 reserved; + u32 reserved2; + } fcq; + } param; + u32 reserved3; +}; + +struct OrbisNgs2SamplerVoiceNumFilters { + OrbisNgs2VoiceParamHeader header; + + u32 numFilters; + u32 reserved; +}; + +struct OrbisNgs2SamplerVoiceState { + OrbisNgs2VoiceState voiceState; + float envelopeHeight; + float peakHeight; + u32 reserved; + u64 numDecodedSamples; + u64 decodedDataSize; + u64 userData; + const void* waveformData; +}; + +struct OrbisNgs2SamplerRackInfo { + OrbisNgs2RackInfo rackInfo; + u32 maxChannelWorks; + u32 maxCodecCaches; + u32 maxWaveformBlocks; + u32 maxEnvelopePoints; + u32 maxFilters; + u32 maxAtrac9Decoders; + u32 maxAtrac9ChannelWorks; + u32 maxAjmAtrac9Decoders; +}; + +} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_submixer.cpp b/src/core/libraries/ngs2/ngs2_submixer.cpp new file mode 100644 index 000000000..8c82e4e49 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_submixer.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ngs2_error.h" +#include "ngs2_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Ngs2 {} // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_submixer.h b/src/core/libraries/ngs2/ngs2_submixer.h new file mode 100644 index 000000000..df2d8a835 --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_submixer.h @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" + +namespace Libraries::Ngs2 { + +class Ngs2Submixer; + +struct OrbisNgs2SubmixerRackOption { + OrbisNgs2RackOption rackOption; + u32 maxChannels; + u32 maxEnvelopePoints; + u32 maxFilters; + u32 maxInputs; + u32 numPeakMeterBlocks; +}; + +struct OrbisNgs2SubmixerVoiceSetupParam { + OrbisNgs2VoiceParamHeader header; + u32 numIoChannels; + u32 flags; +}; + +struct OrbisNgs2SubmixerVoiceEnvelopeParam { + OrbisNgs2VoiceParamHeader header; + + u32 numForwardPoints; + u32 numReleasePoints; + const OrbisNgs2EnvelopePoint* aPoint; +}; + +struct OrbisNgs2SubmixerVoiceCompressorParam { + OrbisNgs2VoiceParamHeader header; + + u32 flags; + float threshold; + float ratio; + float knee; + float attackTime; + float releaseTime; + float level; + u32 reserved; +}; + +struct OrbisNgs2SubmixerVoiceDistortionParam { + OrbisNgs2VoiceParamHeader header; + + u32 flags; + float a; + float b; + float clip; + float gate; + float wetLevel; + float dryLevel; + u32 reserved; +}; + +struct OrbisNgs2SubmixerVoiceUserFxParam { + OrbisNgs2VoiceParamHeader header; + + OrbisNgs2UserFxProcessHandler handler; + + uintptr_t userData0; + uintptr_t userData1; + uintptr_t userData2; +}; + +struct OrbisNgs2SubmixerVoicePeakMeterParam { + OrbisNgs2VoiceParamHeader header; + + u32 enableFlag; + u32 reserved; +}; + +struct OrbisNgs2SubmixerVoiceFilterParam { + OrbisNgs2VoiceParamHeader header; + + u32 index; + u32 location; + u32 type; + u32 channelMask; + union { + struct { + float i0; + float i1; + float i2; + float o1; + float o2; + } direct; + struct { + float fc; + float q; + float level; + u32 reserved; + u32 reserved2; + } fcq; + } param; + u32 reserved3; +}; + +struct OrbisNgs2SubmixerVoiceNumFilters { + OrbisNgs2VoiceParamHeader header; + + u32 numFilters; + u32 reserved; +}; + +struct OrbisNgs2SubmixerVoiceState { + OrbisNgs2VoiceState voiceState; + float envelopeHeight; + float peakHeight; + float compressorHeight; +}; + +struct OrbisNgs2SubmixerRackInfo { + OrbisNgs2RackInfo rackInfo; + u32 maxChannels; + u32 maxEnvelopePoints; + u32 maxFilters; + u32 maxInputs; +}; + +} // namespace Libraries::Ngs2 From 9c37aa039bff93a2dd11f1aeb500cc047bc6d817 Mon Sep 17 00:00:00 2001 From: illusion0001 <37698908+illusion0001@users.noreply.github.com> Date: Thu, 27 Mar 2025 09:50:21 +1300 Subject: [PATCH 071/194] Add isDevKit bool (#2685) --- src/common/config.cpp | 8 ++++++++ src/common/config.h | 1 + src/core/memory.cpp | 10 ++++++++++ 3 files changed, 19 insertions(+) diff --git a/src/common/config.cpp b/src/common/config.cpp index e0a348fbe..16d9e5724 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -32,6 +32,7 @@ std::filesystem::path find_fs_path_or(const basic_value& v, const K& ky, namespace Config { static bool isNeo = false; +static bool isDevKit = false; static bool playBGM = false; static bool isTrophyPopupDisabled = false; static int BGMvolume = 50; @@ -167,6 +168,10 @@ bool isNeoModeConsole() { return isNeo; } +bool isDevKitConsole() { + return isDevKit; +} + bool getIsFullscreen() { return isFullscreen; } @@ -755,6 +760,7 @@ void load(const std::filesystem::path& path) { const toml::value& general = data.at("General"); isNeo = toml::find_or(general, "isPS4Pro", false); + isDevKit = toml::find_or(general, "isDevKit", false); playBGM = toml::find_or(general, "playBGM", false); isTrophyPopupDisabled = toml::find_or(general, "isTrophyPopupDisabled", false); trophyNotificationDuration = @@ -955,6 +961,7 @@ void save(const std::filesystem::path& path) { } data["General"]["isPS4Pro"] = isNeo; + data["General"]["isDevKit"] = isDevKit; data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled; data["General"]["trophyNotificationDuration"] = trophyNotificationDuration; data["General"]["playBGM"] = playBGM; @@ -1101,6 +1108,7 @@ void saveMainWindow(const std::filesystem::path& path) { void setDefaultValues() { isHDRAllowed = false; isNeo = false; + isDevKit = false; isFullscreen = false; isTrophyPopupDisabled = false; playBGM = false; diff --git a/src/common/config.h b/src/common/config.h index 4202da88a..1025e9956 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -28,6 +28,7 @@ void setLoadGameSizeEnabled(bool enable); bool getIsFullscreen(); std::string getFullscreenMode(); bool isNeoModeConsole(); +bool isDevKitConsole(); bool getPlayBGM(); int getBGMvolume(); bool getisTrophyPopupDisabled(); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 98d587e00..8b108a654 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -38,6 +38,16 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1 bool use_extended_mem2) { const bool is_neo = ::Libraries::Kernel::sceKernelIsNeoMode(); auto total_size = is_neo ? SCE_KERNEL_TOTAL_MEM_PRO : SCE_KERNEL_TOTAL_MEM; + if (Config::isDevKitConsole()) { + const auto old_size = total_size; + // Assuming 2gb is neo for now, will need to link it with sceKernelIsDevKit + total_size += is_neo ? 2_GB : 768_MB; + LOG_WARNING(Kernel_Vmm, + "Config::isDevKitConsole is enabled! Added additional {:s} of direct memory.", + is_neo ? "2 GB" : "768 MB"); + LOG_WARNING(Kernel_Vmm, "Old Direct Size: {:#x} -> New Direct Size: {:#x}", old_size, + total_size); + } if (!use_extended_mem1 && is_neo) { total_size -= 256_MB; } From 5caab76a4557b2721898e1257356ad3b3356dd2f Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Wed, 26 Mar 2025 22:03:50 +0100 Subject: [PATCH 072/194] Implement DmaDataSrc::MemoryUsingL2 and DmaDataDst::MemoryUsingL2 (#2680) * Implement DmaDataSrc::MemoryUsingL2 and DmaDataDst::MemoryUsingL2 * Add L2 handling to the other place it's used --- src/video_core/amdgpu/liverpool.cpp | 30 +++++++++++++++++++---------- src/video_core/amdgpu/pm4_cmds.h | 2 ++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index bfe99c754..967b952c6 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -602,20 +602,25 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spansrc_sel == DmaDataSrc::Data && dma_data->dst_sel == DmaDataDst::Gds) { rasterizer->InlineData(dma_data->dst_addr_lo, &dma_data->data, sizeof(u32), true); - } else if (dma_data->src_sel == DmaDataSrc::Memory && + } else if ((dma_data->src_sel == DmaDataSrc::Memory || + dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && dma_data->dst_sel == DmaDataDst::Gds) { rasterizer->InlineData(dma_data->dst_addr_lo, dma_data->SrcAddress(), dma_data->NumBytes(), true); } else if (dma_data->src_sel == DmaDataSrc::Data && - dma_data->dst_sel == DmaDataDst::Memory) { + (dma_data->dst_sel == DmaDataDst::Memory || + dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { rasterizer->InlineData(dma_data->DstAddress(), &dma_data->data, sizeof(u32), false); } else if (dma_data->src_sel == DmaDataSrc::Gds && - dma_data->dst_sel == DmaDataDst::Memory) { + (dma_data->dst_sel == DmaDataDst::Memory || + dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { // LOG_WARNING(Render_Vulkan, "GDS memory read"); - } else if (dma_data->src_sel == DmaDataSrc::Memory && - dma_data->dst_sel == DmaDataDst::Memory) { + } else if ((dma_data->src_sel == DmaDataSrc::Memory || + dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && + (dma_data->dst_sel == DmaDataDst::Memory || + dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { rasterizer->InlineData(dma_data->DstAddress(), dma_data->SrcAddress(), dma_data->NumBytes(), false); @@ -785,19 +790,24 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq } if (dma_data->src_sel == DmaDataSrc::Data && dma_data->dst_sel == DmaDataDst::Gds) { rasterizer->InlineData(dma_data->dst_addr_lo, &dma_data->data, sizeof(u32), true); - } else if (dma_data->src_sel == DmaDataSrc::Memory && + } else if ((dma_data->src_sel == DmaDataSrc::Memory || + dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && dma_data->dst_sel == DmaDataDst::Gds) { rasterizer->InlineData(dma_data->dst_addr_lo, dma_data->SrcAddress(), dma_data->NumBytes(), true); } else if (dma_data->src_sel == DmaDataSrc::Data && - dma_data->dst_sel == DmaDataDst::Memory) { + (dma_data->dst_sel == DmaDataDst::Memory || + dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { rasterizer->InlineData(dma_data->DstAddress(), &dma_data->data, sizeof(u32), false); } else if (dma_data->src_sel == DmaDataSrc::Gds && - dma_data->dst_sel == DmaDataDst::Memory) { + (dma_data->dst_sel == DmaDataDst::Memory || + dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { // LOG_WARNING(Render_Vulkan, "GDS memory read"); - } else if (dma_data->src_sel == DmaDataSrc::Memory && - dma_data->dst_sel == DmaDataDst::Memory) { + } else if ((dma_data->src_sel == DmaDataSrc::Memory || + dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && + (dma_data->dst_sel == DmaDataDst::Memory || + dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { rasterizer->InlineData(dma_data->DstAddress(), dma_data->SrcAddress(), dma_data->NumBytes(), false); diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index e92ba17fa..ae1d32e00 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -377,12 +377,14 @@ struct PM4CmdAcquireMem { enum class DmaDataDst : u32 { Memory = 0, Gds = 1, + MemoryUsingL2 = 3, }; enum class DmaDataSrc : u32 { Memory = 0, Gds = 1, Data = 2, + MemoryUsingL2 = 3, }; struct PM4DmaData { From ae2c9a745e43a1156405ba84ebf646882a744d71 Mon Sep 17 00:00:00 2001 From: Dmugetsu <168934208+diegolix29@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:50:52 -0600 Subject: [PATCH 073/194] Gui: Adding Pause button working, full screen button and labels to buttons on main window gui (#2634) * Adding names to gui buttoms and adjusting spacing. * moving refresh button to last slot. * Changing the implementation to tooltips for hover over them - qstring to detect background color. * Fixing some themes with inverted tooltip base * Suggestions / Fixes - Pause and FullScreen Buttons * Update REUSE.toml * cleaning up * Icons stuff * clang * Buttons toggle - Cleaning code - Fixing Icons * cleaning boolean * Toggle pause and play icons and label to "Resume" when paused. * Simplifying the toggles. * New icons and final Push to review * Reuse * Icon rename, adding f9 press for pause game when no gui is on without needed of debug menu * clang + reuse * clang dosent work on this part * again Clang * Last fix for review. Light theme white resume icon fix. * Proper fix for Resume icon * New Rebase * Fixed Orientation with docking issues and cleaning boxlayout code * Adding spacer to separate actions, sizeslider on top of search bar. And adding margins * Fixed Background not showing on OLED Theme * Fixing check marks * Adding all Daniel Suggestions and fixed F9 not working with debug menu open. * Clang * reverting all OLED theme changes * Final suggestions --- REUSE.toml | 4 +- src/common/config.cpp | 12 +++ src/common/config.h | 2 + src/core/devtools/layer.cpp | 43 +++++--- src/core/devtools/layer.h | 1 + src/images/controller_icon.png | Bin 9102 -> 4142 bytes src/images/fullscreen_icon.png | Bin 0 -> 2590 bytes src/images/pause_icon.png | Bin 965 -> 1972 bytes src/images/play_icon.png | Bin 1150 -> 2875 bytes src/images/refresh_icon.png | Bin 3381 -> 0 bytes src/images/refreshlist_icon.png | Bin 0 -> 3247 bytes src/images/restart_game_icon.png | Bin 0 -> 3935 bytes src/images/settings_icon.png | Bin 2219 -> 4543 bytes src/images/stop_icon.png | Bin 658 -> 1601 bytes src/qt_gui/main_window.cpp | 168 ++++++++++++++++++++++++++++-- src/qt_gui/main_window.h | 12 +++ src/qt_gui/main_window_themes.cpp | 45 ++++---- src/qt_gui/main_window_ui.h | 32 ++++-- src/sdl_window.cpp | 20 ++++ src/sdl_window.h | 2 + src/shadps4.qrc | 74 ++++++------- 21 files changed, 324 insertions(+), 91 deletions(-) create mode 100644 src/images/fullscreen_icon.png delete mode 100644 src/images/refresh_icon.png create mode 100644 src/images/refreshlist_icon.png create mode 100644 src/images/restart_game_icon.png diff --git a/REUSE.toml b/REUSE.toml index 793990bd8..ad2bc3678 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -49,8 +49,10 @@ path = [ "src/images/pause_icon.png", "src/images/play_icon.png", "src/images/ps4_controller.png", - "src/images/refresh_icon.png", + "src/images/restart_game_icon.png", + "src/images/refreshlist_icon.png", "src/images/settings_icon.png", + "src/images/fullscreen_icon.png", "src/images/stop_icon.png", "src/images/utils_icon.png", "src/images/shadPS4.icns", diff --git a/src/common/config.cpp b/src/common/config.cpp index 16d9e5724..09236f30c 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -107,6 +107,7 @@ static bool showBackgroundImage = true; static bool isFullscreen = false; static std::string fullscreenMode = "Windowed"; static bool isHDRAllowed = false; +static bool showLabelsUnderIcons = true; // Language u32 m_language = 1; // english @@ -176,6 +177,14 @@ bool getIsFullscreen() { return isFullscreen; } +bool getShowLabelsUnderIcons() { + return showLabelsUnderIcons; +} + +bool setShowLabelsUnderIcons() { + return false; +} + std::string getFullscreenMode() { return fullscreenMode; } @@ -427,6 +436,9 @@ void setVblankDiv(u32 value) { void setIsFullscreen(bool enable) { isFullscreen = enable; } +static void setShowLabelsUnderIcons(bool enable) { + showLabelsUnderIcons = enable; +} void setFullscreenMode(std::string mode) { fullscreenMode = mode; diff --git a/src/common/config.h b/src/common/config.h index 1025e9956..3a0bf252c 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -26,6 +26,8 @@ bool GetLoadGameSizeEnabled(); std::filesystem::path GetSaveDataPath(); void setLoadGameSizeEnabled(bool enable); bool getIsFullscreen(); +bool getShowLabelsUnderIcons(); +bool setShowLabelsUnderIcons(); std::string getFullscreenMode(); bool isNeoModeConsole(); bool isDevKitConsole(); diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 87fd9ffb3..94b39e801 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "SDL3/SDL_log.h" #include "layer.h" #include @@ -117,22 +118,6 @@ void L::DrawMenuBar() { EndMainMenuBar(); } - - if (IsKeyPressed(ImGuiKey_F9, false)) { - if (io.KeyCtrl && io.KeyAlt) { - if (!DebugState.ShouldPauseInSubmit()) { - DebugState.RequestFrameDump(dump_frame_count); - } - } - if (!io.KeyCtrl && !io.KeyAlt) { - if (isSystemPaused) { - DebugState.ResumeGuestThreads(); - } else { - DebugState.PauseGuestThreads(); - } - } - } - if (open_popup_options) { OpenPopup("GPU Tools Options"); just_opened_options = true; @@ -381,6 +366,32 @@ void L::Draw() { visibility_toggled = true; } + if (IsKeyPressed(ImGuiKey_F9, false)) { + if (io.KeyCtrl && io.KeyAlt) { + if (!DebugState.ShouldPauseInSubmit()) { + DebugState.RequestFrameDump(dump_frame_count); + } + } else { + if (DebugState.IsGuestThreadsPaused()) { + DebugState.ResumeGuestThreads(); + SDL_Log("Game resumed from Keyboard"); + show_pause_status = false; + } else { + DebugState.PauseGuestThreads(); + SDL_Log("Game paused from Keyboard"); + show_pause_status = true; + } + visibility_toggled = true; + } + } + + if (show_pause_status) { + ImVec2 pos = ImVec2(10, 10); + ImU32 color = IM_COL32(255, 255, 255, 255); + + ImGui::GetForegroundDrawList()->AddText(pos, color, "Game Paused Press F9 to Resume"); + } + if (show_simple_fps) { if (Begin("Video Info", nullptr, ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | diff --git a/src/core/devtools/layer.h b/src/core/devtools/layer.h index 5bb53fbdb..9e949c8e9 100644 --- a/src/core/devtools/layer.h +++ b/src/core/devtools/layer.h @@ -19,6 +19,7 @@ public: static void SetupSettings(); void Draw() override; + bool show_pause_status = false; }; } // namespace Core::Devtools diff --git a/src/images/controller_icon.png b/src/images/controller_icon.png index 40c92a89bc53a9447d223c3c45286afe3b3d1673..0d5556329e55f8b1854821536a58a1ecd4d4528b 100644 GIT binary patch literal 4142 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 UN}CF^{5$l5tgc(4fqtp~1!=YDDgXcg literal 9102 zcmcgy^;?wP(_aLZ?rupzxf`L@vy0}0RRA=3K*mf0H8d-q5v>~&krNl3Y-5ZH*IBk zK;0zm;WL3|D+iGS02-2T{+Xjc(^$^n4{iVee*b?LN*luA1OQ-WRsqSq^ENrj_qhx- zlp1lOcI5clEzhFBgU3YmH4R9n$Q6XCWGN}3Bs9Y%Fb}tRka3}wds`+ zm$kV}gQ4FOrjEp?dK`kLZrrXP^A3F`#i#C@7w&KJ_HU&2C2h|h{vJ2(2ZRvHV={*S z&u{wbv`Rz|tV%ftv#rjL4^bxI-G|NRJ9dF6Nq;`)0&|v?5xq)=y}8q-2JsQrSFjOUeK;jjC`)uah;YvT}|CbAxR9+ zDrlHr5WqNy&;q_3#Bv*V?Bf-F&~u~QgR^yLKd!i^bIg~Fj@Cgt>S8R`@E`$gNrz(- zMPfveOB)jEpZLv#&iJQq3Xcg)i;oEJI}w!x%8k8cwtyf@CW|}Vs*iH4^OXm)KfN4V z?-vpTf@c-3y{w|ME`evn`|&^D_A)O-98c-{Vr{;HOgZ6W{5eEAIpIrec?FJ_5*d`E zI2~eSGkLmT-b;)T1DK1tR?qeC#Am1PEQk7ECa5?}0TWsMx2#^GV4m|Un|T~+rwW9e z$Sc4;)KP5W;U|%MPxSLCIqtp z1Rot6?_2PZb^zjUFJ}l_Sw_)L9hX5RmpQJcDh9Fm+St~Yt9gXMV6QmBZ7wgqtwLU5 z{@>2zdy@cqGheV9d-wrXHUKAG(&+{c+=HQRo=gdVTbBc>|Fj}sWzW=qYyV)_{dRnW zamA04{+~5+<+Nwsr~S|oIp2(NNp;{Bujkba0o&oVw04BZqn)d!Qe?FxA^=!{URc!w z;1MEdkoroT&5PTah5|Rj=~kJY7m0LycShNJhf;-2#{74uOGg*x_cfHqQl^I09O#4< ztyx{NP0eYt=9rh=zEkXQO$<7i4&&YD!E&~s50lfF;9&YST5>80)TGgmF1y3XB>sT# z!v_f0*FGd3Y^E=XJfwvsgDB$tD{u72Y6pi8$J7doI((gWT zGy*)evf#yr#R4VBZ|qj)g0f9)2atAU6W3W41{7M@Sa9y8s`j_+Ae@TX9B&xwRqu%=p0MGJ@^4AY{ zo*5NS1b6riBJ~WhUpjvh@*&9OO!2$8=@s%>WKfl{jAhYJ{7(ck>PzkiG}&Jb<#)kBG6(#Ue#!=Cb4zgeE7-_z@iQ80PN zO>w`Vzuka5{W3V9_bl+zw(+WvV_Q^>mbE z8(tBg8}^uCIT(8G#d6qfVy(H`68#QJ*ZtfeP1m=&bF#A>qc|#h$(B+1hTgN>oQ3&M zLRSfZ$tPbDL4xjk&%+PMWhV~QtVj+4z)Y;rpSm+*kq{k%1`dI6#%^7s{e~)gfQR+j zmTUL=t|Hgd4KUR!;gzdW9(ymv$c^ZQ=pH$rHdjn*YwN1$1{Eghp{L57u+F`-_*VeZ z9QLdsgx-2{fp?f+*uZV#iJ3w>#N2X;S|ro-3;!XFkKZfv`;!gy$0S6=x^Vk>cgaAQ z(w$EC9rmIC%dA8%x01odo})7PMo|B*acZ^0G0M{o=s)?mWjO!sfXK5cbR9`H@Ix|< zK}vh(ekVtNADfW}VyFW6>Z*2n}op7wV$IR37K zR&$O3|Kr1k`VwBusyfrf$$bx_ok>ToO1#+vnA$bQ+RK*SPB~VcMDIx`uyy}&n~t7M z2g~x0cTSSfNO|@>ax`a^5H260%mqaA?w2MEg{y<0lnw7nTHvl)9?!T zEdCszSf2Xgh=-FHCQ(bqcVYxqEbWX4Ox|na8tmH+zr- zcKC5ki?_^0r}MO)^7&77NCT$nT8grF?r4EW@71j|gb!M1X%8)2!8kLeKxkaa{Bf+7 zpO(GIs8c{4RxXc=rq2Bk@dt)KOe8(t&P)lUCKrX-+UxD@cIR<@-ON|bJtWCYihZ6g zCc42;N^SJ$K`H3+2#`q%WWsDlA%yFDGaBKv>wE?l?sKpzB%_ZPe^~#rYcKlNd2I!< z-mZd>zM-8wS;5n1ws@N)@RR-7?iioFVJknk+G*8NFrWj)!P&7kj&Q?nLvV&t%sn8X z9~4j5%>1yTzV6eWvB?73JIn3bSF>04WzonWY$=-+MSY29&s(w~OgAU@CVB}|I)$Vi zi1)d+2oB&%e-{QSDwK)x4Zw;OIB`fsxe_r*Q}fR{J}f`j`%(OOckf=) zMbaKBOO4FLzE)@`xCRK7`icQzG&H;+NxM^n!nfK9(V7}B2q+G}I4wT)F7M`Nf=Y01 zl#EO6?}R$$Hl>9v&E%tRXjOa|YN`OU-&?WY(!|MAzknVkoF2`}8in2O^72$6PQ*EB z7pW;P((}L)!Ha=>IEbi=NcOSdxG>n7lRb{5^65Mg`h>r$r99#ZSK&$`7azAEu~F7Uq0&>Bp(nRq`%+?3aHWW?2I z#6;%CvmWHBb?=9^@YD%)^9DX9^|G?R5%^Gwb7v;Ze&9`bQ`W$KSyH|&Lj(8Jx-%p# z_?esY#jRdF@X?}@>~N(gl$ZZ?i_)Oz)c5DTA!OK~B`?C9i}bixE9FjhkRaD=C~=vi zjJ`FH=(|6a8QQ8Gdcc7kD!Q*dlq(-PX{queX|j6i#aFdfPFMbrO_eWi=K3hZ3(NV;~Y8W>Ud2aC`!J221vIKrkNjpi9M&l*fsJmLLDmsn}Y}KgQlY#8hO_ zPS>vy*AoCHW4!SP&7r!ynWdl(@nzk}GTF82!RG72?en?qf|d%_(Au3>&yY~f^g5Nx zU--wlD03Yb!3&yG*=p^=hCGTz4Liy8K%*GO6Lt$~dzZpqYc)?dyWG%}3BPC1mZ30{ zw6Ac$HL%oCF8`~8DT&&l8yu=c%sz zFbE6%584jNKF|?w?F6nEM^QJ*(cCi^NMrzI2v2LF6TWfJMp`6HC(1@U5#!6$4_jB< z4GRnLzdW~o>nnXPN{lTj&(k#>@uy@tgWwww)!Ffx{4NmP;hwy)vGJ6k&pz||=gNyv zP1-pLg8k$9(P@<7%{7K*8pFVWXLC$r<5ApErY@I6Wj*b7OIh+4k+a##54&B_x5rrt zOy>x}%UtLmd@nhCw<&%{YRc?^Lp|PgCzJMTMyRW-7)N|@4N`+l`Qqhz$l=p5JdBhA=g{Z=U;n_w$cm3-g# z_}^clMqlz1qZs2GI1H6)O7%-Qd&b_A=HKRh{mW>NyF1q`y=_1fl9gnzs-p{LRf6qD zL2mf9;0m;Vf^?S1Zq7}NrbHUvsyxy(dkA&-NBFs03y61*pn@tAzRQ;(z_9#dJ zqsq3%`XuNjlfz9WQ!a$O%OT~rd9zwki}T@G7V%dAeu?Kp*6ZA7PdnRc5DZIL6shQc z-x0k;)kFQe%#K&K^a{~2W}DR!t0PIM@KTK#*)vW0A%!#QUi;FbNq170jVZ@>Hkpb3 zCZO&xWlgjYBRjB5d^KP5;#jHE$%1ETpVw47amc9Kq#R8`T4(>%619{S)xLiI8o=OL zgY1zRLFQ&Wdq9gAibSo+gD+c zxM#|)im?XGb;R$M{h6aiNgnprU(XHtoUl5#SLW*( zr*+`Cnfp!qu`%UlfR1H;7x&v%gQ@^%#I|i0r-q_U71HhE$KihbI_C1Hc#dK}m#KT6P!rqbA992{g;xZHkxbwonMkC3nRj$GaCcSlPlbs^AGx~P@#A?spWed7?CIxnq ze>{!NUH2?>0cvb6z?tF}G72cQiXd#Lb6uy&YV1r&gu7pq$yvsKvQxRvlkAEX;Q~O& zp~tVr?Fl6b&a*9jcT$ysbM&uwJjKgi@W1F)X4dJHYnaf?R@$$z5cn^$qCxN6b+}2Q zm$5;xlbw4JA?!Cm7;@`ncD zHdAP{voA{QLb_P{Pl&J?l?Mp8udT|fzF^LQf zvYp+vsM2hD5>@8xbSl~>1WS_E=8LWd{4xKI(wK8c9`nQUQ;WQPD0k;5X4l3h6|~7m zMu(mrY4})Fd2M8*si6|4f;a5m8s!oDQ`SIw?D9valXb;WP(dXpcRNM(?XHfS%j;$0 zQCU@yzUI08&J0Y4FMWl}0+;YxJe%nZ^46kQSI${8zl2=@g(y1WU^U3PloY>W%Klad z$EI}Cb*Af`QB#%1)ry5QxWrDBYcd{B9jdd$E@ixa=2LuPad4&_xqri*U&yFE_0im# z9Zgc8f{{XvJ82BFEsKutGmfLL(!Gw$a)rlyE3T6*dd1)cgcN|&1>ePbinJ%}--(;F{6J8H=LoDH8xD_HV zqxtxh%SAzE@K>h4rt(D5SEGi>&b$)KqH>qFb|n6wE!jS*rg|9@lSqux`lzXsX_fVr73tzeYT5`aqbhAiJzXTA`=`A|0Sbdvz{;}!Suh#=??!KO( z!6heLiXF1lKm6@2K2|S;f(5qiBBGsy3h zwEXC%FxWIDj>{BvKieV2A~<9>(iE?}_1FyERsb0icBB0LPR_?i(p<*-Hi(4=0Pj06vP5U zH!7{Ix@nG>+p(`|EM;Y#H6nq3uA@aLwQz0lWb%44WdFevErK#5>kQ@;Ssw0U{WQ?c zx#5da&ERhaz9iBxRGJvQ?0P9!@(ieR`ZgJ~T)ujFM2#WaUEBhEc@_KI&`H8ZXqZY$ z3<)^qK5T27UihhBj?G5V7}F%*D~_b1Ua7jfTu_cRz^hbs!oMk zvI<79sATh%=zTy?)F0+-0JKXOQ$qSMLKLeIe+bGVYM!~tJ&>%;+okeJ_)2&jZBen~ z=Wtq$J(WJ3)$mcz5b6_;stAh{qb3O@&#YZXC5qB|0 ziQGBUrN%B*6c0kK3l%;xHlcMMq!kqQri*AqEIXxDh>J8{bg-Eyf?bS4h3Iqoxw7S? z53K0=Lh|BRn&9Bx|e@b$Z9X~P)CH!%`lr=*WU@}ye>4}H8i-1aI7L$ zPpZ>ec`CZ5VYN4}9)h6FMAvqz&dvBHfc$^wIr&lbku$%Nhr)#`$ks}}dl|j1OZZ`! z9Ql#h)Z#T+(BBmb(Wv~nz!DG#EMAY<5&3Zsx4CKk^}c^+K8hKj)e&E!C|XQco;${1 z+Ox`&VEt{GsBsUbwJAF=gZ46m&2#iZ0Oc`h>}}pG`VGC#17}16x)_q(93?D5HNJ9~ zi_HJCUVvrDcR}bzBIYlp8gK1Tz3ob3dzHD^mYO$`=q!DZTm=fHY`h3}^Za&VTxy@R z7;^IhbeHj``32{9%D z3N2ql2AC3(gi}2nnRo%Dq9j?3{MQ^6K_~kTYfY(s}lY!FlNUkgeg@lo`-fS ztoX%=8#ajNB9sOOs!=CRj4r5`Cz1Fxw_h1<&_(?$?sDF@pubNR+ss)W@8nlVSaB{a ztHm4)z%03uP*I@B*~%F$<(%=ZCd9a{pg3g#hP7YNP?b*<#~MaPS6YWwILJdN^1c~) zo|&}sZE|dRWwx-H4XVUc_gvi5u{{rhS1P|W3Nhgn7UYru5V&UJB4!R-=4`yj@CGyJ zMAogutSPhGsYCTbe8@eIwXWGIO$@;#HPrs#ptKFGC${0`QDt(B z8onTntF1xtpTh~_@qeHlFMfu3yru#G-;5Ys6}+Lb7Qr`XJ2uHvuT?niuP0@q0`vqX zF5`Dwz7P9A6Yg;;d=xWVl$+s*vYq8}Z?dY+Y6C3DS+jU=V#Km_Br#LfXEf*wEn}>T zN4rnkb0%3`A9S6J5C1lzKqx09JAE_J^?M}e)H;Wn`k{>%^*v=?zk2Pm zzH6hHWO>xE#M;;Bj+DAGJ@tcL2bI6x4sP?7yAINc9#U-Mg;J_GOs-*fSN{p4!Pmnq zqZ=;uGrx3i$Fj7*pfVWmb}KHnlKa-2_f(_JCc6q6HcuOLwtP;nm`}RgB+W^dhROTh zck}JNNkzdueWTBasP`?#4%d>h3Odf8Wd*kI0ob|`$YpddH71Q{n(b)l7$QWc9Zb?f z{wm=PsX4>(bUhIX{>X!DF*ck7>?H$2afRw zjQf=5#=4yS%lF-5Wt%KF(KGORE9Aht-8aY(@ULmAKI7%ZNSZl0nfV*uG7h%h@2Pzo z;)U)HVzY0-i=9GI)sd|dI$dgf&G#l8s2}L$!z8IA(jw=Znkop0chLc~Za$8kQj z#)eVCQ#;k+&h+A+MMa3$*2O0O2H@(`C;j$a-v(P89SXTf#{gNR9#`YCN})bO;Vk6T zOQA%{u}U*g2b_6;KUv|`KxX*V+4|c&BTf;mV`INMuvq!hiUy1ZW_7|V@jQXM!4Na@ zwQOmd>_yz;FJTg_ed3Rls&sALdS8}{D1#(ssTcB==6_yg|dP^H_T8#`VfSZjbORih@MYv`smH_sdhLbdk79$qP z{og-YY3$=EBeDbij!JJ6jt3;u!QUj|-EOYtoDQ0C=la3maOcUK7%Nvh3P!(_29Q8v zk#9N&)m|lHJ5%g@+Xma`LSm470dwnmAU)3QE{WQJ5i#1QFR82^sVX_2VHg`N4p^;* zXO*kc_YNjd*am>8)3-C9;;h~n_1Z8|(Or;!;;bPh?rOoOi)Hb4tWmcx{~qi0$p}}! zNKQ$^^i`7Sc6{5PZ6OM1G8?JlPUfF3zn{MO&(hL3VW>*nP5aTT_eLH_7i1DH=vmXB6%- z(cXQ40fprFwIt)Ld=PBrlP>JpTYmYm^;BjN6Dwe4uO5?g_<%d$P4Ysi4<5{Rf;Tef zERgHTTU?<|_!BB&?sG!nUBAYXIm$bzbWq!fE2yw>(yRR`|B z5{lYJV?1dHAzmVEX;P}{p&0`#bkLmVUl+3KM`;W_%|D7$fb0ofB$zv`FJr*~kNxvJ?#s`6ra9EF zHE3V}!wP;Ix-rF_Yl71&joIZ`Y00yNd#|A;&~RM+_ao{66o%`8)ZIs0V#}CCB^Lf{ z+NQBL`IKlaeDmQAWm-my#d_#wF-OfVtq-F{5FvuWh~pk_NP8 zppeE9;DqJ<4V*AXjAXz2M**+Lf|US8T9Dd(6|eiAyMGSB5zI@!+ahD1isPc;W|qy0 zsu`f^CdxGXE-rgK$*`p9jKU8?5usOaDX5IOdba8r;#aG7QV z%ScaRw z@HYr6j{vvNxq{V%2=_n6yWrYqmLGomhTL-X7;d`?1dmSks&Sv5t0Sy?pG6+14ccUM zz{I9VE6LCN3sW~Nem^@sk1#iPJ!1PQ9m&d;qA#+9+p_cHU0sE_B~{^&O{Z+0jUqR<&)fe>jFV^@U)eG#o*{anqD zf`R=*TyeYk2Zu{2iEQJX>LVJI4JP&Zqx7K%S-d$tlS!Wn|HQ52+J6DM(Q%S&iZe4e zC$B$Ra7}08iNwI^|C^EIC2cEIuM#zmc1u^l`|IEG#d~!W5mIh+v)rWv#(?6aKPB60 z2%76k?3a^;65mrX-bK@!sTCx6Ku;-{Z}zeyZ|$$z+3El5hlHi0`p%x1Te5G&1@F#Za;EFD9YuwpHVR( zg)-n$fQ0D6f~5Krh~~#NHC z42`;LpbLAwFl@4MsLXDL9_xi-NgBq%vM+4?`X6j`O>A@(hi{r{ZmDZFCu}iX4VcY43#UPoZSt=_tF?c<; zj+#hjnOSI^vJ)qq?EfP7M6eZ^bS~`gs3ncC1-{Sf!bpnk$Da}zI~>YUkq+S|pz2G- zDa=_tR6`9J?nB)Rqtcg1pi`}bT2Ki{hXglxQFRhF#xQ>4;|CAWyks1F1|j}!${QohPhMiEHM+SlG+5=Vy@6q!a8i0zT2B=QnEcE{X DoToWu diff --git a/src/images/fullscreen_icon.png b/src/images/fullscreen_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..719ffe4a19b842f711a4cd6be61fcbfb668b6ef1 GIT binary patch literal 2590 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 literal 0 HcmV?d00001 diff --git a/src/images/pause_icon.png b/src/images/pause_icon.png index 5375689b7880bb46d843eca0f8494e66bee69f19..86bbc6acbd15eecfa96376cfe90511a7be46387d 100644 GIT binary patch literal 1972 zcmdT_i!arfy{iVAzJ9Gbo`tMNHA#@h>IfA7M$xv8To!iOS+ z!uBEnXs6+WaHM18#mo5Cow10st?dRo|D_QOsqwN!1e@q@-_j~Y7?#6qD};vME15Pv z(U|W@K^UBosebu>(?`bl49=V?w=HGt;NW_cMJ+i6_crQ@-wqeGY$TqDRFw)lCtBX~ zR-ZQ$3+PYG-}J4~cs!d7sm0td?I($U?R3sshp$b6G-u9=L8@n!P(%_Efm0xNq)8;9 z_0IAofiHTs*LrT1*0mYCWnVZ`yvE;**s2@xP!YWk?bvKgN$F=d;Injx$1aq9I4gy6}J(8 z`xSOLdBty+xCTAIoUyF!u!C>d9+=0XtmEBHmFI_5va!dr5~Ou&QD2$PHv`eNZ-Y~p zpN+C9f?{8XLsHZBT~8;tC?AD1r2R^D?pK5*=7aeIIO>5Q7sbptUeEq1)1hT7& z=Vs?@I3x*M(H%G^piVk5>zdC|P&cw)C4EsE;hq;k+1?pcTcp(MKJvibGsk#UOi4-K z<5}xToD55I8~B|(bm{%^S8!R|@%G9Ho#*cHCSP)cUnTK8UJ$aHkOz|`>}6d|re9jK zMM;A2deITR(nQqlwk9vWK8pZVq+hk_W$q7fsgpfFy_C^7vLGT?wQrv3EKgWB}q zfZe=4&c!kRNcK7U>s#y}VkWuEW<_-h!n)rTamnb7&fiPE_8Qew&AfBatE@0%^-l#x z$)mZ)MYJbzKF!p7;f#zO+C`?YSiiwI0=K7O27$sz}M(aUznWS^rI^g z_$#AppVG~K)b8S3u~YeH^giqUpj<>3 z@$5z$_g&Pal_hb%gx)0zYj}B8#V*#ISWIN`u0X|af*hF-1^vOkz<)vOBa7kLV`-Jf zIYBrpeCZ0u^A}AnJsbB%F$Vi;n;H)FJF`0SPUe&J`V_vJnN#7N3mzG>Fx4&w%N2_u z3t1Z!hnG&X(_-8tdIPFh_oppJMZ`)yl=h5kC@36hV1_DuB4cMs^0gSyz2;jiiY2W?XKr*L~&n`rOg yq4X_mcVRF$pQf;`3y&P1L&}yT-L$My4=tU-d1msLTBv`w>9f#KDkjb=i$8av~#YX{;OV|SJ{5L_TAEltb!0QbM7;y zxd$W{u}{pdTEZO}P* z=XB%hfAdnG|Kpj%CUzky_kag0|CjGy1vAzczRNw3!CSna!R(UK>%aW#0xy4Eyi7cG zy8Z5TJMDkOUs>|(>X&mFe;&`>wCK>zaK`yUXKd&EDt_wH)SmU@by{ojeJ_jehC)@r z1}9hyICxAdZ5fVnpPS7zEzF?!o9n~ygSE&0>YP1iYjIY*DO_dK)`Skl`K=M%jn%Br zZkAFq2JMXPm#^KX2PV@1u2pis@n4=i3V-r0m>{- zIU2ml$!l8Uq|^sf)>L?fd7Lx~*u*nulBoYlF3m_bpiJW z`Re&gZ|-bg7FqdaQcIsWPte!W#fNrQ3!kf;YA{&@cD64BQY zxBmzo-6i+Rdc)?nowu{&bS$6Qzlcs^{;;L3y^22vnA~Llv4?-Xqg=?&+KN6{`R67{1=U l`|swmAL-)I{E#;Lew559d%-E~fxvvp;OXk;vd$@?2>?c3y|VxS diff --git a/src/images/play_icon.png b/src/images/play_icon.png index 2815be39d8594b213fad22e68c7e8ac16872d639..d50d404b79bc4b5f5e7122e2f30cb5663ad3c4ad 100644 GIT binary patch delta 2848 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 LNkvXXu0mjf_p(8M diff --git a/src/images/refreshlist_icon.png b/src/images/refreshlist_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7de6685b27e195b1517d5058f11e48b079f4ee08 GIT binary patch literal 3247 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 literal 0 HcmV?d00001 diff --git a/src/images/restart_game_icon.png b/src/images/restart_game_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1e549e10192135af9d1ba30522c3a405521a2259 GIT binary patch literal 3935 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 literal 0 HcmV?d00001 diff --git a/src/images/settings_icon.png b/src/images/settings_icon.png index c88cd7a6f16cbc54bbb44730d3dac206f3affe2b..81127bfa3e7856ac86bb645a79a905c4324d42b3 100644 GIT binary patch literal 4543 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 r0w|%4^SL>bXBXW&WwtaSTv9Aq5DDXYMx@8o?^} delta 2190 zcmV;92yyqnBdZaRB#}WBe+XSkL_t(|ob8=iY*s}S$N&8*%3j290Dc3|W|=7O4ghZe7#K#!ju8O z9tID6bEfxd6Zb zq?{Q5&gL}uJa0#DG0cBGfCfe9H3R5pArqC^B5a-e8PH;3xIb3Ob0NTU;SJ>j>Fk+olx`@8bA&P9ZeTc^A_|7fJ^+pfATu91i;;VQcnunf7af^&Jo|TEZG4#!m>W2IR8AQ z>4R|zwgaCFf_#d?VY|=|JChPZvN4<~FvHJ40DlYpL2tZwz7hm@9)-sXLVtYWyYjn) z%8vr*jAZBmU@F^9-s7`S75_|jjwuNq(mD(JH%b$HR=!%O_(`7?11JVClh>$AhC8EL z0Of-If6Nuq0#CLbFLV{=OWJ#P@_Yb2Y`8^L**-|E?~6{tne3D!O@pzWo6>7OD#U+| z3Y%jLMEkUf4~9N?U8vWK{eFj{GSZ?B_E6toR*kngP6t^#?V2HiM%l*ntmTZ*NK)Dj zbvzi29tI ziykiLEH-TDGTsr{vRPApGwX`RD0WX7w%}C?rrT^H{gf?Naa&FLd|$muI3r6~&sZ?JmnHrbJ^CKWOpr zf4pMQ{wD6LMCikZE=eFdybX&`=1k3-5of+H(Yz^9)cqq24yLt^4QocwYxo?13xZ%Z z4m=E-BTiE)ZWX3m>OfPZ2k`>;Ns=an|8cLd%bczDg`x*iSBEtaMn1@ zeUS?02u}^|&U8!_@{e&S=QyFB`(v&he~%+&s18PSgVxsDy~(j+FeCVnks4jcE=69l zHl~gjg;TMP6$x71b}8ywZje{CyQBvyb*Nl|$iG*|21VYr9^_Q5BXq9NabA)4AzVfS zLJrBc15%lww@jP?p|+=vFlD8TeTt`t)b=_m6nT%QY*F8-$V(fVRPjVi70MJLfB#sA za*mI=?S0wtWg;(37HlOgh6ls$f@kAehqA60PT1WH?H;Ow;Ti6Wyy04PQY#Fq=?yH2 zQG)6uikzRMQFUUXT%;%d<_Kk$3uV0dDrtw2 zo0h~h;chZ$WF;#KRVLi=ynyQ-9`bgyrI@|u^Jc;|adL36_3O!2Jd=i=VBJ*K$$_S| z-wpHDJGu~*Hzx<38xU;?T`@@GFB>;CIKDNT7dN1~gdODu8O;ASsov8Sf9Av;6r(*%}<{ zL)B=Cg5Z-3dxz8-ULd`|C-4~wTt`@#kLw7!>ue1PUfAi*%D@V?^ljj@oVB+18S187 z&_&~iOj~fTd?{3JqQbt?e^?~+2WbfMdL_CLFMXaC`h%_jCc+*;h;S_$#@;s0n4nou zJ6hl=P}sA)&^F|#HBPa$aTzP)d@sPcP+gO_?tM8|_F7oR8kV_V#DzTZzyZ)G*K$K^ zjOMe%#Ryx}@&x2BkFb?)A6nx!O=u0e9Gf`T8U&x&YuMn9(w3Mue-ITVF$4YUg3mPO zEBFl9AF0h_I$#-RQAPg<0cUKJO<+x@m|R?mllHNeATHfF>YGnleK_`69nyR^^aGE< znO#0ONgYOw_T#3PEf4c|qRM!OGoROsW+6{2$KA4JtU(aWEgX$v63g~>#2}ahF1IkT zyLo<9ZEHtrI)D~pe_LuihlzE0Wx{J9<#Vi%9FfvM%12`cQvN5+ft0h{0AL=ZRH{0STY0$P*Qc0il??Q8}O}s}M~E z*343LB7MFnD%nV*?bD+kt3LiUK(dU>D80m@ya|b3SmN zI<4&GOmF_00R=98m7Nn-a#+C4{PQyY+XDVy$`c3#0)apv5C{YU0d&m2p@aE8r}ynE Q00000NkvXX1g=70f&tk8-T(jq diff --git a/src/images/stop_icon.png b/src/images/stop_icon.png index 74c615f65a54bd8380a3e67f689d86038019edc6..55b6b01c7c4c20ce586a0145ccbbfe7db38cfc43 100644 GIT binary patch literal 1601 zcmeAS@N?(olHy`uVBq!ia0vp^DImQL70(Y)*K0-phSslL`iUdT1k0gQ7S_~VrE{6o}X)of~lUN zo@wfhX`(y)37&w3&Rt70XRt82O%L|C5p=^+AG#Ht|;!HrcAtMum0FaIX z;>>myuy_`b4FaB$j0`WB5j2{m3_z&~>vp@3Xlx~Oh9v) zz$$|*Er2YjE<*zYknFd0hqp*vSLz3b#0O6o$B>G+w{yK$gr-Wgm4|PheXBJNS zg{G8*|NsAAllt+s$544k!o%~PJALkHT|a61 zNyzf;`(OJD{QfDQKQXz;@1Lf6(j+CJLt&N6_xRpZSU*AgiR+~=RsT=!f3p75mN`@B zpEx{eeg9Hlh47PSi@NWqy`RLAc+A=0_|w~;oPWMNbbVRc(i)F_`jL~$s_suVo;1tv z=+RHCKc}9lZI~OA^>fdXZINEp_L_FO@)ONJvToK{GtK;T_ESSoVFR&qJlj_4r|PBd zE_tqc`})+PhC2)E`fCF1CcHc5oc(?KljdEweudS4_g1}|A#D*{c6#y4yvB!2pH9n7 z)?d2YXiI_p$@eP1AD#acfAgt>h4{K@IiA-~hE8&C3bfJuesY(poZf@7pF8&G-+lV< z@ck+Jr}}Pblq*a^)#z1|HQ95I}0BA-%zNO4QeuO z&VAKgl77o!x!2hhv7A|cZ-vh~Tsw98_w-1M1D3{7U(cyIem|)_`TgZsxj5&Klguvi zw6ssXlx*wWm)FI7$A`^yaoJMy{a60X`u8+JK_%2`MP}yoFF|V_2Uus#Xr6VQzvHY! zMQ>16tYyuM4QEP1`q?a3RQ}~!E$Q)+U2E6BIU7ZeU0t`X)>uH8fnA7asG*)Tg|+|L z>x~W9s{5NTOPW7Ev^ulu*x9s|>f)x|e`f2Pot|WNf05zBWu{lwnM{~5>-y#HS&e@b z{=bOVYucE{qp9a-Y33Rs3zVERb=LnI`(=2ZO!>37e&*AvlPunCYO~4$R_jg*T%&qp z=DxS_d~8PQ{@?XC8{D2+xadrk>DwKyLB?6(O|yc3yBG^hJNJB(NpG*??lWbz5k?0u zX)op3dQ(NRd7tyNHWA~hsk5#te{EyGWBm4qn`M^h9ev4`6B}H%Svt9%6R(R8Uz_D1 z@%_~DCwi7${ZFEc%$vlIy9a8|m_x5Hj%{^aAA?sB}CnXb}Y`cq-|pL5>l z#Fnk*J8;jO=XATlkF;s`vofdkuYdabljzPDhRWg`1#$%s3Qky0dD{H>Tjts>q)s;Pb7n*6h+re$$!H6bLNxZl}X=?J*+FvB`m+GA3yobV*S1I zynHT+UObdLwfw|w)ybbM(>-K3yX!e)TdY1Eo7N{>DSJrRg1=VYyKKg;t+GeAzDXp00i_>zopr E0K^za>cWLdxmJqeaYUez~9^NjLra`DY8Sp;&0&QkN;qmtC9m^~iQ58_A_Neb2c6`26F&neh9wX8V@~tzW;(G}W2c zV$IJF8*WT}W%zvk+R3x{_4QmgG&`1`Hd{(ZYX zo~-abb2xIc_?6j@PP7%qypfAClUr`HE-8-JdPYH@YJY9&fr;+FOpnE$JACjEW5gWi zrSB!eE%{l08wvAA&-oIU`?=!$*IBc+T$q1+es!qgW6giFvT7LrzZTvs^#5YJ`tvQR mi{; #include #include @@ -132,23 +134,160 @@ void MainWindow::CreateActions() { m_theme_act_group->addAction(ui->setThemeOled); } +void MainWindow::PauseGame() { + SDL_Event event; + SDL_memset(&event, 0, sizeof(event)); + event.type = SDL_EVENT_TOGGLE_PAUSE; + is_paused = !is_paused; + UpdateToolbarButtons(); + SDL_PushEvent(&event); +} + +void MainWindow::toggleLabelsUnderIcons() { + bool showLabels = ui->toggleLabelsAct->isChecked(); + Config::setShowLabelsUnderIcons(); + UpdateToolbarLabels(); + if (isGameRunning) { + UpdateToolbarButtons(); + } +} + +void MainWindow::toggleFullscreen() { + SDL_Event event; + SDL_memset(&event, 0, sizeof(event)); + event.type = SDL_EVENT_TOGGLE_FULLSCREEN; + SDL_PushEvent(&event); +} + +QWidget* MainWindow::createButtonWithLabel(QPushButton* button, const QString& labelText, + bool showLabel) { + QWidget* container = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(container); + layout->setAlignment(Qt::AlignCenter | Qt::AlignBottom); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(button); + + QLabel* label = nullptr; + if (showLabel && ui->toggleLabelsAct->isChecked()) { + label = new QLabel(labelText, this); + label->setAlignment(Qt::AlignCenter | Qt::AlignBottom); + layout->addWidget(label); + button->setToolTip(""); + } else { + button->setToolTip(labelText); + } + + container->setLayout(layout); + container->setProperty("buttonLabel", QVariant::fromValue(label)); + return container; +} + +QWidget* createSpacer(QWidget* parent) { + QWidget* spacer = new QWidget(parent); + spacer->setFixedWidth(15); + spacer->setFixedHeight(15); + return spacer; +} + void MainWindow::AddUiWidgets() { // add toolbar widgets QApplication::setStyle("Fusion"); - ui->toolBar->setObjectName("mw_toolbar"); - ui->toolBar->addWidget(ui->playButton); - ui->toolBar->addWidget(ui->pauseButton); - ui->toolBar->addWidget(ui->stopButton); - ui->toolBar->addWidget(ui->refreshButton); - ui->toolBar->addWidget(ui->settingsButton); - ui->toolBar->addWidget(ui->controllerButton); - ui->toolBar->addWidget(ui->keyboardButton); + + bool showLabels = ui->toggleLabelsAct->isChecked(); + ui->toolBar->clear(); + + ui->toolBar->addWidget(createSpacer(this)); + ui->toolBar->addWidget(createButtonWithLabel(ui->playButton, tr("Play"), showLabels)); + ui->toolBar->addWidget(createButtonWithLabel(ui->pauseButton, tr("Pause"), showLabels)); + ui->toolBar->addWidget(createButtonWithLabel(ui->stopButton, tr("Stop"), showLabels)); + ui->toolBar->addWidget(createButtonWithLabel(ui->restartButton, tr("Restart"), showLabels)); + ui->toolBar->addWidget(createSpacer(this)); + ui->toolBar->addWidget(createButtonWithLabel(ui->settingsButton, tr("Settings"), showLabels)); + ui->toolBar->addWidget( + createButtonWithLabel(ui->fullscreenButton, tr("Full Screen"), showLabels)); + ui->toolBar->addWidget(createSpacer(this)); + ui->toolBar->addWidget( + createButtonWithLabel(ui->controllerButton, tr("Controllers"), showLabels)); + ui->toolBar->addWidget(createButtonWithLabel(ui->keyboardButton, tr("Keyboard"), showLabels)); + ui->toolBar->addWidget(createSpacer(this)); QFrame* line = new QFrame(this); - line->setFrameShape(QFrame::StyledPanel); + line->setFrameShape(QFrame::VLine); line->setFrameShadow(QFrame::Sunken); + line->setMinimumWidth(2); ui->toolBar->addWidget(line); - ui->toolBar->addWidget(ui->sizeSliderContainer); - ui->toolBar->addWidget(ui->mw_searchbar); + ui->toolBar->addWidget(createSpacer(this)); + if (showLabels) { + QLabel* pauseButtonLabel = ui->pauseButton->parentWidget()->findChild(); + if (pauseButtonLabel) { + pauseButtonLabel->setVisible(false); + } + } + ui->toolBar->addWidget( + createButtonWithLabel(ui->refreshButton, tr("Refresh List"), showLabels)); + ui->toolBar->addWidget(createSpacer(this)); + + QBoxLayout* toolbarLayout = new QBoxLayout(QBoxLayout::TopToBottom); + toolbarLayout->setSpacing(2); + toolbarLayout->setContentsMargins(2, 2, 2, 2); + ui->sizeSliderContainer->setFixedWidth(150); + + QWidget* searchSliderContainer = new QWidget(this); + QBoxLayout* searchSliderLayout = new QBoxLayout(QBoxLayout::TopToBottom); + searchSliderLayout->setContentsMargins(0, 0, 6, 6); + searchSliderLayout->setSpacing(2); + ui->mw_searchbar->setFixedWidth(150); + + searchSliderLayout->addWidget(ui->sizeSliderContainer); + searchSliderLayout->addWidget(ui->mw_searchbar); + + searchSliderContainer->setLayout(searchSliderLayout); + + ui->toolBar->addWidget(searchSliderContainer); + + if (!showLabels) { + toolbarLayout->addWidget(searchSliderContainer); + } + + ui->playButton->setVisible(true); + ui->pauseButton->setVisible(false); +} + +void MainWindow::UpdateToolbarButtons() { + // add toolbar widgets when game is running + bool showLabels = ui->toggleLabelsAct->isChecked(); + + ui->playButton->setVisible(false); + ui->pauseButton->setVisible(true); + + if (showLabels) { + QLabel* playButtonLabel = ui->playButton->parentWidget()->findChild(); + if (playButtonLabel) + playButtonLabel->setVisible(false); + } + + if (is_paused) { + ui->pauseButton->setIcon(ui->playButton->icon()); + ui->pauseButton->setToolTip(tr("Resume")); + } else { + if (isIconBlack) { + ui->pauseButton->setIcon(QIcon(":images/pause_icon.png")); + } else { + ui->pauseButton->setIcon(RecolorIcon(QIcon(":images/pause_icon.png"), isWhite)); + } + ui->pauseButton->setToolTip(tr("Pause")); + } + + if (showLabels) { + QLabel* pauseButtonLabel = ui->pauseButton->parentWidget()->findChild(); + if (pauseButtonLabel) { + pauseButtonLabel->setText(is_paused ? tr("Resume") : tr("Pause")); + pauseButtonLabel->setVisible(true); + } + } +} + +void MainWindow::UpdateToolbarLabels() { + AddUiWidgets(); } void MainWindow::CreateDockWindows() { @@ -253,6 +392,8 @@ void MainWindow::CreateConnects() { connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::RefreshGameTable); connect(ui->showGameListAct, &QAction::triggered, this, &MainWindow::ShowGameList); connect(this, &MainWindow::ExtractionFinished, this, &MainWindow::RefreshGameTable); + connect(ui->toggleLabelsAct, &QAction::toggled, this, &MainWindow::toggleLabelsUnderIcons); + connect(ui->fullscreenButton, &QPushButton::clicked, this, &MainWindow::toggleFullscreen); connect(ui->sizeSlider, &QSlider::valueChanged, this, [this](int value) { if (isTableList) { @@ -276,6 +417,7 @@ void MainWindow::CreateConnects() { }); connect(ui->playButton, &QPushButton::clicked, this, &MainWindow::StartGame); + connect(ui->pauseButton, &QPushButton::clicked, this, &MainWindow::PauseGame); connect(m_game_grid_frame.get(), &QTableWidget::cellDoubleClicked, this, &MainWindow::StartGame); connect(m_game_list_frame.get(), &QTableWidget::cellDoubleClicked, this, @@ -743,6 +885,8 @@ void MainWindow::StartGame() { return; } StartEmulator(path); + + UpdateToolbarButtons(); } } @@ -1217,7 +1361,9 @@ void MainWindow::SetUiIcons(bool isWhite) { ui->pauseButton->setIcon(RecolorIcon(ui->pauseButton->icon(), isWhite)); ui->stopButton->setIcon(RecolorIcon(ui->stopButton->icon(), isWhite)); ui->refreshButton->setIcon(RecolorIcon(ui->refreshButton->icon(), isWhite)); + ui->restartButton->setIcon(RecolorIcon(ui->restartButton->icon(), isWhite)); ui->settingsButton->setIcon(RecolorIcon(ui->settingsButton->icon(), isWhite)); + ui->fullscreenButton->setIcon(RecolorIcon(ui->fullscreenButton->icon(), isWhite)); ui->controllerButton->setIcon(RecolorIcon(ui->controllerButton->icon(), isWhite)); ui->keyboardButton->setIcon(RecolorIcon(ui->keyboardButton->icon(), isWhite)); ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite)); diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 5ac56e44c..bcd5e53ba 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "background_music_player.h" @@ -38,6 +39,8 @@ public: void InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg); void InstallDirectory(); void StartGame(); + void PauseGame(); + bool showLabels; private Q_SLOTS: void ConfigureGuiFromSettings(); @@ -47,15 +50,21 @@ private Q_SLOTS: void RefreshGameTable(); void HandleResize(QResizeEvent* event); void OnLanguageChanged(const std::string& locale); + void toggleLabelsUnderIcons(); private: Ui_MainWindow* ui; void AddUiWidgets(); + void UpdateToolbarLabels(); + void UpdateToolbarButtons(); + QWidget* createButtonWithLabel(QPushButton* button, const QString& labelText, bool showLabel); void CreateActions(); + void toggleFullscreen(); void CreateRecentGameActions(); void CreateDockWindows(); void GetPhysicalDevices(); void LoadGameLists(); + #ifdef ENABLE_UPDATER void CheckUpdateMain(bool checkSave); #endif @@ -73,6 +82,9 @@ private: bool isIconBlack = false; bool isTableList = true; bool isGameRunning = false; + bool isWhite = false; + bool is_paused = false; + QActionGroup* m_icon_size_act_group = nullptr; QActionGroup* m_list_mode_act_group = nullptr; QActionGroup* m_theme_act_group = nullptr; diff --git a/src/qt_gui/main_window_themes.cpp b/src/qt_gui/main_window_themes.cpp index c5574fca9..624673cba 100644 --- a/src/qt_gui/main_window_themes.cpp +++ b/src/qt_gui/main_window_themes.cpp @@ -19,7 +19,7 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { themePalette.setColor(QPalette::WindowText, Qt::white); themePalette.setColor(QPalette::Base, QColor(20, 20, 20)); themePalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); - themePalette.setColor(QPalette::ToolTipBase, Qt::white); + themePalette.setColor(QPalette::ToolTipBase, QColor(20, 20, 20)); themePalette.setColor(QPalette::ToolTipText, Qt::white); themePalette.setColor(QPalette::Text, Qt::white); themePalette.setColor(QPalette::Button, QColor(53, 53, 53)); @@ -37,18 +37,18 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { "border-radius: 4px; padding: 5px; }" "QLineEdit:focus {" "border: 1px solid #2A82DA; }"); - themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray - themePalette.setColor(QPalette::WindowText, Qt::black); // Black - themePalette.setColor(QPalette::Base, QColor(230, 230, 230, 80)); // Grayish - themePalette.setColor(QPalette::ToolTipBase, Qt::black); // Black - themePalette.setColor(QPalette::ToolTipText, Qt::black); // Black - themePalette.setColor(QPalette::Text, Qt::black); // Black - themePalette.setColor(QPalette::Button, QColor(240, 240, 240)); // Light gray - themePalette.setColor(QPalette::ButtonText, Qt::black); // Black - themePalette.setColor(QPalette::BrightText, Qt::red); // Red - themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Blue - themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Blue - themePalette.setColor(QPalette::HighlightedText, Qt::white); // White + themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray + themePalette.setColor(QPalette::WindowText, Qt::black); // Black + themePalette.setColor(QPalette::Base, QColor(230, 230, 230, 80)); // Grayish + themePalette.setColor(QPalette::ToolTipBase, QColor(230, 230, 230, 80)); // Grayish + themePalette.setColor(QPalette::ToolTipText, Qt::black); // Black + themePalette.setColor(QPalette::Text, Qt::black); // Black + themePalette.setColor(QPalette::Button, QColor(240, 240, 240)); // Light gray + themePalette.setColor(QPalette::ButtonText, Qt::black); // Black + themePalette.setColor(QPalette::BrightText, Qt::red); // Red + themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Blue + themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Blue + themePalette.setColor(QPalette::HighlightedText, Qt::white); // White qApp->setPalette(themePalette); break; case Theme::Green: @@ -62,8 +62,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { themePalette.setColor(QPalette::WindowText, Qt::white); // White text themePalette.setColor(QPalette::Base, QColor(25, 40, 25)); // Darker green base themePalette.setColor(QPalette::AlternateBase, - QColor(53, 69, 53)); // Dark green alternate base - themePalette.setColor(QPalette::ToolTipBase, Qt::white); // White tooltip background + QColor(53, 69, 53)); // Dark green alternate base + themePalette.setColor(QPalette::ToolTipBase, + QColor(25, 40, 25)); // White tooltip background themePalette.setColor(QPalette::ToolTipText, Qt::white); // White tooltip text themePalette.setColor(QPalette::Text, Qt::white); // White text themePalette.setColor(QPalette::Button, QColor(53, 69, 53)); // Dark green button @@ -85,8 +86,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { themePalette.setColor(QPalette::WindowText, Qt::white); // White text themePalette.setColor(QPalette::Base, QColor(20, 40, 60)); // Darker blue base themePalette.setColor(QPalette::AlternateBase, - QColor(40, 60, 90)); // Dark blue alternate base - themePalette.setColor(QPalette::ToolTipBase, Qt::white); // White tooltip background + QColor(40, 60, 90)); // Dark blue alternate base + themePalette.setColor(QPalette::ToolTipBase, + QColor(20, 40, 60)); // White tooltip background themePalette.setColor(QPalette::ToolTipText, Qt::white); // White tooltip text themePalette.setColor(QPalette::Text, Qt::white); // White text themePalette.setColor(QPalette::Button, QColor(40, 60, 90)); // Dark blue button @@ -109,8 +111,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { themePalette.setColor(QPalette::WindowText, Qt::white); // White text themePalette.setColor(QPalette::Base, QColor(80, 30, 90)); // Darker violet base themePalette.setColor(QPalette::AlternateBase, - QColor(100, 50, 120)); // Violet alternate base - themePalette.setColor(QPalette::ToolTipBase, Qt::white); // White tooltip background + QColor(100, 50, 120)); // Violet alternate base + themePalette.setColor(QPalette::ToolTipBase, + QColor(80, 30, 90)); // White tooltip background themePalette.setColor(QPalette::ToolTipText, Qt::white); // White tooltip text themePalette.setColor(QPalette::Text, Qt::white); // White text themePalette.setColor(QPalette::Button, QColor(100, 50, 120)); // Violet button @@ -133,7 +136,7 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { themePalette.setColor(QPalette::WindowText, QColor(249, 245, 215)); themePalette.setColor(QPalette::Base, QColor(29, 32, 33)); themePalette.setColor(QPalette::AlternateBase, QColor(50, 48, 47)); - themePalette.setColor(QPalette::ToolTipBase, QColor(249, 245, 215)); + themePalette.setColor(QPalette::ToolTipBase, QColor(29, 32, 33)); themePalette.setColor(QPalette::ToolTipText, QColor(249, 245, 215)); themePalette.setColor(QPalette::Text, QColor(249, 245, 215)); themePalette.setColor(QPalette::Button, QColor(40, 40, 40)); @@ -155,7 +158,7 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { themePalette.setColor(QPalette::WindowText, QColor(192, 202, 245)); themePalette.setColor(QPalette::Base, QColor(25, 28, 39)); themePalette.setColor(QPalette::AlternateBase, QColor(36, 40, 59)); - themePalette.setColor(QPalette::ToolTipBase, QColor(192, 202, 245)); + themePalette.setColor(QPalette::ToolTipBase, QColor(25, 28, 39)); themePalette.setColor(QPalette::ToolTipText, QColor(192, 202, 245)); themePalette.setColor(QPalette::Text, QColor(192, 202, 245)); themePalette.setColor(QPalette::Button, QColor(30, 30, 41)); diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 246c2afd6..c4f47b636 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -20,6 +20,7 @@ public: QAction* setIconSizeSmallAct; QAction* setIconSizeMediumAct; QAction* setIconSizeLargeAct; + QAction* toggleLabelsAct; QAction* setlistModeListAct; QAction* setlistModeGridAct; QAction* setlistElfAct; @@ -50,6 +51,8 @@ public: QPushButton* settingsButton; QPushButton* controllerButton; QPushButton* keyboardButton; + QPushButton* fullscreenButton; + QPushButton* restartButton; QWidget* sizeSliderContainer; QHBoxLayout* sizeSliderContainer_layout; @@ -104,7 +107,15 @@ public: showGameListAct->setCheckable(true); refreshGameListAct = new QAction(MainWindow); refreshGameListAct->setObjectName("refreshGameListAct"); - refreshGameListAct->setIcon(QIcon(":images/refresh_icon.png")); + refreshGameListAct->setIcon(QIcon(":images/refreshlist_icon.png")); + + toggleLabelsAct = new QAction(MainWindow); + toggleLabelsAct->setObjectName("toggleLabelsAct"); + toggleLabelsAct->setText( + QCoreApplication::translate("MainWindow", "Show Labels Under Icons")); + toggleLabelsAct->setCheckable(true); + toggleLabelsAct->setChecked(Config::getShowLabelsUnderIcons()); + setIconSizeTinyAct = new QAction(MainWindow); setIconSizeTinyAct->setObjectName("setIconSizeTinyAct"); setIconSizeTinyAct->setCheckable(true); @@ -210,20 +221,28 @@ public: stopButton->setIconSize(QSize(40, 40)); refreshButton = new QPushButton(centralWidget); refreshButton->setFlat(true); - refreshButton->setIcon(QIcon(":images/refresh_icon.png")); - refreshButton->setIconSize(QSize(32, 32)); + refreshButton->setIcon(QIcon(":images/refreshlist_icon.png")); + refreshButton->setIconSize(QSize(40, 40)); + fullscreenButton = new QPushButton(centralWidget); + fullscreenButton->setFlat(true); + fullscreenButton->setIcon(QIcon(":images/fullscreen_icon.png")); + fullscreenButton->setIconSize(QSize(38, 38)); settingsButton = new QPushButton(centralWidget); settingsButton->setFlat(true); settingsButton->setIcon(QIcon(":images/settings_icon.png")); - settingsButton->setIconSize(QSize(44, 44)); + settingsButton->setIconSize(QSize(40, 40)); controllerButton = new QPushButton(centralWidget); controllerButton->setFlat(true); controllerButton->setIcon(QIcon(":images/controller_icon.png")); - controllerButton->setIconSize(QSize(40, 40)); + controllerButton->setIconSize(QSize(55, 48)); keyboardButton = new QPushButton(centralWidget); keyboardButton->setFlat(true); keyboardButton->setIcon(QIcon(":images/keyboard_icon.png")); - keyboardButton->setIconSize(QSize(48, 44)); + keyboardButton->setIconSize(QSize(50, 50)); + restartButton = new QPushButton(centralWidget); + restartButton->setFlat(true); + restartButton->setIcon(QIcon(":images/restart_game_icon.png")); + restartButton->setIconSize(QSize(40, 40)); sizeSliderContainer = new QWidget(centralWidget); sizeSliderContainer->setObjectName("sizeSliderContainer"); @@ -304,6 +323,7 @@ public: menuView->addAction(refreshGameListAct); menuView->addAction(menuGame_List_Mode->menuAction()); menuView->addAction(menuGame_List_Icons->menuAction()); + menuView->addAction(toggleLabelsAct); menuView->addAction(menuThemes->menuAction()); menuThemes->addAction(setThemeDark); menuThemes->addAction(setThemeLight); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 80d196147..fcdde7240 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -11,6 +11,7 @@ #include "common/config.h" #include "common/elf_info.h" #include "common/version.h" +#include "core/debug_state.h" #include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" #include "imgui/renderer/imgui_core.h" @@ -396,6 +397,25 @@ void WindowSDL::WaitEvent() { case SDL_EVENT_QUIT: is_open = false; break; + case SDL_EVENT_TOGGLE_FULLSCREEN: { + if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) { + SDL_SetWindowFullscreen(window, 0); + } else { + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); + } + break; + } + case SDL_EVENT_TOGGLE_PAUSE: + SDL_Log("Received SDL_EVENT_TOGGLE_PAUSE"); + + if (DebugState.IsGuestThreadsPaused()) { + SDL_Log("Game Resumed"); + DebugState.ResumeGuestThreads(); + } else { + SDL_Log("Game Paused"); + DebugState.PauseGuestThreads(); + } + break; default: break; } diff --git a/src/sdl_window.h b/src/sdl_window.h index 03ba0797b..48a9be58c 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -7,6 +7,8 @@ #include "core/libraries/pad/pad.h" #include "input/controller.h" #include "string" +#define SDL_EVENT_TOGGLE_FULLSCREEN (SDL_EVENT_USER + 1) +#define SDL_EVENT_TOGGLE_PAUSE (SDL_EVENT_USER + 2) struct SDL_Window; struct SDL_Gamepad; diff --git a/src/shadps4.qrc b/src/shadps4.qrc index 340756f5c..83dea01c4 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -1,38 +1,40 @@ - - images/shadps4.ico - images/about_icon.png - images/dump_icon.png - images/play_icon.png - images/pause_icon.png - images/stop_icon.png - images/utils_icon.png - images/file_icon.png - images/trophy_icon.png - images/folder_icon.png - images/themes_icon.png - images/iconsize_icon.png - images/list_icon.png - images/grid_icon.png - images/exit_icon.png - images/settings_icon.png - images/controller_icon.png - images/refresh_icon.png - images/update_icon.png - images/list_mode_icon.png - images/flag_jp.png - images/flag_eu.png - images/flag_unk.png - images/flag_us.png - images/flag_world.png - images/flag_china.png - images/github.png - images/discord.png - images/ko-fi.png - images/youtube.png - images/website.png - images/ps4_controller.png - images/keyboard_icon.png - images/KBM.png - + + images/shadps4.ico + images/about_icon.png + images/dump_icon.png + images/play_icon.png + images/pause_icon.png + images/stop_icon.png + images/utils_icon.png + images/file_icon.png + images/folder_icon.png + images/themes_icon.png + images/iconsize_icon.png + images/list_icon.png + images/grid_icon.png + images/exit_icon.png + images/settings_icon.png + images/controller_icon.png + images/restart_game_icon.png + images/update_icon.png + images/list_mode_icon.png + images/flag_jp.png + images/flag_eu.png + images/flag_unk.png + images/flag_us.png + images/flag_world.png + images/flag_china.png + images/github.png + images/discord.png + images/ko-fi.png + images/youtube.png + images/website.png + images/ps4_controller.png + images/keyboard_icon.png + images/KBM.png + images/fullscreen_icon.png + images/refreshlist_icon.png + images/trophy_icon.png + From 437af9320104011b32c34c85a6d888a78969b705 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 26 Mar 2025 23:51:17 +0200 Subject: [PATCH 074/194] New translations en_us.ts (Chinese Traditional) (#2691) --- src/qt_gui/translations/zh_TW.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index bd546380a..fb42a43b0 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -407,7 +407,7 @@ ControlSettings Configure Controls - Configure Controls + 操控設定 D-Pad @@ -519,7 +519,7 @@ Color Adjustment - Color Adjustment + 色彩調整 R: @@ -543,7 +543,7 @@ Unable to Save - Unable to Save + 無法保存 Cannot bind axis values more than once @@ -551,11 +551,11 @@ Save - Save + 保存 Apply - Apply + 套用 Restore Defaults @@ -563,7 +563,7 @@ Cancel - Cancel + 取消 @@ -578,7 +578,7 @@ Error - Error + 錯誤 Could not open the file for reading @@ -590,7 +590,7 @@ Save Changes - Save Changes + 儲存變更 Do you want to save changes? From c96853816a7d0897a113352c186ff40b94ea885c Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 27 Mar 2025 12:14:09 +0200 Subject: [PATCH 075/194] [ci skip] Qt GUI: Update Translation. (#2692) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 20cba0378..d18609295 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + + + + Pause + + + + Stop + + + + Restart + + + + Full Screen + + + + Controllers + + + + Keyboard + + + + Refresh List + + + + Resume + + + + Show Labels Under Icons + + PKGViewer From 602de0c370467f75d07a7841672e8dacd09faec5 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Thu, 27 Mar 2025 21:40:15 +0100 Subject: [PATCH 076/194] Fork detection: Fix PR actions only showing HEAD as the branch name (#2697) * I'd be very surprised if this works 1st try * More logging and cleanup * Minor fixes --- CMakeLists.txt | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c0932b5c..b3d214ec9 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,28 +113,39 @@ git_describe(GIT_DESC --always --long --dirty) git_branch_name(GIT_BRANCH) string(TIMESTAMP BUILD_DATE "%Y-%m-%d %H:%M:%S") +message("start git things") # Try to get the upstream remote and branch +message("check for remote and branch") execute_process( COMMAND git rev-parse --abbrev-ref --symbolic-full-name @{u} OUTPUT_VARIABLE GIT_REMOTE_NAME - RESULT_VARIABLE GIT_BRANCH_RESULT + RESULT_VARIABLE GIT_REMOTE_RESULT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE ) - # If there's no upstream set or the command failed, check remote.pushDefault -if (GIT_BRANCH_RESULT OR GIT_REMOTE_NAME STREQUAL "") +if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") + message("check default push") execute_process( COMMAND git config --get remote.pushDefault OUTPUT_VARIABLE GIT_REMOTE_NAME - RESULT_VARIABLE GIT_PUSH_DEFAULT_RESULT + RESULT_VARIABLE GIT_REMOTE_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() +# If running in GitHub Actions and the above fails +if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") + message("check github") + set(GIT_REMOTE_NAME "origin") + + if (DEFINED ENV{GITHUB_HEAD_REF}) # PR branch name + set(GIT_BRANCH "pr-$ENV{GITHUB_HEAD_REF}") + elseif (DEFINED ENV{GITHUB_REF}) # Normal branch name + string(REGEX REPLACE "^refs/[^/]*/" "" GIT_BRANCH "$ENV{GITHUB_REF}") + else() + message("couldn't find branch") + set(GIT_BRANCH "detached-head") endif() else() # Extract remote name if the output contains a remote/branch format @@ -148,6 +159,7 @@ else() endif() # Get remote link +message("getting remote link") execute_process( COMMAND git config --get remote.${GIT_REMOTE_NAME}.url OUTPUT_VARIABLE GIT_REMOTE_URL @@ -156,6 +168,8 @@ execute_process( configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) +message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") + find_package(Boost 1.84.0 CONFIG) find_package(FFmpeg 5.1.2 MODULE) find_package(fmt 10.2.0 CONFIG) From d339b3f7d663ba89bb5b3cd572ead77b41b0f0a8 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Thu, 27 Mar 2025 20:40:40 +0000 Subject: [PATCH 077/194] change async to sync (#2698) --- src/common/config.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 09236f30c..b113ac0ef 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -41,7 +41,7 @@ static u32 screenWidth = 1280; static u32 screenHeight = 720; static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select static std::string logFilter; -static std::string logType = "async"; +static std::string logType = "sync"; static std::string userName = "shadPS4"; static std::string updateChannel; static std::string chooseHomeTab; @@ -1129,7 +1129,7 @@ void setDefaultValues() { screenWidth = 1280; screenHeight = 720; logFilter = ""; - logType = "async"; + logType = "sync"; userName = "shadPS4"; if (Common::isRelease) { updateChannel = "Release"; From be22674f8c1ac84e1cff89947ff4a6753070f21b Mon Sep 17 00:00:00 2001 From: IndecisiveTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Fri, 28 Mar 2025 16:15:31 +0200 Subject: [PATCH 078/194] code: Remove fpkg code Signed-off-by: georgemoralis --- CMakeLists.txt | 9 - src/core/crypto/crypto.cpp | 215 ---------- src/core/crypto/crypto.h | 63 --- src/core/crypto/keys.h | 305 -------------- src/core/file_format/pkg.h | 174 -------- src/core/file_format/pkg_type.cpp | 638 ------------------------------ src/core/file_format/pkg_type.h | 10 - src/core/file_format/trp.cpp | 30 +- src/core/file_format/trp.h | 4 +- src/qt_gui/main_window.cpp | 284 +------------ src/qt_gui/main_window.h | 18 - src/qt_gui/main_window_ui.h | 17 +- src/qt_gui/pkg_viewer.cpp | 217 ---------- src/qt_gui/pkg_viewer.h | 62 --- 14 files changed, 31 insertions(+), 2015 deletions(-) delete mode 100644 src/core/crypto/crypto.cpp delete mode 100644 src/core/crypto/crypto.h delete mode 100644 src/core/crypto/keys.h delete mode 100644 src/core/file_format/pkg.h delete mode 100644 src/core/file_format/pkg_type.cpp delete mode 100644 src/core/file_format/pkg_type.h delete mode 100644 src/qt_gui/pkg_viewer.cpp delete mode 100644 src/qt_gui/pkg_viewer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b3d214ec9..0359246c8 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -653,9 +653,6 @@ set(CORE src/core/aerolib/stubs.cpp src/core/aerolib/aerolib.h src/core/address_space.cpp src/core/address_space.h - src/core/crypto/crypto.cpp - src/core/crypto/crypto.h - src/core/crypto/keys.h src/core/devices/base_device.cpp src/core/devices/base_device.h src/core/devices/ioccom.h @@ -673,10 +670,6 @@ set(CORE src/core/aerolib/stubs.cpp src/core/devices/srandom_device.cpp src/core/devices/srandom_device.h src/core/file_format/pfs.h - src/core/file_format/pkg.cpp - src/core/file_format/pkg.h - src/core/file_format/pkg_type.cpp - src/core/file_format/pkg_type.h src/core/file_format/psf.cpp src/core/file_format/psf.h src/core/file_format/playgo_chunk.cpp @@ -978,8 +971,6 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/game_install_dialog.h src/qt_gui/install_dir_select.cpp src/qt_gui/install_dir_select.h - src/qt_gui/pkg_viewer.cpp - src/qt_gui/pkg_viewer.h src/qt_gui/trophy_viewer.cpp src/qt_gui/trophy_viewer.h src/qt_gui/elf_viewer.cpp diff --git a/src/core/crypto/crypto.cpp b/src/core/crypto/crypto.cpp deleted file mode 100644 index 4020edfd8..000000000 --- a/src/core/crypto/crypto.cpp +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "crypto.h" - -CryptoPP::RSA::PrivateKey Crypto::key_pkg_derived_key3_keyset_init() { - CryptoPP::InvertibleRSAFunction params; - params.SetPrime1(CryptoPP::Integer(PkgDerivedKey3Keyset::Prime1, 0x80)); - params.SetPrime2(CryptoPP::Integer(PkgDerivedKey3Keyset::Prime2, 0x80)); - - params.SetPublicExponent(CryptoPP::Integer(PkgDerivedKey3Keyset::PublicExponent, 4)); - params.SetPrivateExponent(CryptoPP::Integer(PkgDerivedKey3Keyset::PrivateExponent, 0x100)); - - params.SetModPrime1PrivateExponent(CryptoPP::Integer(PkgDerivedKey3Keyset::Exponent1, 0x80)); - params.SetModPrime2PrivateExponent(CryptoPP::Integer(PkgDerivedKey3Keyset::Exponent2, 0x80)); - - params.SetModulus(CryptoPP::Integer(PkgDerivedKey3Keyset::Modulus, 0x100)); - params.SetMultiplicativeInverseOfPrime2ModPrime1( - CryptoPP::Integer(PkgDerivedKey3Keyset::Coefficient, 0x80)); - - CryptoPP::RSA::PrivateKey privateKey(params); - - return privateKey; -} - -CryptoPP::RSA::PrivateKey Crypto::FakeKeyset_keyset_init() { - CryptoPP::InvertibleRSAFunction params; - params.SetPrime1(CryptoPP::Integer(FakeKeyset::Prime1, 0x80)); - params.SetPrime2(CryptoPP::Integer(FakeKeyset::Prime2, 0x80)); - - params.SetPublicExponent(CryptoPP::Integer(FakeKeyset::PublicExponent, 4)); - params.SetPrivateExponent(CryptoPP::Integer(FakeKeyset::PrivateExponent, 0x100)); - - params.SetModPrime1PrivateExponent(CryptoPP::Integer(FakeKeyset::Exponent1, 0x80)); - params.SetModPrime2PrivateExponent(CryptoPP::Integer(FakeKeyset::Exponent2, 0x80)); - - params.SetModulus(CryptoPP::Integer(FakeKeyset::Modulus, 0x100)); - params.SetMultiplicativeInverseOfPrime2ModPrime1( - CryptoPP::Integer(FakeKeyset::Coefficient, 0x80)); - - CryptoPP::RSA::PrivateKey privateKey(params); - - return privateKey; -} - -CryptoPP::RSA::PrivateKey Crypto::DebugRifKeyset_init() { - CryptoPP::InvertibleRSAFunction params; - params.SetPrime1(CryptoPP::Integer(DebugRifKeyset::Prime1, sizeof(DebugRifKeyset::Prime1))); - params.SetPrime2(CryptoPP::Integer(DebugRifKeyset::Prime2, sizeof(DebugRifKeyset::Prime2))); - - params.SetPublicExponent( - CryptoPP::Integer(DebugRifKeyset::PublicExponent, sizeof(DebugRifKeyset::PublicExponent))); - params.SetPrivateExponent(CryptoPP::Integer(DebugRifKeyset::PrivateExponent, - sizeof(DebugRifKeyset::PrivateExponent))); - - params.SetModPrime1PrivateExponent( - CryptoPP::Integer(DebugRifKeyset::Exponent1, sizeof(DebugRifKeyset::Exponent1))); - params.SetModPrime2PrivateExponent( - CryptoPP::Integer(DebugRifKeyset::Exponent2, sizeof(DebugRifKeyset::Exponent2))); - - params.SetModulus(CryptoPP::Integer(DebugRifKeyset::Modulus, sizeof(DebugRifKeyset::Modulus))); - params.SetMultiplicativeInverseOfPrime2ModPrime1( - CryptoPP::Integer(DebugRifKeyset::Coefficient, sizeof(DebugRifKeyset::Coefficient))); - - CryptoPP::RSA::PrivateKey privateKey(params); - - return privateKey; -} - -void Crypto::RSA2048Decrypt(std::span dec_key, - std::span ciphertext, - bool is_dk3) { // RSAES_PKCS1v15_ - // Create an RSA decryptor - CryptoPP::RSA::PrivateKey privateKey; - if (is_dk3) { - privateKey = key_pkg_derived_key3_keyset_init(); - } else { - privateKey = FakeKeyset_keyset_init(); - } - - CryptoPP::RSAES_PKCS1v15_Decryptor rsaDecryptor(privateKey); - - // Allocate memory for the decrypted data - std::array decrypted; - - // Perform the decryption - CryptoPP::AutoSeededRandomPool rng; - CryptoPP::DecodingResult result = - rsaDecryptor.Decrypt(rng, ciphertext.data(), decrypted.size(), decrypted.data()); - std::copy(decrypted.begin(), decrypted.begin() + dec_key.size(), dec_key.begin()); -} - -void Crypto::ivKeyHASH256(std::span cipher_input, - std::span ivkey_result) { - CryptoPP::SHA256 sha256; - std::array hashResult; - auto array_sink = new CryptoPP::ArraySink(hashResult.data(), CryptoPP::SHA256::DIGESTSIZE); - auto filter = new CryptoPP::HashFilter(sha256, array_sink); - CryptoPP::ArraySource r(cipher_input.data(), cipher_input.size(), true, filter); - std::copy(hashResult.begin(), hashResult.begin() + ivkey_result.size(), ivkey_result.begin()); -} - -void Crypto::aesCbcCfb128Decrypt(std::span ivkey, - std::span ciphertext, - std::span decrypted) { - std::array key; - std::array iv; - - std::copy(ivkey.begin() + 16, ivkey.begin() + 16 + key.size(), key.begin()); - std::copy(ivkey.begin(), ivkey.begin() + iv.size(), iv.begin()); - - CryptoPP::AES::Decryption aesDecryption(key.data(), CryptoPP::AES::DEFAULT_KEYLENGTH); - CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv.data()); - - for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) { - cbcDecryption.ProcessData(decrypted.data() + i, ciphertext.data() + i, - CryptoPP::AES::BLOCKSIZE); - } -} - -void Crypto::aesCbcCfb128DecryptEntry(std::span ivkey, - std::span ciphertext, - std::span decrypted) { - std::array key; - std::array iv; - - std::copy(ivkey.begin() + 16, ivkey.begin() + 16 + key.size(), key.begin()); - std::copy(ivkey.begin(), ivkey.begin() + iv.size(), iv.begin()); - - CryptoPP::AES::Decryption aesDecryption(key.data(), CryptoPP::AES::DEFAULT_KEYLENGTH); - CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv.data()); - - for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) { - cbcDecryption.ProcessData(decrypted.data() + i, ciphertext.data() + i, - CryptoPP::AES::BLOCKSIZE); - } -} - -void Crypto::decryptEFSM(std::span trophyKey, - std::span NPcommID, - std::span efsmIv, std::span ciphertext, - std::span decrypted) { - - // step 1: Encrypt NPcommID - CryptoPP::CBC_Mode::Encryption encrypt; - - std::vector trophyIv(16, 0); - std::vector trpKey(16); - - encrypt.SetKeyWithIV(trophyKey.data(), trophyKey.size(), trophyIv.data()); - encrypt.ProcessData(trpKey.data(), NPcommID.data(), 16); - - // step 2: decrypt efsm. - CryptoPP::CBC_Mode::Decryption decrypt; - decrypt.SetKeyWithIV(trpKey.data(), trpKey.size(), efsmIv.data()); - - for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) { - decrypt.ProcessData(decrypted.data() + i, ciphertext.data() + i, CryptoPP::AES::BLOCKSIZE); - } -} - -void Crypto::PfsGenCryptoKey(std::span ekpfs, - std::span seed, - std::span dataKey, - std::span tweakKey) { - CryptoPP::HMAC hmac(ekpfs.data(), ekpfs.size()); - - CryptoPP::SecByteBlock d(20); // Use Crypto++ SecByteBlock for better memory management - - // Copy the bytes of 'index' to the 'd' array - uint32_t index = 1; - std::memcpy(d, &index, sizeof(uint32_t)); - - // Copy the bytes of 'seed' to the 'd' array starting from index 4 - std::memcpy(d + sizeof(uint32_t), seed.data(), seed.size()); - - // Allocate memory for 'u64' using new - std::vector data_tweak_key(hmac.DigestSize()); - - // Calculate the HMAC - hmac.CalculateDigest(data_tweak_key.data(), d, d.size()); - std::copy(data_tweak_key.begin(), data_tweak_key.begin() + dataKey.size(), tweakKey.begin()); - std::copy(data_tweak_key.begin() + tweakKey.size(), - data_tweak_key.begin() + tweakKey.size() + dataKey.size(), dataKey.begin()); -} - -void Crypto::decryptPFS(std::span dataKey, - std::span tweakKey, std::span src_image, - std::span dst_image, u64 sector) { - // Start at 0x10000 to keep the header when decrypting the whole pfs_image. - for (int i = 0; i < src_image.size(); i += 0x1000) { - const u64 current_sector = sector + (i / 0x1000); - CryptoPP::ECB_Mode::Encryption encrypt(tweakKey.data(), tweakKey.size()); - CryptoPP::ECB_Mode::Decryption decrypt(dataKey.data(), dataKey.size()); - - std::array tweak{}; - std::array encryptedTweak; - std::array xorBuffer; - std::memcpy(tweak.data(), ¤t_sector, sizeof(u64)); - - // Encrypt the tweak for each sector. - encrypt.ProcessData(encryptedTweak.data(), tweak.data(), 16); - - for (int plaintextOffset = 0; plaintextOffset < 0x1000; plaintextOffset += 16) { - xtsXorBlock(xorBuffer.data(), src_image.data() + i + plaintextOffset, - encryptedTweak.data()); // x, c, t - decrypt.ProcessData(xorBuffer.data(), xorBuffer.data(), 16); // x, x - xtsXorBlock(dst_image.data() + i + plaintextOffset, xorBuffer.data(), - encryptedTweak.data()); //(p) c, x , t - xtsMult(encryptedTweak); - } - } -} diff --git a/src/core/crypto/crypto.h b/src/core/crypto/crypto.h deleted file mode 100644 index b5d8104b5..000000000 --- a/src/core/crypto/crypto.h +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/types.h" -#include "keys.h" - -class Crypto { -public: - CryptoPP::RSA::PrivateKey key_pkg_derived_key3_keyset_init(); - CryptoPP::RSA::PrivateKey FakeKeyset_keyset_init(); - CryptoPP::RSA::PrivateKey DebugRifKeyset_init(); - - void RSA2048Decrypt(std::span dk3, - std::span ciphertext, - bool is_dk3); // RSAES_PKCS1v15_ - void ivKeyHASH256(std::span cipher_input, - std::span ivkey_result); - void aesCbcCfb128Decrypt(std::span ivkey, - std::span ciphertext, - std::span decrypted); - void aesCbcCfb128DecryptEntry(std::span ivkey, - std::span ciphertext, - std::span decrypted); - void decryptEFSM(std::span trophyKey, - std::span NPcommID, std::span efsmIv, - std::span ciphertext, std::span decrypted); - void PfsGenCryptoKey(std::span ekpfs, - std::span seed, - std::span dataKey, - std::span tweakKey); - void decryptPFS(std::span dataKey, - std::span tweakKey, std::span src_image, - std::span dst_image, u64 sector); - - void xtsXorBlock(CryptoPP::byte* x, const CryptoPP::byte* a, const CryptoPP::byte* b) { - for (int i = 0; i < 16; i++) { - x[i] = a[i] ^ b[i]; - } - } - - void xtsMult(std::span encryptedTweak) { - int feedback = 0; - for (int k = 0; k < encryptedTweak.size(); k++) { - const auto tmp = (encryptedTweak[k] >> 7) & 1; - encryptedTweak[k] = ((encryptedTweak[k] << 1) + feedback) & 0xFF; - feedback = tmp; - } - if (feedback != 0) { - encryptedTweak[0] ^= 0x87; - } - } -}; diff --git a/src/core/crypto/keys.h b/src/core/crypto/keys.h deleted file mode 100644 index 441082481..000000000 --- a/src/core/crypto/keys.h +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once -#include - -class FakeKeyset { -public: - // Constructor - static constexpr CryptoPP::byte Exponent1[] = { - 0x6D, 0x48, 0xE0, 0x54, 0x40, 0x25, 0xC8, 0x41, 0x29, 0x52, 0x42, 0x27, 0xEB, 0xD2, 0xC7, - 0xAB, 0x6B, 0x9C, 0x27, 0x0A, 0xB4, 0x1F, 0x94, 0x4E, 0xFA, 0x42, 0x1D, 0xB7, 0xBC, 0xB9, - 0xAE, 0xBC, 0x04, 0x6F, 0x75, 0x8F, 0x10, 0x5F, 0x89, 0xAC, 0xAB, 0x9C, 0xD2, 0xFA, 0xE6, - 0xA4, 0x13, 0x83, 0x68, 0xD4, 0x56, 0x38, 0xFE, 0xE5, 0x2B, 0x78, 0x44, 0x9C, 0x34, 0xE6, - 0x5A, 0xA0, 0xBE, 0x05, 0x70, 0xAD, 0x15, 0xC3, 0x2D, 0x31, 0xAC, 0x97, 0x5D, 0x88, 0xFC, - 0xC1, 0x62, 0x3D, 0xE2, 0xED, 0x11, 0xDB, 0xB6, 0x9E, 0xFC, 0x5A, 0x5A, 0x03, 0xF6, 0xCF, - 0x08, 0xD4, 0x5D, 0x90, 0xC9, 0x2A, 0xB9, 0x9B, 0xCF, 0xC8, 0x1A, 0x65, 0xF3, 0x5B, 0xE8, - 0x7F, 0xCF, 0xA5, 0xA6, 0x4C, 0x5C, 0x2A, 0x12, 0x0F, 0x92, 0xA5, 0xE3, 0xF0, 0x17, 0x1E, - 0x9A, 0x97, 0x45, 0x86, 0xFD, 0xDB, 0x54, 0x25}; - // exponent2 = d mod (q - 1) - static constexpr CryptoPP::byte Exponent2[] = { - 0x2A, 0x51, 0xCE, 0x02, 0x44, 0x28, 0x50, 0xE8, 0x30, 0x20, 0x7C, 0x9C, 0x55, 0xBF, 0x60, - 0x39, 0xBC, 0xD1, 0xF0, 0xE7, 0x68, 0xF8, 0x08, 0x5B, 0x61, 0x1F, 0xA7, 0xBF, 0xD0, 0xE8, - 0x8B, 0xB5, 0xB1, 0xD5, 0xD9, 0x16, 0xAC, 0x75, 0x0C, 0x6D, 0xF2, 0xE0, 0xB5, 0x97, 0x75, - 0xD2, 0x68, 0x16, 0x1F, 0x00, 0x7D, 0x8B, 0x17, 0xE8, 0x78, 0x48, 0x41, 0x71, 0x2B, 0x18, - 0x96, 0x80, 0x11, 0xDB, 0x68, 0x39, 0x9C, 0xD6, 0xE0, 0x72, 0x42, 0x86, 0xF0, 0x1B, 0x16, - 0x0D, 0x3E, 0x12, 0x94, 0x3D, 0x25, 0xA8, 0xA9, 0x30, 0x9E, 0x54, 0x5A, 0xD6, 0x36, 0x6C, - 0xD6, 0x8C, 0x20, 0x62, 0x8F, 0xA1, 0x6B, 0x1F, 0x7C, 0x6D, 0xB2, 0xB1, 0xC1, 0x2E, 0xAD, - 0x36, 0x02, 0x9C, 0x3A, 0xCA, 0x2F, 0x09, 0xD2, 0x45, 0x9E, 0xEB, 0xF2, 0xBC, 0x6C, 0xAA, - 0x3B, 0x3E, 0x90, 0xBC, 0x38, 0x67, 0x35, 0x4D}; - // e - static constexpr CryptoPP::byte PublicExponent[] = {0, 1, 0, 1}; - // (InverseQ)(q) = 1 mod p - static constexpr CryptoPP::byte Coefficient[] = { - 0x0B, 0x67, 0x1C, 0x0D, 0x6C, 0x57, 0xD3, 0xE7, 0x05, 0x65, 0x94, 0x31, 0x56, 0x55, 0xFD, - 0x28, 0x08, 0xFA, 0x05, 0x8A, 0xCC, 0x55, 0x39, 0x61, 0x97, 0x63, 0xA0, 0x16, 0x27, 0x3D, - 0xED, 0xC1, 0x16, 0x40, 0x2A, 0x12, 0xEA, 0x6F, 0xD9, 0xD8, 0x58, 0x56, 0xA8, 0x56, 0x8B, - 0x0D, 0x38, 0x5E, 0x1E, 0x80, 0x3B, 0x5F, 0x40, 0x80, 0x6F, 0x62, 0x4F, 0x28, 0xA2, 0x69, - 0xF3, 0xD3, 0xF7, 0xFD, 0xB2, 0xC3, 0x52, 0x43, 0x20, 0x92, 0x9D, 0x97, 0x8D, 0xA0, 0x15, - 0x07, 0x15, 0x6E, 0xA4, 0x0D, 0x56, 0xD3, 0x37, 0x1A, 0xC4, 0x9E, 0xDF, 0x02, 0x49, 0xB8, - 0x0A, 0x84, 0x62, 0xF5, 0xFA, 0xB9, 0x3F, 0xA4, 0x09, 0x76, 0xCC, 0xAA, 0xB9, 0x9B, 0xA6, - 0x4F, 0xC1, 0x6A, 0x64, 0xCE, 0xD8, 0x77, 0xAB, 0x4B, 0xF9, 0xA0, 0xAE, 0xDA, 0xF1, 0x67, - 0x87, 0x7C, 0x98, 0x5C, 0x7E, 0xB8, 0x73, 0xF5}; - // n = p * q - static constexpr CryptoPP::byte Modulus[] = { - 0xC6, 0xCF, 0x71, 0xE7, 0xE5, 0x9A, 0xF0, 0xD1, 0x2A, 0x2C, 0x45, 0x8B, 0xF9, 0x2A, 0x0E, - 0xC1, 0x43, 0x05, 0x8B, 0xC3, 0x71, 0x17, 0x80, 0x1D, 0xCD, 0x49, 0x7D, 0xDE, 0x35, 0x9D, - 0x25, 0x9B, 0xA0, 0xD7, 0xA0, 0xF2, 0x7D, 0x6C, 0x08, 0x7E, 0xAA, 0x55, 0x02, 0x68, 0x2B, - 0x23, 0xC6, 0x44, 0xB8, 0x44, 0x18, 0xEB, 0x56, 0xCF, 0x16, 0xA2, 0x48, 0x03, 0xC9, 0xE7, - 0x4F, 0x87, 0xEB, 0x3D, 0x30, 0xC3, 0x15, 0x88, 0xBF, 0x20, 0xE7, 0x9D, 0xFF, 0x77, 0x0C, - 0xDE, 0x1D, 0x24, 0x1E, 0x63, 0xA9, 0x4F, 0x8A, 0xBF, 0x5B, 0xBE, 0x60, 0x19, 0x68, 0x33, - 0x3B, 0xFC, 0xED, 0x9F, 0x47, 0x4E, 0x5F, 0xF8, 0xEA, 0xCB, 0x3D, 0x00, 0xBD, 0x67, 0x01, - 0xF9, 0x2C, 0x6D, 0xC6, 0xAC, 0x13, 0x64, 0xE7, 0x67, 0x14, 0xF3, 0xDC, 0x52, 0x69, 0x6A, - 0xB9, 0x83, 0x2C, 0x42, 0x30, 0x13, 0x1B, 0xB2, 0xD8, 0xA5, 0x02, 0x0D, 0x79, 0xED, 0x96, - 0xB1, 0x0D, 0xF8, 0xCC, 0x0C, 0xDF, 0x81, 0x95, 0x4F, 0x03, 0x58, 0x09, 0x57, 0x0E, 0x80, - 0x69, 0x2E, 0xFE, 0xFF, 0x52, 0x77, 0xEA, 0x75, 0x28, 0xA8, 0xFB, 0xC9, 0xBE, 0xBF, 0x9F, - 0xBB, 0xB7, 0x79, 0x8E, 0x18, 0x05, 0xE1, 0x80, 0xBD, 0x50, 0x34, 0x94, 0x81, 0xD3, 0x53, - 0xC2, 0x69, 0xA2, 0xD2, 0x4C, 0xCF, 0x6C, 0xF4, 0x57, 0x2C, 0x10, 0x4A, 0x3F, 0xFB, 0x22, - 0xFD, 0x8B, 0x97, 0xE2, 0xC9, 0x5B, 0xA6, 0x2B, 0xCD, 0xD6, 0x1B, 0x6B, 0xDB, 0x68, 0x7F, - 0x4B, 0xC2, 0xA0, 0x50, 0x34, 0xC0, 0x05, 0xE5, 0x8D, 0xEF, 0x24, 0x67, 0xFF, 0x93, 0x40, - 0xCF, 0x2D, 0x62, 0xA2, 0xA0, 0x50, 0xB1, 0xF1, 0x3A, 0xA8, 0x3D, 0xFD, 0x80, 0xD1, 0xF9, - 0xB8, 0x05, 0x22, 0xAF, 0xC8, 0x35, 0x45, 0x90, 0x58, 0x8E, 0xE3, 0x3A, 0x7C, 0xBD, 0x3E, - 0x27}; - // p - static constexpr CryptoPP::byte Prime1[] = { - 0xFE, 0xF6, 0xBF, 0x1D, 0x69, 0xAB, 0x16, 0x25, 0x08, 0x47, 0x55, 0x6B, 0x86, 0xE4, 0x35, - 0x88, 0x72, 0x2A, 0xB1, 0x3D, 0xF8, 0xB6, 0x44, 0xCA, 0xB3, 0xAB, 0x19, 0xD1, 0x04, 0x24, - 0x28, 0x0A, 0x74, 0x55, 0xB8, 0x15, 0x45, 0x09, 0xCC, 0x13, 0x1C, 0xF2, 0xBA, 0x37, 0xA9, - 0x03, 0x90, 0x8F, 0x02, 0x10, 0xFF, 0x25, 0x79, 0x86, 0xCC, 0x18, 0x50, 0x9A, 0x10, 0x5F, - 0x5B, 0x4C, 0x1C, 0x4E, 0xB0, 0xA7, 0xE3, 0x59, 0xB1, 0x2D, 0xA0, 0xC6, 0xB0, 0x20, 0x2C, - 0x21, 0x33, 0x12, 0xB3, 0xAF, 0x72, 0x34, 0x83, 0xCD, 0x52, 0x2F, 0xAF, 0x0F, 0x20, 0x5A, - 0x1B, 0xC0, 0xE2, 0xA3, 0x76, 0x34, 0x0F, 0xD7, 0xFC, 0xC1, 0x41, 0xC9, 0xF9, 0x79, 0x40, - 0x17, 0x42, 0x21, 0x3E, 0x9D, 0xFD, 0xC7, 0xC1, 0x50, 0xDE, 0x44, 0x5A, 0xC9, 0x31, 0x89, - 0x6A, 0x78, 0x05, 0xBE, 0x65, 0xB4, 0xE8, 0x2D}; - // q - static constexpr CryptoPP::byte Prime2[] = { - 0xC7, 0x9E, 0x47, 0x58, 0x00, 0x7D, 0x62, 0x82, 0xB0, 0xD2, 0x22, 0x81, 0xD4, 0xA8, 0x97, - 0x1B, 0x79, 0x0C, 0x3A, 0xB0, 0xD7, 0xC9, 0x30, 0xE3, 0xC3, 0x53, 0x8E, 0x57, 0xEF, 0xF0, - 0x9B, 0x9F, 0xB3, 0x90, 0x52, 0xC6, 0x94, 0x22, 0x36, 0xAA, 0xE6, 0x4A, 0x5F, 0x72, 0x1D, - 0x70, 0xE8, 0x76, 0x58, 0xC8, 0xB2, 0x91, 0xCE, 0x9C, 0xC3, 0xE9, 0x09, 0x7F, 0x2E, 0x47, - 0x97, 0xCC, 0x90, 0x39, 0x15, 0x35, 0x31, 0xDE, 0x1F, 0x0C, 0x8C, 0x0D, 0xC1, 0xC2, 0x92, - 0xBE, 0x97, 0xBF, 0x2F, 0x91, 0xA1, 0x8C, 0x7D, 0x50, 0xA8, 0x21, 0x2F, 0xD7, 0xA2, 0x9A, - 0x7E, 0xB5, 0xA7, 0x2A, 0x90, 0x02, 0xD9, 0xF3, 0x3D, 0xD1, 0xEB, 0xB8, 0xE0, 0x5A, 0x79, - 0x9E, 0x7D, 0x8D, 0xCA, 0x18, 0x6D, 0xBD, 0x9E, 0xA1, 0x80, 0x28, 0x6B, 0x2A, 0xFE, 0x51, - 0x24, 0x9B, 0x6F, 0x4D, 0x84, 0x77, 0x80, 0x23}; - static constexpr CryptoPP::byte PrivateExponent[] = { - 0x7F, 0x76, 0xCD, 0x0E, 0xE2, 0xD4, 0xDE, 0x05, 0x1C, 0xC6, 0xD9, 0xA8, 0x0E, 0x8D, 0xFA, - 0x7B, 0xCA, 0x1E, 0xAA, 0x27, 0x1A, 0x40, 0xF8, 0xF1, 0x22, 0x87, 0x35, 0xDD, 0xDB, 0xFD, - 0xEE, 0xF8, 0xC2, 0xBC, 0xBD, 0x01, 0xFB, 0x8B, 0xE2, 0x3E, 0x63, 0xB2, 0xB1, 0x22, 0x5C, - 0x56, 0x49, 0x6E, 0x11, 0xBE, 0x07, 0x44, 0x0B, 0x9A, 0x26, 0x66, 0xD1, 0x49, 0x2C, 0x8F, - 0xD3, 0x1B, 0xCF, 0xA4, 0xA1, 0xB8, 0xD1, 0xFB, 0xA4, 0x9E, 0xD2, 0x21, 0x28, 0x83, 0x09, - 0x8A, 0xF6, 0xA0, 0x0B, 0xA3, 0xD6, 0x0F, 0x9B, 0x63, 0x68, 0xCC, 0xBC, 0x0C, 0x4E, 0x14, - 0x5B, 0x27, 0xA4, 0xA9, 0xF4, 0x2B, 0xB9, 0xB8, 0x7B, 0xC0, 0xE6, 0x51, 0xAD, 0x1D, 0x77, - 0xD4, 0x6B, 0xB9, 0xCE, 0x20, 0xD1, 0x26, 0x66, 0x7E, 0x5E, 0x9E, 0xA2, 0xE9, 0x6B, 0x90, - 0xF3, 0x73, 0xB8, 0x52, 0x8F, 0x44, 0x11, 0x03, 0x0C, 0x13, 0x97, 0x39, 0x3D, 0x13, 0x22, - 0x58, 0xD5, 0x43, 0x82, 0x49, 0xDA, 0x6E, 0x7C, 0xA1, 0xC5, 0x8C, 0xA5, 0xB0, 0x09, 0xE0, - 0xCE, 0x3D, 0xDF, 0xF4, 0x9D, 0x3C, 0x97, 0x15, 0xE2, 0x6A, 0xC7, 0x2B, 0x3C, 0x50, 0x93, - 0x23, 0xDB, 0xBA, 0x4A, 0x22, 0x66, 0x44, 0xAC, 0x78, 0xBB, 0x0E, 0x1A, 0x27, 0x43, 0xB5, - 0x71, 0x67, 0xAF, 0xF4, 0xAB, 0x48, 0x46, 0x93, 0x73, 0xD0, 0x42, 0xAB, 0x93, 0x63, 0xE5, - 0x6C, 0x9A, 0xDE, 0x50, 0x24, 0xC0, 0x23, 0x7D, 0x99, 0x79, 0x3F, 0x22, 0x07, 0xE0, 0xC1, - 0x48, 0x56, 0x1B, 0xDF, 0x83, 0x09, 0x12, 0xB4, 0x2D, 0x45, 0x6B, 0xC9, 0xC0, 0x68, 0x85, - 0x99, 0x90, 0x79, 0x96, 0x1A, 0xD7, 0xF5, 0x4D, 0x1F, 0x37, 0x83, 0x40, 0x4A, 0xEC, 0x39, - 0x37, 0xA6, 0x80, 0x92, 0x7D, 0xC5, 0x80, 0xC7, 0xD6, 0x6F, 0xFE, 0x8A, 0x79, 0x89, 0xC6, - 0xB1}; -}; - -class DebugRifKeyset { -public: - // std::uint8_t* PrivateExponent; - static constexpr CryptoPP::byte Exponent1[] = { - 0xCD, 0x9A, 0x61, 0xB0, 0xB8, 0xD5, 0xB4, 0xE4, 0xE4, 0xF6, 0xAB, 0xF7, 0x27, 0xB7, 0x56, - 0x59, 0x6B, 0xB9, 0x11, 0xE7, 0xF4, 0x83, 0xAF, 0xB9, 0x73, 0x99, 0x7F, 0x49, 0xA2, 0x9C, - 0xF0, 0xB5, 0x6D, 0x37, 0x82, 0x14, 0x15, 0xF1, 0x04, 0x8A, 0xD4, 0x8E, 0xEB, 0x2E, 0x1F, - 0xE2, 0x81, 0xA9, 0x62, 0x6E, 0xB1, 0x68, 0x75, 0x62, 0xF3, 0x0F, 0xFE, 0xD4, 0x91, 0x87, - 0x98, 0x78, 0xBF, 0x26, 0xB5, 0x07, 0x58, 0xD0, 0xEE, 0x3F, 0x21, 0xE8, 0xC8, 0x0F, 0x5F, - 0xFA, 0x1C, 0x64, 0x74, 0x49, 0x52, 0xEB, 0xE7, 0xEE, 0xDE, 0xBA, 0x23, 0x26, 0x4A, 0xF6, - 0x9C, 0x1A, 0x09, 0x3F, 0xB9, 0x0B, 0x36, 0x26, 0x1A, 0xBE, 0xA9, 0x76, 0xE6, 0xF2, 0x69, - 0xDE, 0xFF, 0xAF, 0xCC, 0x0C, 0x9A, 0x66, 0x03, 0x86, 0x0A, 0x1F, 0x49, 0xA4, 0x10, 0xB6, - 0xBC, 0xC3, 0x7C, 0x88, 0xE8, 0xCE, 0x4B, 0xD9}; - // exponent2 = d mod (q - 1) - static constexpr CryptoPP::byte Exponent2[] = { - 0xB3, 0x73, 0xA3, 0x59, 0xE6, 0x97, 0xC0, 0xAB, 0x3B, 0x68, 0xFC, 0x39, 0xAC, 0xDB, 0x44, - 0xB1, 0xB4, 0x9E, 0x35, 0x4D, 0xBE, 0xC5, 0x36, 0x69, 0x6C, 0x3D, 0xC5, 0xFC, 0xFE, 0x4B, - 0x2F, 0xDC, 0x86, 0x80, 0x46, 0x96, 0x40, 0x1A, 0x0D, 0x6E, 0xFA, 0x8C, 0xE0, 0x47, 0x91, - 0xAC, 0xAD, 0x95, 0x2B, 0x8E, 0x1F, 0xF2, 0x0A, 0x45, 0xF8, 0x29, 0x95, 0x70, 0xC6, 0x88, - 0x5F, 0x71, 0x03, 0x99, 0x79, 0xBC, 0x84, 0x71, 0xBD, 0xE8, 0x84, 0x8C, 0x0E, 0xD4, 0x7B, - 0x30, 0x74, 0x57, 0x1A, 0x95, 0xE7, 0x90, 0x19, 0x8D, 0xAD, 0x8B, 0x4C, 0x4E, 0xC3, 0xE7, - 0x6B, 0x23, 0x86, 0x01, 0xEE, 0x9B, 0xE0, 0x2F, 0x15, 0xA2, 0x2C, 0x4C, 0x39, 0xD3, 0xDF, - 0x9C, 0x39, 0x01, 0xF1, 0x8C, 0x44, 0x4A, 0x15, 0x44, 0xDC, 0x51, 0xF7, 0x22, 0xD7, 0x7F, - 0x41, 0x7F, 0x68, 0xFA, 0xEE, 0x56, 0xE8, 0x05}; - // e - static constexpr CryptoPP::byte PublicExponent[] = {0x00, 0x01, 0x00, 0x01}; - // (InverseQ)(q) = 1 mod p - static constexpr CryptoPP::byte Coefficient[] = { - 0xC0, 0x32, 0x43, 0xD3, 0x8C, 0x3D, 0xB4, 0xD2, 0x48, 0x8C, 0x42, 0x41, 0x24, 0x94, 0x6C, - 0x80, 0xC9, 0xC1, 0x79, 0x36, 0x7F, 0xAC, 0xC3, 0xFF, 0x6A, 0x25, 0xEB, 0x2C, 0xFB, 0xD4, - 0x2B, 0xA0, 0xEB, 0xFE, 0x25, 0xE9, 0xC6, 0x77, 0xCE, 0xFE, 0x2D, 0x23, 0xFE, 0xD0, 0xF4, - 0x0F, 0xD9, 0x7E, 0xD5, 0xA5, 0x7D, 0x1F, 0xC0, 0xE8, 0xE8, 0xEC, 0x80, 0x5B, 0xC7, 0xFD, - 0xE2, 0xBD, 0x94, 0xA6, 0x2B, 0xDD, 0x6A, 0x60, 0x45, 0x54, 0xAB, 0xCA, 0x42, 0x9C, 0x6A, - 0x6C, 0xBF, 0x3C, 0x84, 0xF9, 0xA5, 0x0E, 0x63, 0x0C, 0x51, 0x58, 0x62, 0x6D, 0x5A, 0xB7, - 0x3C, 0x3F, 0x49, 0x1A, 0xD0, 0x93, 0xB8, 0x4F, 0x1A, 0x6C, 0x5F, 0xC5, 0xE5, 0xA9, 0x75, - 0xD4, 0x86, 0x9E, 0xDF, 0x87, 0x0F, 0x27, 0xB0, 0x26, 0x78, 0x4E, 0xFB, 0xC1, 0x8A, 0x4A, - 0x24, 0x3F, 0x7F, 0x8F, 0x9A, 0x12, 0x51, 0xCB}; - // n = p * q - static constexpr CryptoPP::byte Modulus[] = { - 0xC2, 0xD2, 0x44, 0xBC, 0xDD, 0x84, 0x3F, 0xD9, 0xC5, 0x22, 0xAF, 0xF7, 0xFC, 0x88, 0x8A, - 0x33, 0x80, 0xED, 0x8E, 0xE2, 0xCC, 0x81, 0xF7, 0xEC, 0xF8, 0x1C, 0x79, 0xBF, 0x02, 0xBB, - 0x12, 0x8E, 0x61, 0x68, 0x29, 0x1B, 0x15, 0xB6, 0x5E, 0xC6, 0xF8, 0xBF, 0x5A, 0xE0, 0x3B, - 0x6A, 0x6C, 0xD9, 0xD6, 0xF5, 0x75, 0xAB, 0xA0, 0x6F, 0x34, 0x81, 0x34, 0x9A, 0x5B, 0xAD, - 0xED, 0x31, 0xE3, 0xC6, 0xEA, 0x1A, 0xD1, 0x13, 0x22, 0xBB, 0xB3, 0xDA, 0xB3, 0xB2, 0x53, - 0xBD, 0x45, 0x79, 0x87, 0xAD, 0x0A, 0x01, 0x72, 0x18, 0x10, 0x29, 0x49, 0xF4, 0x41, 0x7F, - 0xD6, 0x47, 0x0C, 0x72, 0x92, 0x9E, 0xE9, 0xBB, 0x95, 0xA9, 0x5D, 0x79, 0xEB, 0xE4, 0x30, - 0x76, 0x90, 0x45, 0x4B, 0x9D, 0x9C, 0xCF, 0x92, 0x03, 0x60, 0x8C, 0x4B, 0x6C, 0xB3, 0x7A, - 0x3A, 0x05, 0x39, 0xA0, 0x66, 0xA9, 0x35, 0xCF, 0xB9, 0xFA, 0xAD, 0x9C, 0xAB, 0xEB, 0xE4, - 0x6A, 0x8C, 0xE9, 0x3B, 0xCC, 0x72, 0x12, 0x62, 0x63, 0xBD, 0x80, 0xC4, 0xEE, 0x37, 0x2B, - 0x32, 0x03, 0xA3, 0x09, 0xF7, 0xA0, 0x61, 0x57, 0xAD, 0x0D, 0xCF, 0x15, 0x98, 0x9E, 0x4E, - 0x49, 0xF8, 0xB5, 0xA3, 0x5C, 0x27, 0xEE, 0x45, 0x04, 0xEA, 0xE4, 0x4B, 0xBC, 0x8F, 0x87, - 0xED, 0x19, 0x1E, 0x46, 0x75, 0x63, 0xC4, 0x5B, 0xD5, 0xBC, 0x09, 0x2F, 0x02, 0x73, 0x19, - 0x3C, 0x58, 0x55, 0x49, 0x66, 0x4C, 0x11, 0xEC, 0x0F, 0x09, 0xFA, 0xA5, 0x56, 0x0A, 0x5A, - 0x63, 0x56, 0xAD, 0xA0, 0x0D, 0x86, 0x08, 0xC1, 0xE6, 0xB6, 0x13, 0x22, 0x49, 0x2F, 0x7C, - 0xDB, 0x4C, 0x56, 0x97, 0x0E, 0xC2, 0xD9, 0x2E, 0x87, 0xBC, 0x0E, 0x67, 0xC0, 0x1B, 0x58, - 0xBC, 0x64, 0x2B, 0xC2, 0x6E, 0xE2, 0x93, 0x2E, 0xB5, 0x6B, 0x70, 0xA4, 0x42, 0x9F, 0x64, - 0xC1}; - // p - static constexpr CryptoPP::byte Prime1[] = { - 0xE5, 0x62, 0xE1, 0x7F, 0x9F, 0x86, 0x08, 0xE2, 0x61, 0xD3, 0xD0, 0x42, 0xE2, 0xC4, 0xB6, - 0xA8, 0x51, 0x09, 0x19, 0x14, 0xA4, 0x3A, 0x11, 0x4C, 0x33, 0xA5, 0x9C, 0x01, 0x5E, 0x34, - 0xB6, 0x3F, 0x02, 0x1A, 0xCA, 0x47, 0xF1, 0x4F, 0x3B, 0x35, 0x2A, 0x07, 0x20, 0xEC, 0xD8, - 0xC1, 0x15, 0xD9, 0xCA, 0x03, 0x4F, 0xB8, 0xE8, 0x09, 0x73, 0x3F, 0x85, 0xB7, 0x41, 0xD5, - 0x51, 0x3E, 0x7B, 0xE3, 0x53, 0x2B, 0x48, 0x8B, 0x8E, 0xCB, 0xBA, 0xF7, 0xE0, 0x60, 0xF5, - 0x35, 0x0E, 0x6F, 0xB0, 0xD9, 0x2A, 0x99, 0xD0, 0xFF, 0x60, 0x14, 0xED, 0x40, 0xEA, 0xF8, - 0xD7, 0x0B, 0xC3, 0x8D, 0x8C, 0xE8, 0x81, 0xB3, 0x75, 0x93, 0x15, 0xB3, 0x7D, 0xF6, 0x39, - 0x60, 0x1A, 0x00, 0xE7, 0xC3, 0x27, 0xAD, 0xA4, 0x33, 0xD5, 0x3E, 0xA4, 0x35, 0x48, 0x6F, - 0x22, 0xEF, 0x5D, 0xDD, 0x7D, 0x7B, 0x61, 0x05}; - // q - static constexpr CryptoPP::byte Prime2[] = { - 0xD9, 0x6C, 0xC2, 0x0C, 0xF7, 0xAE, 0xD1, 0xF3, 0x3B, 0x3B, 0x49, 0x1E, 0x9F, 0x12, 0x9C, - 0xA1, 0x78, 0x1F, 0x35, 0x1D, 0x98, 0x26, 0x13, 0x71, 0xF9, 0x09, 0xFD, 0xF0, 0xAD, 0x38, - 0x55, 0xB7, 0xEE, 0x61, 0x04, 0x72, 0x51, 0x87, 0x2E, 0x05, 0x84, 0xB1, 0x1D, 0x0C, 0x0D, - 0xDB, 0xD4, 0x25, 0x3E, 0x26, 0xED, 0xEA, 0xB8, 0xF7, 0x49, 0xFE, 0xA2, 0x94, 0xE6, 0xF2, - 0x08, 0x92, 0xA7, 0x85, 0xF5, 0x30, 0xB9, 0x84, 0x22, 0xBF, 0xCA, 0xF0, 0x5F, 0xCB, 0x31, - 0x20, 0x34, 0x49, 0x16, 0x76, 0x34, 0xCC, 0x7A, 0xCB, 0x96, 0xFE, 0x78, 0x7A, 0x41, 0xFE, - 0x9A, 0xA2, 0x23, 0xF7, 0x68, 0x80, 0xD6, 0xCE, 0x4A, 0x78, 0xA5, 0xB7, 0x05, 0x77, 0x81, - 0x1F, 0xDE, 0x5E, 0xA8, 0x6E, 0x3E, 0x87, 0xEC, 0x44, 0xD2, 0x69, 0xC6, 0x54, 0x91, 0x6B, - 0x5E, 0x13, 0x8A, 0x03, 0x87, 0x05, 0x31, 0x8D}; - static constexpr CryptoPP::byte PrivateExponent[] = { - 0x01, 0x61, 0xAD, 0xD8, 0x9C, 0x06, 0x89, 0xD0, 0x60, 0xC8, 0x41, 0xF0, 0xB3, 0x83, 0x01, - 0x5D, 0xE3, 0xA2, 0x6B, 0xA2, 0xBA, 0x9A, 0x0A, 0x58, 0xCD, 0x1A, 0xA0, 0x97, 0x64, 0xEC, - 0xD0, 0x31, 0x1F, 0xCA, 0x36, 0x0E, 0x69, 0xDD, 0x40, 0xF7, 0x4E, 0xC0, 0xC6, 0xA3, 0x73, - 0xF0, 0x69, 0x84, 0xB2, 0xF4, 0x4B, 0x29, 0x14, 0x2A, 0x6D, 0xB8, 0x23, 0xD8, 0x1B, 0x61, - 0xD4, 0x9E, 0x87, 0xB3, 0xBB, 0xA9, 0xC4, 0x85, 0x4A, 0xF8, 0x03, 0x4A, 0xBF, 0xFE, 0xF9, - 0xFE, 0x8B, 0xDD, 0x54, 0x83, 0xBA, 0xE0, 0x2F, 0x3F, 0xB1, 0xEF, 0xA5, 0x05, 0x5D, 0x28, - 0x8B, 0xAB, 0xB5, 0xD0, 0x23, 0x2F, 0x8A, 0xCF, 0x48, 0x7C, 0xAA, 0xBB, 0xC8, 0x5B, 0x36, - 0x27, 0xC5, 0x16, 0xA4, 0xB6, 0x61, 0xAC, 0x0C, 0x28, 0x47, 0x79, 0x3F, 0x38, 0xAE, 0x5E, - 0x25, 0xC6, 0xAF, 0x35, 0xAE, 0xBC, 0xB0, 0xF3, 0xBC, 0xBD, 0xFD, 0xA4, 0x87, 0x0D, 0x14, - 0x3D, 0x90, 0xE4, 0xDE, 0x5D, 0x1D, 0x46, 0x81, 0xF1, 0x28, 0x6D, 0x2F, 0x2C, 0x5E, 0x97, - 0x2D, 0x89, 0x2A, 0x51, 0x72, 0x3C, 0x20, 0x02, 0x59, 0xB1, 0x98, 0x93, 0x05, 0x1E, 0x3F, - 0xA1, 0x8A, 0x69, 0x30, 0x0E, 0x70, 0x84, 0x8B, 0xAE, 0x97, 0xA1, 0x08, 0x95, 0x63, 0x4C, - 0xC7, 0xE8, 0x5D, 0x59, 0xCA, 0x78, 0x2A, 0x23, 0x87, 0xAC, 0x6F, 0x04, 0x33, 0xB1, 0x61, - 0xB9, 0xF0, 0x95, 0xDA, 0x33, 0xCC, 0xE0, 0x4C, 0x82, 0x68, 0x82, 0x14, 0x51, 0xBE, 0x49, - 0x1C, 0x58, 0xA2, 0x8B, 0x05, 0x4E, 0x98, 0x37, 0xEB, 0x94, 0x0B, 0x01, 0x22, 0xDC, 0xB3, - 0x19, 0xCA, 0x77, 0xA6, 0x6E, 0x97, 0xFF, 0x8A, 0x53, 0x5A, 0xC5, 0x24, 0xE4, 0xAF, 0x6E, - 0xA8, 0x2B, 0x53, 0xA4, 0xBE, 0x96, 0xA5, 0x7B, 0xCE, 0x22, 0x56, 0xA3, 0xF1, 0xCF, 0x14, - 0xA5}; -}; - -class PkgDerivedKey3Keyset { -public: - // std::uint8_t* PrivateExponent; - static constexpr CryptoPP::byte Exponent1[] = { - 0x52, 0xCC, 0x2D, 0xA0, 0x9C, 0x9E, 0x75, 0xE7, 0x28, 0xEE, 0x3D, 0xDE, 0xE3, 0x45, 0xD1, - 0x4F, 0x94, 0x1C, 0xCC, 0xC8, 0x87, 0x29, 0x45, 0x3B, 0x8D, 0x6E, 0xAB, 0x6E, 0x2A, 0xA7, - 0xC7, 0x15, 0x43, 0xA3, 0x04, 0x8F, 0x90, 0x5F, 0xEB, 0xF3, 0x38, 0x4A, 0x77, 0xFA, 0x36, - 0xB7, 0x15, 0x76, 0xB6, 0x01, 0x1A, 0x8E, 0x25, 0x87, 0x82, 0xF1, 0x55, 0xD8, 0xC6, 0x43, - 0x2A, 0xC0, 0xE5, 0x98, 0xC9, 0x32, 0xD1, 0x94, 0x6F, 0xD9, 0x01, 0xBA, 0x06, 0x81, 0xE0, - 0x6D, 0x88, 0xF2, 0x24, 0x2A, 0x25, 0x01, 0x64, 0x5C, 0xBF, 0xF2, 0xD9, 0x99, 0x67, 0x3E, - 0xF6, 0x72, 0xEE, 0xE4, 0xE2, 0x33, 0x5C, 0xF8, 0x00, 0x40, 0xE3, 0x2A, 0x9A, 0xF4, 0x3D, - 0x22, 0x86, 0x44, 0x3C, 0xFB, 0x0A, 0xA5, 0x7C, 0x3F, 0xCC, 0xF5, 0xF1, 0x16, 0xC4, 0xAC, - 0x88, 0xB4, 0xDE, 0x62, 0x94, 0x92, 0x6A, 0x13}; - // exponent2 = d mod (q - 1) - static constexpr CryptoPP::byte Exponent2[] = { - 0x7C, 0x9D, 0xAD, 0x39, 0xE0, 0xD5, 0x60, 0x14, 0x94, 0x48, 0x19, 0x7F, 0x88, 0x95, 0xD5, - 0x8B, 0x80, 0xAD, 0x85, 0x8A, 0x4B, 0x77, 0x37, 0x85, 0xD0, 0x77, 0xBB, 0xBF, 0x89, 0x71, - 0x4A, 0x72, 0xCB, 0x72, 0x68, 0x38, 0xEC, 0x02, 0xC6, 0x7D, 0xC6, 0x44, 0x06, 0x33, 0x51, - 0x1C, 0xC0, 0xFF, 0x95, 0x8F, 0x0D, 0x75, 0xDC, 0x25, 0xBB, 0x0B, 0x73, 0x91, 0xA9, 0x6D, - 0x42, 0xD8, 0x03, 0xB7, 0x68, 0xD4, 0x1E, 0x75, 0x62, 0xA3, 0x70, 0x35, 0x79, 0x78, 0x00, - 0xC8, 0xF5, 0xEF, 0x15, 0xB9, 0xFC, 0x4E, 0x47, 0x5A, 0xC8, 0x70, 0x70, 0x5B, 0x52, 0x98, - 0xC0, 0xC2, 0x58, 0x4A, 0x70, 0x96, 0xCC, 0xB8, 0x10, 0xE1, 0x2F, 0x78, 0x8B, 0x2B, 0xA1, - 0x7F, 0xF9, 0xAC, 0xDE, 0xF0, 0xBB, 0x2B, 0xE2, 0x66, 0xE3, 0x22, 0x92, 0x31, 0x21, 0x57, - 0x92, 0xC4, 0xB8, 0xF2, 0x3E, 0x76, 0x20, 0x37}; - // e - static constexpr CryptoPP::byte PublicExponent[] = {0, 1, 0, 1}; - // (InverseQ)(q) = 1 mod p - static constexpr CryptoPP::byte Coefficient[] = { - 0x45, 0x97, 0x55, 0xD4, 0x22, 0x08, 0x5E, 0xF3, 0x5C, 0xB4, 0x05, 0x7A, 0xFD, 0xAA, 0x42, - 0x42, 0xAD, 0x9A, 0x8C, 0xA0, 0x6C, 0xBB, 0x1D, 0x68, 0x54, 0x54, 0x6E, 0x3E, 0x32, 0xE3, - 0x53, 0x73, 0x76, 0xF1, 0x3E, 0x01, 0xEA, 0xD3, 0xCF, 0xEB, 0xEB, 0x23, 0x3E, 0xC0, 0xBE, - 0xCE, 0xEC, 0x2C, 0x89, 0x5F, 0xA8, 0x27, 0x3A, 0x4C, 0xB7, 0xE6, 0x74, 0xBC, 0x45, 0x4C, - 0x26, 0xC8, 0x25, 0xFF, 0x34, 0x63, 0x25, 0x37, 0xE1, 0x48, 0x10, 0xC1, 0x93, 0xA6, 0xAF, - 0xEB, 0xBA, 0xE3, 0xA2, 0xF1, 0x3D, 0xEF, 0x63, 0xD8, 0xF4, 0xFD, 0xD3, 0xEE, 0xE2, 0x5D, - 0xE9, 0x33, 0xCC, 0xAD, 0xBA, 0x75, 0x5C, 0x85, 0xAF, 0xCE, 0xA9, 0x3D, 0xD1, 0xA2, 0x17, - 0xF3, 0xF6, 0x98, 0xB3, 0x50, 0x8E, 0x5E, 0xF6, 0xEB, 0x02, 0x8E, 0xA1, 0x62, 0xA7, 0xD6, - 0x2C, 0xEC, 0x91, 0xFF, 0x15, 0x40, 0xD2, 0xE3}; - // n = p * q - static constexpr CryptoPP::byte Modulus[] = { - 0xd2, 0x12, 0xfc, 0x33, 0x5f, 0x6d, 0xdb, 0x83, 0x16, 0x09, 0x62, 0x8b, 0x03, 0x56, 0x27, - 0x37, 0x82, 0xd4, 0x77, 0x85, 0x35, 0x29, 0x39, 0x2d, 0x52, 0x6b, 0x8c, 0x4c, 0x8c, 0xfb, - 0x06, 0xc1, 0x84, 0x5b, 0xe7, 0xd4, 0xf7, 0xbc, 0xd2, 0x4e, 0x62, 0x45, 0xcd, 0x2a, 0xbb, - 0xd7, 0x77, 0x76, 0x45, 0x36, 0x55, 0x27, 0x3f, 0xb3, 0xf5, 0xf9, 0x8e, 0xda, 0x4b, 0xef, - 0xaa, 0x59, 0xae, 0xb3, 0x9b, 0xea, 0x54, 0x98, 0xd2, 0x06, 0x32, 0x6a, 0x58, 0x31, 0x2a, - 0xe0, 0xd4, 0x4f, 0x90, 0xb5, 0x0a, 0x7d, 0xec, 0xf4, 0x3a, 0x9c, 0x52, 0x67, 0x2d, 0x99, - 0x31, 0x8e, 0x0c, 0x43, 0xe6, 0x82, 0xfe, 0x07, 0x46, 0xe1, 0x2e, 0x50, 0xd4, 0x1f, 0x2d, - 0x2f, 0x7e, 0xd9, 0x08, 0xba, 0x06, 0xb3, 0xbf, 0x2e, 0x20, 0x3f, 0x4e, 0x3f, 0xfe, 0x44, - 0xff, 0xaa, 0x50, 0x43, 0x57, 0x91, 0x69, 0x94, 0x49, 0x15, 0x82, 0x82, 0xe4, 0x0f, 0x4c, - 0x8d, 0x9d, 0x2c, 0xc9, 0x5b, 0x1d, 0x64, 0xbf, 0x88, 0x8b, 0xd4, 0xc5, 0x94, 0xe7, 0x65, - 0x47, 0x84, 0x1e, 0xe5, 0x79, 0x10, 0xfb, 0x98, 0x93, 0x47, 0xb9, 0x7d, 0x85, 0x12, 0xa6, - 0x40, 0x98, 0x2c, 0xf7, 0x92, 0xbc, 0x95, 0x19, 0x32, 0xed, 0xe8, 0x90, 0x56, 0x0d, 0x65, - 0xc1, 0xaa, 0x78, 0xc6, 0x2e, 0x54, 0xfd, 0x5f, 0x54, 0xa1, 0xf6, 0x7e, 0xe5, 0xe0, 0x5f, - 0x61, 0xc1, 0x20, 0xb4, 0xb9, 0xb4, 0x33, 0x08, 0x70, 0xe4, 0xdf, 0x89, 0x56, 0xed, 0x01, - 0x29, 0x46, 0x77, 0x5f, 0x8c, 0xb8, 0xa9, 0xf5, 0x1e, 0x2e, 0xb3, 0xb9, 0xbf, 0xe0, 0x09, - 0xb7, 0x8d, 0x28, 0xd4, 0xa6, 0xc3, 0xb8, 0x1e, 0x1f, 0x07, 0xeb, 0xb4, 0x12, 0x0b, 0x95, - 0xb8, 0x85, 0x30, 0xfd, 0xdc, 0x39, 0x13, 0xd0, 0x7c, 0xdc, 0x8f, 0xed, 0xf9, 0xc9, 0xa3, - 0xc1}; - // p - static constexpr CryptoPP::byte Prime1[] = { - 0xF9, 0x67, 0xAD, 0x99, 0x12, 0x31, 0x0C, 0x56, 0xA2, 0x2E, 0x16, 0x1C, 0x46, 0xB3, 0x4D, - 0x5B, 0x43, 0xBE, 0x42, 0xA2, 0xF6, 0x86, 0x96, 0x80, 0x42, 0xC3, 0xC7, 0x3F, 0xC3, 0x42, - 0xF5, 0x87, 0x49, 0x33, 0x9F, 0x07, 0x5D, 0x6E, 0x2C, 0x04, 0xFD, 0xE3, 0xE1, 0xB2, 0xAE, - 0x0A, 0x0C, 0xF0, 0xC7, 0xA6, 0x1C, 0xA1, 0x63, 0x50, 0xC8, 0x09, 0x9C, 0x51, 0x24, 0x52, - 0x6C, 0x5E, 0x5E, 0xBD, 0x1E, 0x27, 0x06, 0xBB, 0xBC, 0x9E, 0x94, 0xE1, 0x35, 0xD4, 0x6D, - 0xB3, 0xCB, 0x3C, 0x68, 0xDD, 0x68, 0xB3, 0xFE, 0x6C, 0xCB, 0x8D, 0x82, 0x20, 0x76, 0x23, - 0x63, 0xB7, 0xE9, 0x68, 0x10, 0x01, 0x4E, 0xDC, 0xBA, 0x27, 0x5D, 0x01, 0xC1, 0x2D, 0x80, - 0x5E, 0x2B, 0xAF, 0x82, 0x6B, 0xD8, 0x84, 0xB6, 0x10, 0x52, 0x86, 0xA7, 0x89, 0x8E, 0xAE, - 0x9A, 0xE2, 0x89, 0xC6, 0xF7, 0xD5, 0x87, 0xFB}; - // q - static constexpr CryptoPP::byte Prime2[] = { - 0xD7, 0xA1, 0x0F, 0x9A, 0x8B, 0xF2, 0xC9, 0x11, 0x95, 0x32, 0x9A, 0x8C, 0xF0, 0xD9, 0x40, - 0x47, 0xF5, 0x68, 0xA0, 0x0D, 0xBD, 0xC1, 0xFC, 0x43, 0x2F, 0x65, 0xF9, 0xC3, 0x61, 0x0F, - 0x25, 0x77, 0x54, 0xAD, 0xD7, 0x58, 0xAC, 0x84, 0x40, 0x60, 0x8D, 0x3F, 0xF3, 0x65, 0x89, - 0x75, 0xB5, 0xC6, 0x2C, 0x51, 0x1A, 0x2F, 0x1F, 0x22, 0xE4, 0x43, 0x11, 0x54, 0xBE, 0xC9, - 0xB4, 0xC7, 0xB5, 0x1B, 0x05, 0x0B, 0xBC, 0x56, 0x9A, 0xCD, 0x4A, 0xD9, 0x73, 0x68, 0x5E, - 0x5C, 0xFB, 0x92, 0xB7, 0x8B, 0x0D, 0xFF, 0xF5, 0x07, 0xCA, 0xB4, 0xC8, 0x9B, 0x96, 0x3C, - 0x07, 0x9E, 0x3E, 0x6B, 0x2A, 0x11, 0xF2, 0x8A, 0xB1, 0x8A, 0xD7, 0x2E, 0x1B, 0xA5, 0x53, - 0x24, 0x06, 0xED, 0x50, 0xB8, 0x90, 0x67, 0xB1, 0xE2, 0x41, 0xC6, 0x92, 0x01, 0xEE, 0x10, - 0xF0, 0x61, 0xBB, 0xFB, 0xB2, 0x7D, 0x4A, 0x73}; - static constexpr CryptoPP::byte PrivateExponent[] = { - 0x32, 0xD9, 0x03, 0x90, 0x8F, 0xBD, 0xB0, 0x8F, 0x57, 0x2B, 0x28, 0x5E, 0x0B, 0x8D, 0xB3, - 0xEA, 0x5C, 0xD1, 0x7E, 0xA8, 0x90, 0x88, 0x8C, 0xDD, 0x6A, 0x80, 0xBB, 0xB1, 0xDF, 0xC1, - 0xF7, 0x0D, 0xAA, 0x32, 0xF0, 0xB7, 0x7C, 0xCB, 0x88, 0x80, 0x0E, 0x8B, 0x64, 0xB0, 0xBE, - 0x4C, 0xD6, 0x0E, 0x9B, 0x8C, 0x1E, 0x2A, 0x64, 0xE1, 0xF3, 0x5C, 0xD7, 0x76, 0x01, 0x41, - 0x5E, 0x93, 0x5C, 0x94, 0xFE, 0xDD, 0x46, 0x62, 0xC3, 0x1B, 0x5A, 0xE2, 0xA0, 0xBC, 0x2D, - 0xEB, 0xC3, 0x98, 0x0A, 0xA7, 0xB7, 0x85, 0x69, 0x70, 0x68, 0x2B, 0x64, 0x4A, 0xB3, 0x1F, - 0xCC, 0x7D, 0xDC, 0x7C, 0x26, 0xF4, 0x77, 0xF6, 0x5C, 0xF2, 0xAE, 0x5A, 0x44, 0x2D, 0xD3, - 0xAB, 0x16, 0x62, 0x04, 0x19, 0xBA, 0xFB, 0x90, 0xFF, 0xE2, 0x30, 0x50, 0x89, 0x6E, 0xCB, - 0x56, 0xB2, 0xEB, 0xC0, 0x91, 0x16, 0x92, 0x5E, 0x30, 0x8E, 0xAE, 0xC7, 0x94, 0x5D, 0xFD, - 0x35, 0xE1, 0x20, 0xF8, 0xAD, 0x3E, 0xBC, 0x08, 0xBF, 0xC0, 0x36, 0x74, 0x9F, 0xD5, 0xBB, - 0x52, 0x08, 0xFD, 0x06, 0x66, 0xF3, 0x7A, 0xB3, 0x04, 0xF4, 0x75, 0x29, 0x5D, 0xE9, 0x5F, - 0xAA, 0x10, 0x30, 0xB2, 0x0F, 0x5A, 0x1A, 0xC1, 0x2A, 0xB3, 0xFE, 0xCB, 0x21, 0xAD, 0x80, - 0xEC, 0x8F, 0x20, 0x09, 0x1C, 0xDB, 0xC5, 0x58, 0x94, 0xC2, 0x9C, 0xC6, 0xCE, 0x82, 0x65, - 0x3E, 0x57, 0x90, 0xBC, 0xA9, 0x8B, 0x06, 0xB4, 0xF0, 0x72, 0xF6, 0x77, 0xDF, 0x98, 0x64, - 0xF1, 0xEC, 0xFE, 0x37, 0x2D, 0xBC, 0xAE, 0x8C, 0x08, 0x81, 0x1F, 0xC3, 0xC9, 0x89, 0x1A, - 0xC7, 0x42, 0x82, 0x4B, 0x2E, 0xDC, 0x8E, 0x8D, 0x73, 0xCE, 0xB1, 0xCC, 0x01, 0xD9, 0x08, - 0x70, 0x87, 0x3C, 0x44, 0x08, 0xEC, 0x49, 0x8F, 0x81, 0x5A, 0xE2, 0x40, 0xFF, 0x77, 0xFC, - 0x0D}; -}; \ No newline at end of file diff --git a/src/core/file_format/pkg.h b/src/core/file_format/pkg.h deleted file mode 100644 index a488a2df8..000000000 --- a/src/core/file_format/pkg.h +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include "common/endian.h" -#include "core/crypto/crypto.h" -#include "pfs.h" -#include "trp.h" - -struct PKGHeader { - u32_be magic; // Magic - u32_be pkg_type; - u32_be pkg_0x8; // unknown field - u32_be pkg_file_count; - u32_be pkg_table_entry_count; - u16_be pkg_sc_entry_count; - u16_be pkg_table_entry_count_2; // same as pkg_entry_count - u32_be pkg_table_entry_offset; // file table offset - u32_be pkg_sc_entry_data_size; - u64_be pkg_body_offset; // offset of PKG entries - u64_be pkg_body_size; // length of all PKG entries - u64_be pkg_content_offset; - u64_be pkg_content_size; - u8 pkg_content_id[0x24]; // packages' content ID as a 36-byte string - u8 pkg_padding[0xC]; // padding - u32_be pkg_drm_type; // DRM type - u32_be pkg_content_type; // Content type - u32_be pkg_content_flags; // Content flags - u32_be pkg_promote_size; - u32_be pkg_version_date; - u32_be pkg_version_hash; - u32_be pkg_0x088; - u32_be pkg_0x08C; - u32_be pkg_0x090; - u32_be pkg_0x094; - u32_be pkg_iro_tag; - u32_be pkg_drm_type_version; - - u8 pkg_zeroes_1[0x60]; - - /* Digest table */ - u8 digest_entries1[0x20]; // sha256 digest for main entry 1 - u8 digest_entries2[0x20]; // sha256 digest for main entry 2 - u8 digest_table_digest[0x20]; // sha256 digest for digest table - u8 digest_body_digest[0x20]; // sha256 digest for main table - - u8 pkg_zeroes_2[0x280]; - - u32_be pkg_0x400; - - u32_be pfs_image_count; // count of PFS images - u64_be pfs_image_flags; // PFS flags - u64_be pfs_image_offset; // offset to start of external PFS image - u64_be pfs_image_size; // size of external PFS image - u64_be mount_image_offset; - u64_be mount_image_size; - u64_be pkg_size; - u32_be pfs_signed_size; - u32_be pfs_cache_size; - u8 pfs_image_digest[0x20]; - u8 pfs_signed_digest[0x20]; - u64_be pfs_split_size_nth_0; - u64_be pfs_split_size_nth_1; - - u8 pkg_zeroes_3[0xB50]; - - u8 pkg_digest[0x20]; -}; - -enum class PKGContentFlag { - FIRST_PATCH = 0x100000, - PATCHGO = 0x200000, - REMASTER = 0x400000, - PS_CLOUD = 0x800000, - GD_AC = 0x2000000, - NON_GAME = 0x4000000, - UNKNOWN_0x8000000 = 0x8000000, - SUBSEQUENT_PATCH = 0x40000000, - DELTA_PATCH = 0x41000000, - CUMULATIVE_PATCH = 0x60000000 -}; - -struct PKGEntry { - u32_be id; // File ID, useful for files without a filename entry - u32_be filename_offset; // Offset into the filenames table (ID 0x200) where this file's name is - // located - u32_be flags1; // Flags including encrypted flag, etc - u32_be flags2; // Flags including encryption key index, etc - u32_be offset; // Offset into PKG to find the file - u32_be size; // Size of the file - u64_be padding; // blank padding -}; -static_assert(sizeof(PKGEntry) == 32); - -class PKG { -public: - PKG(); - ~PKG(); - - bool Open(const std::filesystem::path& filepath, std::string& failreason); - void ExtractFiles(const int index); - bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract, - std::string& failreason); - - std::vector sfo; - - u32 GetNumberOfFiles() { - return fsTable.size(); - } - - u64 GetPkgSize() { - return pkgSize; - } - - std::string GetPkgFlags() { - return pkgFlags; - } - - std::string_view GetTitleID() { - return std::string_view(pkgTitleID, 9); - } - - PKGHeader GetPkgHeader() { - return pkgheader; - } - - static bool isFlagSet(u32_be variable, PKGContentFlag flag) { - return (variable) & static_cast(flag); - } - - static constexpr std::array, 10> flagNames = { - {{PKGContentFlag::FIRST_PATCH, "FIRST_PATCH"}, - {PKGContentFlag::PATCHGO, "PATCHGO"}, - {PKGContentFlag::REMASTER, "REMASTER"}, - {PKGContentFlag::PS_CLOUD, "PS_CLOUD"}, - {PKGContentFlag::GD_AC, "GD_AC"}, - {PKGContentFlag::NON_GAME, "NON_GAME"}, - {PKGContentFlag::UNKNOWN_0x8000000, "UNKNOWN_0x8000000"}, - {PKGContentFlag::SUBSEQUENT_PATCH, "SUBSEQUENT_PATCH"}, - {PKGContentFlag::DELTA_PATCH, "DELTA_PATCH"}, - {PKGContentFlag::CUMULATIVE_PATCH, "CUMULATIVE_PATCH"}}}; - -private: - Crypto crypto; - TRP trp; - u64 pkgSize = 0; - char pkgTitleID[9]; - PKGHeader pkgheader; - std::string pkgFlags; - - std::unordered_map extractPaths; - std::vector fsTable; - std::vector iNodeBuf; - std::vector sectorMap; - u64 pfsc_offset; - - std::array dk3_; - std::array ivKey; - std::array imgKey; - std::array ekpfsKey; - std::array dataKey; - std::array tweakKey; - std::vector decNp; - - std::filesystem::path pkgpath; - std::filesystem::path current_dir; - std::filesystem::path extract_path; -}; diff --git a/src/core/file_format/pkg_type.cpp b/src/core/file_format/pkg_type.cpp deleted file mode 100644 index 464f0b993..000000000 --- a/src/core/file_format/pkg_type.cpp +++ /dev/null @@ -1,638 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include "pkg_type.h" - -struct PkgEntryValue { - u32 type; - std::string_view name; - - operator u32() const noexcept { - return type; - } -}; - -constexpr static std::array PkgEntries = {{ - {0x0001, "digests"}, - {0x0010, "entry_keys"}, - {0x0020, "image_key"}, - {0x0080, "general_digests"}, - {0x0100, "metas"}, - {0x0200, "entry_names"}, - {0x0400, "license.dat"}, - {0x0401, "license.info"}, - {0x0402, "nptitle.dat"}, - {0x0403, "npbind.dat"}, - {0x0404, "selfinfo.dat"}, - {0x0406, "imageinfo.dat"}, - {0x0407, "target-deltainfo.dat"}, - {0x0408, "origin-deltainfo.dat"}, - {0x0409, "psreserved.dat"}, - {0x1000, "param.sfo"}, - {0x1001, "playgo-chunk.dat"}, - {0x1002, "playgo-chunk.sha"}, - {0x1003, "playgo-manifest.xml"}, - {0x1004, "pronunciation.xml"}, - {0x1005, "pronunciation.sig"}, - {0x1006, "pic1.png"}, - {0x1007, "pubtoolinfo.dat"}, - {0x1008, "app/playgo-chunk.dat"}, - {0x1009, "app/playgo-chunk.sha"}, - {0x100A, "app/playgo-manifest.xml"}, - {0x100B, "shareparam.json"}, - {0x100C, "shareoverlayimage.png"}, - {0x100D, "save_data.png"}, - {0x100E, "shareprivacyguardimage.png"}, - {0x1200, "icon0.png"}, - {0x1201, "icon0_00.png"}, - {0x1202, "icon0_01.png"}, - {0x1203, "icon0_02.png"}, - {0x1204, "icon0_03.png"}, - {0x1205, "icon0_04.png"}, - {0x1206, "icon0_05.png"}, - {0x1207, "icon0_06.png"}, - {0x1208, "icon0_07.png"}, - {0x1209, "icon0_08.png"}, - {0x120A, "icon0_09.png"}, - {0x120B, "icon0_10.png"}, - {0x120C, "icon0_11.png"}, - {0x120D, "icon0_12.png"}, - {0x120E, "icon0_13.png"}, - {0x120F, "icon0_14.png"}, - {0x1210, "icon0_15.png"}, - {0x1211, "icon0_16.png"}, - {0x1212, "icon0_17.png"}, - {0x1213, "icon0_18.png"}, - {0x1214, "icon0_19.png"}, - {0x1215, "icon0_20.png"}, - {0x1216, "icon0_21.png"}, - {0x1217, "icon0_22.png"}, - {0x1218, "icon0_23.png"}, - {0x1219, "icon0_24.png"}, - {0x121A, "icon0_25.png"}, - {0x121B, "icon0_26.png"}, - {0x121C, "icon0_27.png"}, - {0x121D, "icon0_28.png"}, - {0x121E, "icon0_29.png"}, - {0x121F, "icon0_30.png"}, - {0x1220, "pic0.png"}, - {0x1240, "snd0.at9"}, - {0x1241, "pic1_00.png"}, - {0x1242, "pic1_01.png"}, - {0x1243, "pic1_02.png"}, - {0x1244, "pic1_03.png"}, - {0x1245, "pic1_04.png"}, - {0x1246, "pic1_05.png"}, - {0x1247, "pic1_06.png"}, - {0x1248, "pic1_07.png"}, - {0x1249, "pic1_08.png"}, - {0x124A, "pic1_09.png"}, - {0x124B, "pic1_10.png"}, - {0x124C, "pic1_11.png"}, - {0x124D, "pic1_12.png"}, - {0x124E, "pic1_13.png"}, - {0x124F, "pic1_14.png"}, - {0x1250, "pic1_15.png"}, - {0x1251, "pic1_16.png"}, - {0x1252, "pic1_17.png"}, - {0x1253, "pic1_18.png"}, - {0x1254, "pic1_19.png"}, - {0x1255, "pic1_20.png"}, - {0x1256, "pic1_21.png"}, - {0x1257, "pic1_22.png"}, - {0x1258, "pic1_23.png"}, - {0x1259, "pic1_24.png"}, - {0x125A, "pic1_25.png"}, - {0x125B, "pic1_26.png"}, - {0x125C, "pic1_27.png"}, - {0x125D, "pic1_28.png"}, - {0x125E, "pic1_29.png"}, - {0x125F, "pic1_30.png"}, - {0x1260, "changeinfo/changeinfo.xml"}, - {0x1261, "changeinfo/changeinfo_00.xml"}, - {0x1262, "changeinfo/changeinfo_01.xml"}, - {0x1263, "changeinfo/changeinfo_02.xml"}, - {0x1264, "changeinfo/changeinfo_03.xml"}, - {0x1265, "changeinfo/changeinfo_04.xml"}, - {0x1266, "changeinfo/changeinfo_05.xml"}, - {0x1267, "changeinfo/changeinfo_06.xml"}, - {0x1268, "changeinfo/changeinfo_07.xml"}, - {0x1269, "changeinfo/changeinfo_08.xml"}, - {0x126A, "changeinfo/changeinfo_09.xml"}, - {0x126B, "changeinfo/changeinfo_10.xml"}, - {0x126C, "changeinfo/changeinfo_11.xml"}, - {0x126D, "changeinfo/changeinfo_12.xml"}, - {0x126E, "changeinfo/changeinfo_13.xml"}, - {0x126F, "changeinfo/changeinfo_14.xml"}, - {0x1270, "changeinfo/changeinfo_15.xml"}, - {0x1271, "changeinfo/changeinfo_16.xml"}, - {0x1272, "changeinfo/changeinfo_17.xml"}, - {0x1273, "changeinfo/changeinfo_18.xml"}, - {0x1274, "changeinfo/changeinfo_19.xml"}, - {0x1275, "changeinfo/changeinfo_20.xml"}, - {0x1276, "changeinfo/changeinfo_21.xml"}, - {0x1277, "changeinfo/changeinfo_22.xml"}, - {0x1278, "changeinfo/changeinfo_23.xml"}, - {0x1279, "changeinfo/changeinfo_24.xml"}, - {0x127A, "changeinfo/changeinfo_25.xml"}, - {0x127B, "changeinfo/changeinfo_26.xml"}, - {0x127C, "changeinfo/changeinfo_27.xml"}, - {0x127D, "changeinfo/changeinfo_28.xml"}, - {0x127E, "changeinfo/changeinfo_29.xml"}, - {0x127F, "changeinfo/changeinfo_30.xml"}, - {0x1280, "icon0.dds"}, - {0x1281, "icon0_00.dds"}, - {0x1282, "icon0_01.dds"}, - {0x1283, "icon0_02.dds"}, - {0x1284, "icon0_03.dds"}, - {0x1285, "icon0_04.dds"}, - {0x1286, "icon0_05.dds"}, - {0x1287, "icon0_06.dds"}, - {0x1288, "icon0_07.dds"}, - {0x1289, "icon0_08.dds"}, - {0x128A, "icon0_09.dds"}, - {0x128B, "icon0_10.dds"}, - {0x128C, "icon0_11.dds"}, - {0x128D, "icon0_12.dds"}, - {0x128E, "icon0_13.dds"}, - {0x128F, "icon0_14.dds"}, - {0x1290, "icon0_15.dds"}, - {0x1291, "icon0_16.dds"}, - {0x1292, "icon0_17.dds"}, - {0x1293, "icon0_18.dds"}, - {0x1294, "icon0_19.dds"}, - {0x1295, "icon0_20.dds"}, - {0x1296, "icon0_21.dds"}, - {0x1297, "icon0_22.dds"}, - {0x1298, "icon0_23.dds"}, - {0x1299, "icon0_24.dds"}, - {0x129A, "icon0_25.dds"}, - {0x129B, "icon0_26.dds"}, - {0x129C, "icon0_27.dds"}, - {0x129D, "icon0_28.dds"}, - {0x129E, "icon0_29.dds"}, - {0x129F, "icon0_30.dds"}, - {0x12A0, "pic0.dds"}, - {0x12C0, "pic1.dds"}, - {0x12C1, "pic1_00.dds"}, - {0x12C2, "pic1_01.dds"}, - {0x12C3, "pic1_02.dds"}, - {0x12C4, "pic1_03.dds"}, - {0x12C5, "pic1_04.dds"}, - {0x12C6, "pic1_05.dds"}, - {0x12C7, "pic1_06.dds"}, - {0x12C8, "pic1_07.dds"}, - {0x12C9, "pic1_08.dds"}, - {0x12CA, "pic1_09.dds"}, - {0x12CB, "pic1_10.dds"}, - {0x12CC, "pic1_11.dds"}, - {0x12CD, "pic1_12.dds"}, - {0x12CE, "pic1_13.dds"}, - {0x12CF, "pic1_14.dds"}, - {0x12D0, "pic1_15.dds"}, - {0x12D1, "pic1_16.dds"}, - {0x12D2, "pic1_17.dds"}, - {0x12D3, "pic1_18.dds"}, - {0x12D4, "pic1_19.dds"}, - {0x12D5, "pic1_20.dds"}, - {0x12D6, "pic1_21.dds"}, - {0x12D7, "pic1_22.dds"}, - {0x12D8, "pic1_23.dds"}, - {0x12D9, "pic1_24.dds"}, - {0x12DA, "pic1_25.dds"}, - {0x12DB, "pic1_26.dds"}, - {0x12DC, "pic1_27.dds"}, - {0x12DD, "pic1_28.dds"}, - {0x12DE, "pic1_29.dds"}, - {0x12DF, "pic1_30.dds"}, - {0x1400, "trophy/trophy00.trp"}, - {0x1401, "trophy/trophy01.trp"}, - {0x1402, "trophy/trophy02.trp"}, - {0x1403, "trophy/trophy03.trp"}, - {0x1404, "trophy/trophy04.trp"}, - {0x1405, "trophy/trophy05.trp"}, - {0x1406, "trophy/trophy06.trp"}, - {0x1407, "trophy/trophy07.trp"}, - {0x1408, "trophy/trophy08.trp"}, - {0x1409, "trophy/trophy09.trp"}, - {0x140A, "trophy/trophy10.trp"}, - {0x140B, "trophy/trophy11.trp"}, - {0x140C, "trophy/trophy12.trp"}, - {0x140D, "trophy/trophy13.trp"}, - {0x140E, "trophy/trophy14.trp"}, - {0x140F, "trophy/trophy15.trp"}, - {0x1410, "trophy/trophy16.trp"}, - {0x1411, "trophy/trophy17.trp"}, - {0x1412, "trophy/trophy18.trp"}, - {0x1413, "trophy/trophy19.trp"}, - {0x1414, "trophy/trophy20.trp"}, - {0x1415, "trophy/trophy21.trp"}, - {0x1416, "trophy/trophy22.trp"}, - {0x1417, "trophy/trophy23.trp"}, - {0x1418, "trophy/trophy24.trp"}, - {0x1419, "trophy/trophy25.trp"}, - {0x141A, "trophy/trophy26.trp"}, - {0x141B, "trophy/trophy27.trp"}, - {0x141C, "trophy/trophy28.trp"}, - {0x141D, "trophy/trophy29.trp"}, - {0x141E, "trophy/trophy30.trp"}, - {0x141F, "trophy/trophy31.trp"}, - {0x1420, "trophy/trophy32.trp"}, - {0x1421, "trophy/trophy33.trp"}, - {0x1422, "trophy/trophy34.trp"}, - {0x1423, "trophy/trophy35.trp"}, - {0x1424, "trophy/trophy36.trp"}, - {0x1425, "trophy/trophy37.trp"}, - {0x1426, "trophy/trophy38.trp"}, - {0x1427, "trophy/trophy39.trp"}, - {0x1428, "trophy/trophy40.trp"}, - {0x1429, "trophy/trophy41.trp"}, - {0x142A, "trophy/trophy42.trp"}, - {0x142B, "trophy/trophy43.trp"}, - {0x142C, "trophy/trophy44.trp"}, - {0x142D, "trophy/trophy45.trp"}, - {0x142E, "trophy/trophy46.trp"}, - {0x142F, "trophy/trophy47.trp"}, - {0x1430, "trophy/trophy48.trp"}, - {0x1431, "trophy/trophy49.trp"}, - {0x1432, "trophy/trophy50.trp"}, - {0x1433, "trophy/trophy51.trp"}, - {0x1434, "trophy/trophy52.trp"}, - {0x1435, "trophy/trophy53.trp"}, - {0x1436, "trophy/trophy54.trp"}, - {0x1437, "trophy/trophy55.trp"}, - {0x1438, "trophy/trophy56.trp"}, - {0x1439, "trophy/trophy57.trp"}, - {0x143A, "trophy/trophy58.trp"}, - {0x143B, "trophy/trophy59.trp"}, - {0x143C, "trophy/trophy60.trp"}, - {0x143D, "trophy/trophy61.trp"}, - {0x143E, "trophy/trophy62.trp"}, - {0x143F, "trophy/trophy63.trp"}, - {0x1440, "trophy/trophy64.trp"}, - {0x1441, "trophy/trophy65.trp"}, - {0x1442, "trophy/trophy66.trp"}, - {0x1443, "trophy/trophy67.trp"}, - {0x1444, "trophy/trophy68.trp"}, - {0x1445, "trophy/trophy69.trp"}, - {0x1446, "trophy/trophy70.trp"}, - {0x1447, "trophy/trophy71.trp"}, - {0x1448, "trophy/trophy72.trp"}, - {0x1449, "trophy/trophy73.trp"}, - {0x144A, "trophy/trophy74.trp"}, - {0x144B, "trophy/trophy75.trp"}, - {0x144C, "trophy/trophy76.trp"}, - {0x144D, "trophy/trophy77.trp"}, - {0x144E, "trophy/trophy78.trp"}, - {0x144F, "trophy/trophy79.trp"}, - {0x1450, "trophy/trophy80.trp"}, - {0x1451, "trophy/trophy81.trp"}, - {0x1452, "trophy/trophy82.trp"}, - {0x1453, "trophy/trophy83.trp"}, - {0x1454, "trophy/trophy84.trp"}, - {0x1455, "trophy/trophy85.trp"}, - {0x1456, "trophy/trophy86.trp"}, - {0x1457, "trophy/trophy87.trp"}, - {0x1458, "trophy/trophy88.trp"}, - {0x1459, "trophy/trophy89.trp"}, - {0x145A, "trophy/trophy90.trp"}, - {0x145B, "trophy/trophy91.trp"}, - {0x145C, "trophy/trophy92.trp"}, - {0x145D, "trophy/trophy93.trp"}, - {0x145E, "trophy/trophy94.trp"}, - {0x145F, "trophy/trophy95.trp"}, - {0x1460, "trophy/trophy96.trp"}, - {0x1461, "trophy/trophy97.trp"}, - {0x1462, "trophy/trophy98.trp"}, - {0x1463, "trophy/trophy99.trp"}, - {0x1600, "keymap_rp/001.png"}, - {0x1601, "keymap_rp/002.png"}, - {0x1602, "keymap_rp/003.png"}, - {0x1603, "keymap_rp/004.png"}, - {0x1604, "keymap_rp/005.png"}, - {0x1605, "keymap_rp/006.png"}, - {0x1606, "keymap_rp/007.png"}, - {0x1607, "keymap_rp/008.png"}, - {0x1608, "keymap_rp/009.png"}, - {0x1609, "keymap_rp/010.png"}, - {0x1610, "keymap_rp/00/001.png"}, - {0x1611, "keymap_rp/00/002.png"}, - {0x1612, "keymap_rp/00/003.png"}, - {0x1613, "keymap_rp/00/004.png"}, - {0x1614, "keymap_rp/00/005.png"}, - {0x1615, "keymap_rp/00/006.png"}, - {0x1616, "keymap_rp/00/007.png"}, - {0x1617, "keymap_rp/00/008.png"}, - {0x1618, "keymap_rp/00/009.png"}, - {0x1619, "keymap_rp/00/010.png"}, - {0x1620, "keymap_rp/01/001.png"}, - {0x1621, "keymap_rp/01/002.png"}, - {0x1622, "keymap_rp/01/003.png"}, - {0x1623, "keymap_rp/01/004.png"}, - {0x1624, "keymap_rp/01/005.png"}, - {0x1625, "keymap_rp/01/006.png"}, - {0x1626, "keymap_rp/01/007.png"}, - {0x1627, "keymap_rp/01/008.png"}, - {0x1628, "keymap_rp/01/009.png"}, - {0x1629, "keymap_rp/01/010.png"}, - {0x1630, "keymap_rp/02/001.png"}, - {0x1631, "keymap_rp/02/002.png"}, - {0x1632, "keymap_rp/02/003.png"}, - {0x1633, "keymap_rp/02/004.png"}, - {0x1634, "keymap_rp/02/005.png"}, - {0x1635, "keymap_rp/02/006.png"}, - {0x1636, "keymap_rp/02/007.png"}, - {0x1637, "keymap_rp/02/008.png"}, - {0x1638, "keymap_rp/02/009.png"}, - {0x1639, "keymap_rp/02/010.png"}, - {0x1640, "keymap_rp/03/001.png"}, - {0x1641, "keymap_rp/03/002.png"}, - {0x1642, "keymap_rp/03/003.png"}, - {0x1643, "keymap_rp/03/004.png"}, - {0x1644, "keymap_rp/03/005.png"}, - {0x1645, "keymap_rp/03/006.png"}, - {0x1646, "keymap_rp/03/007.png"}, - {0x1647, "keymap_rp/03/008.png"}, - {0x1648, "keymap_rp/03/0010.png"}, - {0x1650, "keymap_rp/04/001.png"}, - {0x1651, "keymap_rp/04/002.png"}, - {0x1652, "keymap_rp/04/003.png"}, - {0x1653, "keymap_rp/04/004.png"}, - {0x1654, "keymap_rp/04/005.png"}, - {0x1655, "keymap_rp/04/006.png"}, - {0x1656, "keymap_rp/04/007.png"}, - {0x1657, "keymap_rp/04/008.png"}, - {0x1658, "keymap_rp/04/009.png"}, - {0x1659, "keymap_rp/04/010.png"}, - {0x1660, "keymap_rp/05/001.png"}, - {0x1661, "keymap_rp/05/002.png"}, - {0x1662, "keymap_rp/05/003.png"}, - {0x1663, "keymap_rp/05/004.png"}, - {0x1664, "keymap_rp/05/005.png"}, - {0x1665, "keymap_rp/05/006.png"}, - {0x1666, "keymap_rp/05/007.png"}, - {0x1667, "keymap_rp/05/008.png"}, - {0x1668, "keymap_rp/05/009.png"}, - {0x1669, "keymap_rp/05/010.png"}, - {0x1670, "keymap_rp/06/001.png"}, - {0x1671, "keymap_rp/06/002.png"}, - {0x1672, "keymap_rp/06/003.png"}, - {0x1673, "keymap_rp/06/004.png"}, - {0x1674, "keymap_rp/06/005.png"}, - {0x1675, "keymap_rp/06/006.png"}, - {0x1676, "keymap_rp/06/007.png"}, - {0x1677, "keymap_rp/06/008.png"}, - {0x1678, "keymap_rp/06/009.png"}, - {0x1679, "keymap_rp/06/010.png"}, - {0x1680, "keymap_rp/07/001.png"}, - {0x1681, "keymap_rp/07/002.png"}, - {0x1682, "keymap_rp/07/003.png"}, - {0x1683, "keymap_rp/07/004.png"}, - {0x1684, "keymap_rp/07/005.png"}, - {0x1685, "keymap_rp/07/006.png"}, - {0x1686, "keymap_rp/07/007.png"}, - {0x1687, "keymap_rp/07/008.png"}, - {0x1688, "keymap_rp/07/009.png"}, - {0x1689, "keymap_rp/07/010.png"}, - {0x1690, "keymap_rp/08/001.png"}, - {0x1691, "keymap_rp/08/002.png"}, - {0x1692, "keymap_rp/08/003.png"}, - {0x1693, "keymap_rp/08/004.png"}, - {0x1694, "keymap_rp/08/005.png"}, - {0x1695, "keymap_rp/08/006.png"}, - {0x1696, "keymap_rp/08/007.png"}, - {0x1697, "keymap_rp/08/008.png"}, - {0x1698, "keymap_rp/08/009.png"}, - {0x1699, "keymap_rp/08/010.png"}, - {0x16A0, "keymap_rp/09/001.png"}, - {0x16A1, "keymap_rp/09/002.png"}, - {0x16A2, "keymap_rp/09/003.png"}, - {0x16A3, "keymap_rp/09/004.png"}, - {0x16A4, "keymap_rp/09/005.png"}, - {0x16A5, "keymap_rp/09/006.png"}, - {0x16A6, "keymap_rp/09/007.png"}, - {0x16A7, "keymap_rp/09/008.png"}, - {0x16A8, "keymap_rp/09/009.png"}, - {0x16A9, "keymap_rp/09/010.png"}, - {0x16B0, "keymap_rp/10/001.png"}, - {0x16B1, "keymap_rp/10/002.png"}, - {0x16B2, "keymap_rp/10/003.png"}, - {0x16B3, "keymap_rp/10/004.png"}, - {0x16B4, "keymap_rp/10/005.png"}, - {0x16B5, "keymap_rp/10/006.png"}, - {0x16B6, "keymap_rp/10/007.png"}, - {0x16B7, "keymap_rp/10/008.png"}, - {0x16B8, "keymap_rp/10/009.png"}, - {0x16B9, "keymap_rp/10/010.png"}, - {0x16C0, "keymap_rp/11/001.png"}, - {0x16C1, "keymap_rp/11/002.png"}, - {0x16C2, "keymap_rp/11/003.png"}, - {0x16C3, "keymap_rp/11/004.png"}, - {0x16C4, "keymap_rp/11/005.png"}, - {0x16C5, "keymap_rp/11/006.png"}, - {0x16C6, "keymap_rp/11/007.png"}, - {0x16C7, "keymap_rp/11/008.png"}, - {0x16C8, "keymap_rp/11/009.png"}, - {0x16C9, "keymap_rp/11/010.png"}, - {0x16D0, "keymap_rp/12/001.png"}, - {0x16D1, "keymap_rp/12/002.png"}, - {0x16D2, "keymap_rp/12/003.png"}, - {0x16D3, "keymap_rp/12/004.png"}, - {0x16D4, "keymap_rp/12/005.png"}, - {0x16D5, "keymap_rp/12/006.png"}, - {0x16D6, "keymap_rp/12/007.png"}, - {0x16D7, "keymap_rp/12/008.png"}, - {0x16D8, "keymap_rp/12/009.png"}, - {0x16D9, "keymap_rp/12/010.png"}, - {0x16E0, "keymap_rp/13/001.png"}, - {0x16E1, "keymap_rp/13/002.png"}, - {0x16E2, "keymap_rp/13/003.png"}, - {0x16E3, "keymap_rp/13/004.png"}, - {0x16E4, "keymap_rp/13/005.png"}, - {0x16E5, "keymap_rp/13/006.png"}, - {0x16E6, "keymap_rp/13/007.png"}, - {0x16E7, "keymap_rp/13/008.png"}, - {0x16E8, "keymap_rp/13/009.png"}, - {0x16E9, "keymap_rp/13/010.png"}, - {0x16F0, "keymap_rp/14/001.png"}, - {0x16F1, "keymap_rp/14/002.png"}, - {0x16F2, "keymap_rp/14/003.png"}, - {0x16F3, "keymap_rp/14/004.png"}, - {0x16F4, "keymap_rp/14/005.png"}, - {0x16F5, "keymap_rp/14/006.png"}, - {0x16F6, "keymap_rp/14/007.png"}, - {0x16F7, "keymap_rp/14/008.png"}, - {0x16F8, "keymap_rp/14/009.png"}, - {0x16F9, "keymap_rp/14/010.png"}, - {0x1700, "keymap_rp/15/001.png"}, - {0x1701, "keymap_rp/15/002.png"}, - {0x1702, "keymap_rp/15/003.png"}, - {0x1703, "keymap_rp/15/004.png"}, - {0x1704, "keymap_rp/15/005.png"}, - {0x1705, "keymap_rp/15/006.png"}, - {0x1706, "keymap_rp/15/007.png"}, - {0x1707, "keymap_rp/15/008.png"}, - {0x1708, "keymap_rp/15/009.png"}, - {0x1709, "keymap_rp/15/010.png"}, - {0x1710, "keymap_rp/16/001.png"}, - {0x1711, "keymap_rp/16/002.png"}, - {0x1712, "keymap_rp/16/003.png"}, - {0x1713, "keymap_rp/16/004.png"}, - {0x1714, "keymap_rp/16/005.png"}, - {0x1715, "keymap_rp/16/006.png"}, - {0x1716, "keymap_rp/16/007.png"}, - {0x1717, "keymap_rp/16/008.png"}, - {0x1718, "keymap_rp/16/009.png"}, - {0x1719, "keymap_rp/16/010.png"}, - {0x1720, "keymap_rp/17/001.png"}, - {0x1721, "keymap_rp/17/002.png"}, - {0x1722, "keymap_rp/17/003.png"}, - {0x1723, "keymap_rp/17/004.png"}, - {0x1724, "keymap_rp/17/005.png"}, - {0x1725, "keymap_rp/17/006.png"}, - {0x1726, "keymap_rp/17/007.png"}, - {0x1727, "keymap_rp/17/008.png"}, - {0x1728, "keymap_rp/17/009.png"}, - {0x1729, "keymap_rp/17/010.png"}, - {0x1730, "keymap_rp/18/001.png"}, - {0x1731, "keymap_rp/18/002.png"}, - {0x1732, "keymap_rp/18/003.png"}, - {0x1733, "keymap_rp/18/004.png"}, - {0x1734, "keymap_rp/18/005.png"}, - {0x1735, "keymap_rp/18/006.png"}, - {0x1736, "keymap_rp/18/007.png"}, - {0x1737, "keymap_rp/18/008.png"}, - {0x1738, "keymap_rp/18/009.png"}, - {0x1739, "keymap_rp/18/010.png"}, - {0x1740, "keymap_rp/19/001.png"}, - {0x1741, "keymap_rp/19/002.png"}, - {0x1742, "keymap_rp/19/003.png"}, - {0x1743, "keymap_rp/19/004.png"}, - {0x1744, "keymap_rp/19/005.png"}, - {0x1745, "keymap_rp/19/006.png"}, - {0x1746, "keymap_rp/19/007.png"}, - {0x1747, "keymap_rp/19/008.png"}, - {0x1748, "keymap_rp/19/009.png"}, - {0x1749, "keymap_rp/19/010.png"}, - {0x1750, "keymap_rp/20/001.png"}, - {0x1751, "keymap_rp/20/002.png"}, - {0x1752, "keymap_rp/20/003.png"}, - {0x1753, "keymap_rp/20/004.png"}, - {0x1754, "keymap_rp/20/005.png"}, - {0x1755, "keymap_rp/20/006.png"}, - {0x1756, "keymap_rp/20/007.png"}, - {0x1757, "keymap_rp/20/008.png"}, - {0x1758, "keymap_rp/20/009.png"}, - {0x1759, "keymap_rp/20/010.png"}, - {0x1760, "keymap_rp/21/001.png"}, - {0x1761, "keymap_rp/21/002.png"}, - {0x1762, "keymap_rp/21/003.png"}, - {0x1763, "keymap_rp/21/004.png"}, - {0x1764, "keymap_rp/21/005.png"}, - {0x1765, "keymap_rp/21/006.png"}, - {0x1766, "keymap_rp/21/007.png"}, - {0x1767, "keymap_rp/21/008.png"}, - {0x1768, "keymap_rp/21/009.png"}, - {0x1769, "keymap_rp/21/010.png"}, - {0x1770, "keymap_rp/22/001.png"}, - {0x1771, "keymap_rp/22/002.png"}, - {0x1772, "keymap_rp/22/003.png"}, - {0x1773, "keymap_rp/22/004.png"}, - {0x1774, "keymap_rp/22/005.png"}, - {0x1775, "keymap_rp/22/006.png"}, - {0x1776, "keymap_rp/22/007.png"}, - {0x1777, "keymap_rp/22/008.png"}, - {0x1778, "keymap_rp/22/009.png"}, - {0x1779, "keymap_rp/22/010.png"}, - {0x1780, "keymap_rp/23/001.png"}, - {0x1781, "keymap_rp/23/002.png"}, - {0x1782, "keymap_rp/23/003.png"}, - {0x1783, "keymap_rp/23/004.png"}, - {0x1784, "keymap_rp/23/005.png"}, - {0x1785, "keymap_rp/23/006.png"}, - {0x1786, "keymap_rp/23/007.png"}, - {0x1787, "keymap_rp/23/008.png"}, - {0x1788, "keymap_rp/23/009.png"}, - {0x1789, "keymap_rp/23/010.png"}, - {0x1790, "keymap_rp/24/001.png"}, - {0x1791, "keymap_rp/24/002.png"}, - {0x1792, "keymap_rp/24/003.png"}, - {0x1793, "keymap_rp/24/004.png"}, - {0x1794, "keymap_rp/24/005.png"}, - {0x1795, "keymap_rp/24/006.png"}, - {0x1796, "keymap_rp/24/007.png"}, - {0x1797, "keymap_rp/24/008.png"}, - {0x1798, "keymap_rp/24/009.png"}, - {0x1799, "keymap_rp/24/010.png"}, - {0x17A0, "keymap_rp/25/001.png"}, - {0x17A1, "keymap_rp/25/002.png"}, - {0x17A2, "keymap_rp/25/003.png"}, - {0x17A3, "keymap_rp/25/004.png"}, - {0x17A4, "keymap_rp/25/005.png"}, - {0x17A5, "keymap_rp/25/006.png"}, - {0x17A6, "keymap_rp/25/007.png"}, - {0x17A7, "keymap_rp/25/008.png"}, - {0x17A8, "keymap_rp/25/009.png"}, - {0x17A9, "keymap_rp/25/010.png"}, - {0x17B0, "keymap_rp/26/001.png"}, - {0x17B1, "keymap_rp/26/002.png"}, - {0x17B2, "keymap_rp/26/003.png"}, - {0x17B3, "keymap_rp/26/004.png"}, - {0x17B4, "keymap_rp/26/005.png"}, - {0x17B5, "keymap_rp/26/006.png"}, - {0x17B6, "keymap_rp/26/007.png"}, - {0x17B7, "keymap_rp/26/008.png"}, - {0x17B8, "keymap_rp/26/009.png"}, - {0x17B9, "keymap_rp/26/010.png"}, - {0x17C0, "keymap_rp/27/001.png"}, - {0x17C1, "keymap_rp/27/002.png"}, - {0x17C2, "keymap_rp/27/003.png"}, - {0x17C3, "keymap_rp/27/004.png"}, - {0x17C4, "keymap_rp/27/005.png"}, - {0x17C5, "keymap_rp/27/006.png"}, - {0x17C6, "keymap_rp/27/007.png"}, - {0x17C7, "keymap_rp/27/008.png"}, - {0x17C8, "keymap_rp/27/009.png"}, - {0x17C9, "keymap_rp/27/010.png"}, - {0x17D0, "keymap_rp/28/001.png"}, - {0x17D1, "keymap_rp/28/002.png"}, - {0x17D2, "keymap_rp/28/003.png"}, - {0x17D3, "keymap_rp/28/004.png"}, - {0x17D4, "keymap_rp/28/005.png"}, - {0x17D5, "keymap_rp/28/006.png"}, - {0x17D6, "keymap_rp/28/007.png"}, - {0x17D7, "keymap_rp/28/008.png"}, - {0x17D8, "keymap_rp/28/009.png"}, - {0x17D9, "keymap_rp/28/010.png"}, - {0x17E0, "keymap_rp/29/001.png"}, - {0x17E1, "keymap_rp/29/002.png"}, - {0x17E2, "keymap_rp/29/003.png"}, - {0x17E3, "keymap_rp/29/004.png"}, - {0x17E4, "keymap_rp/29/005.png"}, - {0x17E5, "keymap_rp/29/006.png"}, - {0x17E6, "keymap_rp/29/007.png"}, - {0x17E7, "keymap_rp/29/008.png"}, - {0x17E8, "keymap_rp/29/009.png"}, - {0x17E9, "keymap_rp/29/010.png"}, - {0x17F0, "keymap_rp/30/001.png"}, - {0x17F1, "keymap_rp/30/002.png"}, - {0x17F2, "keymap_rp/30/003.png"}, - {0x17F3, "keymap_rp/30/004.png"}, - {0x17F4, "keymap_rp/30/005.png"}, - {0x17F5, "keymap_rp/30/006.png"}, - {0x17F6, "keymap_rp/30/007.png"}, - {0x17F7, "keymap_rp/30/008.png"}, - {0x17F8, "keymap_rp/30/009.png"}, - {0x17F9, "keymap_rp/30/010.png"}, -}}; - -std::string_view GetEntryNameByType(u32 type) { - const auto key = PkgEntryValue{type}; - const auto it = std::ranges::lower_bound(PkgEntries, key); - if (it != PkgEntries.end() && it->type == type) { - return it->name; - } - return ""; -} diff --git a/src/core/file_format/pkg_type.h b/src/core/file_format/pkg_type.h deleted file mode 100644 index 6b010e3a3..000000000 --- a/src/core/file_format/pkg_type.h +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include "common/types.h" - -/// Retrieves the PKG entry name from its type identifier. -std::string_view GetEntryNameByType(u32 type); diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index d25c93c3f..311bd0b9d 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -1,10 +1,36 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + #include "common/config.h" #include "common/logging/log.h" #include "common/path_util.h" -#include "trp.h" +#include "core/file_format/trp.h" + +static void DecryptEFSM(std::span trophyKey, + std::span NPcommID, + std::span efsmIv, std::span ciphertext, + std::span decrypted) { + + // step 1: Encrypt NPcommID + CryptoPP::CBC_Mode::Encryption encrypt; + + std::vector trophyIv(16, 0); + std::vector trpKey(16); + + encrypt.SetKeyWithIV(trophyKey.data(), trophyKey.size(), trophyIv.data()); + encrypt.ProcessData(trpKey.data(), NPcommID.data(), 16); + + // step 2: decrypt efsm. + CryptoPP::CBC_Mode::Decryption decrypt; + decrypt.SetKeyWithIV(trpKey.data(), trpKey.size(), efsmIv.data()); + + for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) { + decrypt.ProcessData(decrypted.data() + i, ciphertext.data() + i, CryptoPP::AES::BLOCKSIZE); + } +} TRP::TRP() = default; TRP::~TRP() = default; @@ -115,7 +141,7 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit return false; } file.Read(ESFM); - crypto.decryptEFSM(user_key, np_comm_id, esfmIv, ESFM, XML); // decrypt + DecryptEFSM(user_key, np_comm_id, esfmIv, ESFM, XML); // decrypt removePadding(XML); std::string xml_name = entry.entry_name; size_t pos = xml_name.find("ESFM"); diff --git a/src/core/file_format/trp.h b/src/core/file_format/trp.h index aec129f0e..01207475b 100644 --- a/src/core/file_format/trp.h +++ b/src/core/file_format/trp.h @@ -7,7 +7,6 @@ #include "common/endian.h" #include "common/io_file.h" #include "common/types.h" -#include "core/crypto/crypto.h" struct TrpHeader { u32_be magic; // (0xDCA24D00) @@ -37,10 +36,9 @@ public: void GetNPcommID(const std::filesystem::path& trophyPath, int index); private: - Crypto crypto; std::vector NPcommID = std::vector(12); std::array np_comm_id{}; std::array esfmIv{}; std::filesystem::path trpFilesPath; static constexpr int iv_len = 16; -}; \ No newline at end of file +}; diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 3420e933e..e92676c02 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "about_dialog.h" #include "cheats_patches.h" @@ -19,7 +20,6 @@ #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" #include "install_dir_select.h" @@ -718,7 +718,6 @@ void MainWindow::CreateConnects() { }); // Package install. - connect(ui->bootInstallPkgAct, &QAction::triggered, this, &MainWindow::InstallPkg); connect(ui->bootGameAct, &QAction::triggered, this, &MainWindow::BootGame); connect(ui->gameInstallPathAct, &QAction::triggered, this, &MainWindow::InstallDirectory); @@ -726,15 +725,6 @@ void MainWindow::CreateConnects() { connect(ui->addElfFolderAct, &QAction::triggered, m_elf_viewer.data(), &ElfViewer::OpenElfFolder); - // Package Viewer. - connect(ui->pkgViewerAct, &QAction::triggered, this, [this]() { - PKGViewer* pkgViewer = new PKGViewer( - m_game_info, this, [this](std::filesystem::path file, int pkgNum, int nPkg) { - this->InstallDragDropPkg(file, pkgNum, nPkg); - }); - pkgViewer->show(); - }); - // Trophy Viewer connect(ui->trophyViewerAct, &QAction::triggered, this, [this]() { if (m_game_info->m_games.empty()) { @@ -965,22 +955,6 @@ void MainWindow::SaveWindowState() const { this->geometry().width(), this->geometry().height()); } -void MainWindow::InstallPkg() { - QFileDialog dialog; - dialog.setFileMode(QFileDialog::ExistingFiles); - dialog.setNameFilter(tr("PKG File (*.PKG *.pkg)")); - if (dialog.exec()) { - QStringList fileNames = dialog.selectedFiles(); - int nPkg = fileNames.size(); - int pkgNum = 0; - for (const QString& file : fileNames) { - ++pkgNum; - std::filesystem::path path = Common::FS::PathFromQString(file); - MainWindow::InstallDragDropPkg(path, pkgNum, nPkg); - } - } -} - void MainWindow::BootGame() { QFileDialog dialog; dialog.setFileMode(QFileDialog::ExistingFile); @@ -1004,260 +978,6 @@ void MainWindow::BootGame() { } } -void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg) { - if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) { - std::string failreason; - pkg = PKG(); - if (!pkg.Open(file, failreason)) { - QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); - return; - } - if (!psf.Open(pkg.sfo)) { - QMessageBox::critical(this, tr("PKG ERROR"), - "Could not read SFO. Check log for details"); - return; - } - auto category = psf.GetString("CATEGORY"); - - if (!use_for_all_queued || pkgNum == 1) { - InstallDirSelect ids; - const auto selected = ids.exec(); - if (selected == QDialog::Rejected) { - return; - } - - last_install_dir = ids.getSelectedDirectory(); - delete_file_on_install = ids.deleteFileOnInstall(); - use_for_all_queued = ids.useForAllQueued(); - } - std::filesystem::path game_install_dir = last_install_dir; - - QString pkgType = QString::fromStdString(pkg.GetPkgFlags()); - bool use_game_update = pkgType.contains("PATCH") && Config::getSeparateUpdateEnabled(); - - // Default paths - auto game_folder_path = game_install_dir / pkg.GetTitleID(); - auto game_update_path = use_game_update ? game_folder_path.parent_path() / - (std::string{pkg.GetTitleID()} + "-patch") - : game_folder_path; - const int max_depth = 5; - - if (pkgType.contains("PATCH")) { - // For patches, try to find the game recursively - auto found_game = Common::FS::FindGameByID(game_install_dir, - std::string{pkg.GetTitleID()}, max_depth); - if (found_game.has_value()) { - game_folder_path = found_game.value().parent_path(); - game_update_path = use_game_update ? game_folder_path.parent_path() / - (std::string{pkg.GetTitleID()} + "-patch") - : game_folder_path; - } - } else { - // For base games, we check if the game is already installed - auto found_game = Common::FS::FindGameByID(game_install_dir, - std::string{pkg.GetTitleID()}, max_depth); - if (found_game.has_value()) { - game_folder_path = found_game.value().parent_path(); - } - // If the game is not found, we install it in the game install directory - else { - game_folder_path = game_install_dir / pkg.GetTitleID(); - } - game_update_path = use_game_update ? game_folder_path.parent_path() / - (std::string{pkg.GetTitleID()} + "-patch") - : game_folder_path; - } - - QString gameDirPath; - Common::FS::PathToQString(gameDirPath, game_folder_path); - QDir game_dir(gameDirPath); - if (game_dir.exists()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("PKG Extraction")); - - std::string content_id; - if (auto value = psf.GetString("CONTENT_ID"); value.has_value()) { - content_id = std::string{*value}; - } else { - QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no CONTENT_ID"); - return; - } - std::string entitlement_label = Common::SplitString(content_id, '-')[2]; - - auto addon_extract_path = - Config::getAddonInstallDir() / pkg.GetTitleID() / entitlement_label; - QString addonDirPath; - Common::FS::PathToQString(addonDirPath, addon_extract_path); - QDir addon_dir(addonDirPath); - - if (pkgType.contains("PATCH")) { - QString pkg_app_version; - if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { - pkg_app_version = QString::fromStdString(std::string{*app_ver}); - } else { - QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); - return; - } - std::filesystem::path sce_folder_path = - std::filesystem::exists(game_update_path / "sce_sys" / "param.sfo") - ? game_update_path / "sce_sys" / "param.sfo" - : game_folder_path / "sce_sys" / "param.sfo"; - psf.Open(sce_folder_path); - QString game_app_version; - if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { - game_app_version = QString::fromStdString(std::string{*app_ver}); - } else { - QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); - return; - } - double appD = game_app_version.toDouble(); - double pkgD = pkg_app_version.toDouble(); - if (pkgD == appD) { - msgBox.setText(QString(tr("Patch detected!") + "\n" + - tr("PKG and Game versions match: ") + pkg_app_version + - "\n" + tr("Would you like to overwrite?"))); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::No); - } else if (pkgD < appD) { - msgBox.setText(QString(tr("Patch detected!") + "\n" + - tr("PKG Version %1 is older than installed version: ") - .arg(pkg_app_version) + - game_app_version + "\n" + - tr("Would you like to overwrite?"))); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::No); - } else { - msgBox.setText(QString(tr("Patch detected!") + "\n" + - tr("Game is installed: ") + game_app_version + "\n" + - tr("Would you like to install Patch: ") + - pkg_app_version + " ?")); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::No); - } - int result = msgBox.exec(); - if (result == QMessageBox::Yes) { - // Do nothing. - } else { - return; - } - } else if (category == "ac") { - if (!addon_dir.exists()) { - QMessageBox addonMsgBox; - addonMsgBox.setWindowTitle(tr("DLC Installation")); - addonMsgBox.setText(QString(tr("Would you like to install DLC: %1?")) - .arg(QString::fromStdString(entitlement_label))); - - addonMsgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - addonMsgBox.setDefaultButton(QMessageBox::No); - int result = addonMsgBox.exec(); - if (result == QMessageBox::Yes) { - game_update_path = addon_extract_path; - } else { - return; - } - } else { - msgBox.setText(QString(tr("DLC already installed:") + "\n" + addonDirPath + - "\n\n" + tr("Would you like to overwrite?"))); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::No); - int result = msgBox.exec(); - if (result == QMessageBox::Yes) { - game_update_path = addon_extract_path; - } else { - return; - } - } - } else { - msgBox.setText(QString(tr("Game already installed") + "\n" + gameDirPath + "\n" + - tr("Would you like to overwrite?"))); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::No); - int result = msgBox.exec(); - if (result == QMessageBox::Yes) { - // Do nothing. - } else { - return; - } - } - } else { - // Do nothing; - if (pkgType.contains("PATCH") || category == "ac") { - QMessageBox::information( - this, tr("PKG Extraction"), - tr("PKG is a patch or DLC, please install the game first!")); - return; - } - // what else? - } - if (!pkg.Extract(file, game_update_path, failreason)) { - QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); - } else { - int nfiles = pkg.GetNumberOfFiles(); - - if (nfiles > 0) { - QVector indices; - for (int i = 0; i < nfiles; i++) { - indices.append(i); - } - - QProgressDialog dialog; - dialog.setWindowTitle(tr("PKG Extraction")); - dialog.setWindowModality(Qt::WindowModal); - QString extractmsg = QString(tr("Extracting PKG %1/%2")).arg(pkgNum).arg(nPkg); - dialog.setLabelText(extractmsg); - dialog.setAutoClose(true); - dialog.setRange(0, nfiles); - - dialog.setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, - dialog.size(), this->geometry())); - - QFutureWatcher futureWatcher; - connect(&futureWatcher, &QFutureWatcher::finished, this, [=, this]() { - if (pkgNum == nPkg) { - QString path; - - // We want to show the parent path instead of the full path - Common::FS::PathToQString(path, game_folder_path.parent_path()); - QIcon windowIcon( - Common::FS::PathToUTF8String(game_folder_path / "sce_sys/icon0.png") - .c_str()); - - QMessageBox extractMsgBox(this); - extractMsgBox.setWindowTitle(tr("Extraction Finished")); - if (!windowIcon.isNull()) { - extractMsgBox.setWindowIcon(windowIcon); - } - extractMsgBox.setText( - QString(tr("Game successfully installed at %1")).arg(path)); - extractMsgBox.addButton(QMessageBox::Ok); - extractMsgBox.setDefaultButton(QMessageBox::Ok); - connect(&extractMsgBox, &QMessageBox::buttonClicked, this, - [&](QAbstractButton* button) { - if (extractMsgBox.button(QMessageBox::Ok) == button) { - extractMsgBox.close(); - emit ExtractionFinished(); - } - }); - extractMsgBox.exec(); - } - if (delete_file_on_install) { - std::filesystem::remove(file); - } - }); - connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); }); - connect(&futureWatcher, &QFutureWatcher::progressValueChanged, &dialog, - &QProgressDialog::setValue); - futureWatcher.setFuture( - QtConcurrent::map(indices, [&](int index) { pkg.ExtractFiles(index); })); - dialog.exec(); - } - } - } else { - QMessageBox::critical(this, tr("PKG ERROR"), - tr("File doesn't appear to be a valid PKG file")); - } -} - void MainWindow::InstallDirectory() { GameInstallDialog dlg; dlg.exec(); @@ -1340,7 +1060,6 @@ QIcon MainWindow::RecolorIcon(const QIcon& icon, bool isWhite) { } void MainWindow::SetUiIcons(bool isWhite) { - ui->bootInstallPkgAct->setIcon(RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite)); ui->bootGameAct->setIcon(RecolorIcon(ui->bootGameAct->icon(), isWhite)); ui->shadFolderAct->setIcon(RecolorIcon(ui->shadFolderAct->icon(), isWhite)); ui->exitAct->setIcon(RecolorIcon(ui->exitAct->icon(), isWhite)); @@ -1368,7 +1087,6 @@ void MainWindow::SetUiIcons(bool isWhite) { ui->keyboardButton->setIcon(RecolorIcon(ui->keyboardButton->icon(), isWhite)); ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite)); ui->menuGame_List_Mode->setIcon(RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite)); - ui->pkgViewerAct->setIcon(RecolorIcon(ui->pkgViewerAct->icon(), isWhite)); ui->trophyViewerAct->setIcon(RecolorIcon(ui->trophyViewerAct->icon(), isWhite)); ui->configureAct->setIcon(RecolorIcon(ui->configureAct->icon(), isWhite)); ui->addElfFolderAct->setIcon(RecolorIcon(ui->addElfFolderAct->icon(), isWhite)); diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index bcd5e53ba..5d05bfca4 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -22,7 +22,6 @@ #include "game_list_utils.h" #include "main_window_themes.h" #include "main_window_ui.h" -#include "pkg_viewer.h" class GameListFrame; @@ -36,7 +35,6 @@ public: explicit MainWindow(QWidget* parent = nullptr); ~MainWindow(); bool Init(); - void InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg); void InstallDirectory(); void StartGame(); void PauseGame(); @@ -72,7 +70,6 @@ private: void SetLastUsedTheme(); void SetLastIconSizeBullet(); void SetUiIcons(bool isWhite); - void InstallPkg(); void BootGame(); void AddRecentFiles(QString filePath); void LoadTranslation(); @@ -89,7 +86,6 @@ private: QActionGroup* m_list_mode_act_group = nullptr; QActionGroup* m_theme_act_group = nullptr; QActionGroup* m_recent_files_group = nullptr; - PKG pkg; // Dockable widget frames WindowThemes m_window_themes; GameListUtils m_game_list_utils; @@ -120,20 +116,6 @@ protected: } } - void dropEvent(QDropEvent* event1) override { - const QMimeData* mimeData = event1->mimeData(); - if (mimeData->hasUrls()) { - QList urlList = mimeData->urls(); - int pkgNum = 0; - int nPkg = urlList.size(); - for (const QUrl& url : urlList) { - pkgNum++; - std::filesystem::path path = Common::FS::PathFromQString(url.toLocalFile()); - InstallDragDropPkg(path, pkgNum, nPkg); - } - } - } - void resizeEvent(QResizeEvent* event) override; std::filesystem::path last_install_dir = ""; diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index c4f47b636..2c4d4480b 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -9,7 +9,6 @@ class Ui_MainWindow { public: - QAction* bootInstallPkgAct; QAction* bootGameAct; QAction* addElfFolderAct; QAction* shadFolderAct; @@ -27,7 +26,6 @@ public: QAction* gameInstallPathAct; QAction* downloadCheatsPatchesAct; QAction* dumpGameListAct; - QAction* pkgViewerAct; QAction* trophyViewerAct; #ifdef ENABLE_UPDATER QAction* updaterAct; @@ -87,9 +85,6 @@ public: MainWindow->setDockNestingEnabled(true); MainWindow->setDockOptions(QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks | QMainWindow::AnimatedDocks | QMainWindow::GroupedDragging); - bootInstallPkgAct = new QAction(MainWindow); - bootInstallPkgAct->setObjectName("bootInstallPkgAct"); - bootInstallPkgAct->setIcon(QIcon(":images/file_icon.png")); bootGameAct = new QAction(MainWindow); bootGameAct->setObjectName("bootGameAct"); bootGameAct->setIcon(QIcon(":images/play_icon.png")); @@ -148,9 +143,6 @@ public: dumpGameListAct = new QAction(MainWindow); dumpGameListAct->setObjectName("dumpGameList"); dumpGameListAct->setIcon(QIcon(":images/dump_icon.png")); - pkgViewerAct = new QAction(MainWindow); - pkgViewerAct->setObjectName("pkgViewer"); - pkgViewerAct->setIcon(QIcon(":images/file_icon.png")); trophyViewerAct = new QAction(MainWindow); trophyViewerAct->setObjectName("trophyViewer"); trophyViewerAct->setIcon(QIcon(":images/trophy_icon.png")); @@ -309,7 +301,6 @@ public: menuBar->addAction(menuView->menuAction()); menuBar->addAction(menuSettings->menuAction()); menuBar->addAction(menuHelp->menuAction()); - menuFile->addAction(bootInstallPkgAct); menuFile->addAction(bootGameAct); menuFile->addSeparator(); menuFile->addAction(addElfFolderAct); @@ -345,7 +336,6 @@ public: menuSettings->addAction(menuUtils->menuAction()); menuUtils->addAction(downloadCheatsPatchesAct); menuUtils->addAction(dumpGameListAct); - menuUtils->addAction(pkgViewerAct); menuUtils->addAction(trophyViewerAct); #ifdef ENABLE_UPDATER menuHelp->addAction(updaterAct); @@ -361,8 +351,6 @@ public: MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "shadPS4", nullptr)); addElfFolderAct->setText( QCoreApplication::translate("MainWindow", "Open/Add Elf Folder", nullptr)); - bootInstallPkgAct->setText( - QCoreApplication::translate("MainWindow", "Install Packages (PKG)", nullptr)); bootGameAct->setText(QCoreApplication::translate("MainWindow", "Boot Game", nullptr)); #ifdef ENABLE_UPDATER updaterAct->setText( @@ -371,8 +359,6 @@ public: aboutAct->setText(QCoreApplication::translate("MainWindow", "About shadPS4", nullptr)); configureAct->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr)); #if QT_CONFIG(tooltip) - bootInstallPkgAct->setToolTip(QCoreApplication::translate( - "MainWindow", "Install application from a .pkg file", nullptr)); #endif // QT_CONFIG(tooltip) menuRecent->setTitle(QCoreApplication::translate("MainWindow", "Recent Games", nullptr)); shadFolderAct->setText( @@ -404,7 +390,6 @@ public: QCoreApplication::translate("MainWindow", "Download Cheats/Patches", nullptr)); dumpGameListAct->setText( QCoreApplication::translate("MainWindow", "Dump Game List", nullptr)); - pkgViewerAct->setText(QCoreApplication::translate("MainWindow", "PKG Viewer", nullptr)); trophyViewerAct->setText( QCoreApplication::translate("MainWindow", "Trophy Viewer", nullptr)); mw_searchbar->setPlaceholderText( @@ -433,4 +418,4 @@ public: namespace Ui { class MainWindow : public Ui_MainWindow {}; -} // namespace Ui \ No newline at end of file +} // namespace Ui diff --git a/src/qt_gui/pkg_viewer.cpp b/src/qt_gui/pkg_viewer.cpp deleted file mode 100644 index ecbc6312d..000000000 --- a/src/qt_gui/pkg_viewer.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "pkg_viewer.h" - -PKGViewer::PKGViewer(std::shared_ptr game_info_get, QWidget* parent, - std::function InstallDragDropPkg) - : QMainWindow(), m_game_info(game_info_get) { - this->resize(1280, 720); - this->setAttribute(Qt::WA_DeleteOnClose); - dir_list_std = Config::getPkgViewer(); - dir_list.clear(); - for (const auto& str : dir_list_std) { - dir_list.append(QString::fromStdString(str)); - } - statusBar = new QStatusBar(treeWidget); - this->setStatusBar(statusBar); - treeWidget = new QTreeWidget(this); - treeWidget->setColumnCount(9); - QStringList headers; - headers << tr("Name") << tr("Serial") << tr("Installed") << tr("Size") << tr("Category") - << tr("Type") << tr("App Ver") << tr("FW") << tr("Region") << tr("Flags") << tr("Path"); - treeWidget->setHeaderLabels(headers); - treeWidget->header()->setDefaultAlignment(Qt::AlignCenter); - treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); - treeWidget->setColumnWidth(8, 170); - this->setCentralWidget(treeWidget); - QMenuBar* menuBar = new QMenuBar(this); - menuBar->setContextMenuPolicy(Qt::PreventContextMenu); - QMenu* fileMenu = menuBar->addMenu(tr("File")); - QAction* openFolderAct = new QAction(tr("Open Folder"), this); - fileMenu->addAction(openFolderAct); - this->setMenuBar(menuBar); - CheckPKGFolders(); // Check for new PKG files in existing folders. - ProcessPKGInfo(); - - connect(openFolderAct, &QAction::triggered, this, &PKGViewer::OpenPKGFolder); - - connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, - [=, this](const QPoint& pos) { - if (treeWidget->selectedItems().isEmpty()) { - return; - } - m_gui_context_menus.RequestGameMenuPKGViewer(pos, m_full_pkg_list, treeWidget, - InstallDragDropPkg); - }); - - connect(parent, &QWidget::destroyed, this, [this]() { this->deleteLater(); }); -} - -PKGViewer::~PKGViewer() {} - -void PKGViewer::OpenPKGFolder() { - QString folderPath = - QFileDialog::getExistingDirectory(this, tr("Open Folder"), QDir::homePath()); - if (!dir_list.contains(folderPath)) { - dir_list.append(folderPath); - QDir directory(folderPath); - QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files); - for (const QFileInfo& fileInfo : fileInfoList) { - QString file_ext = fileInfo.suffix(); - if (fileInfo.isFile() && file_ext == "pkg") { - m_pkg_list.append(fileInfo.absoluteFilePath()); - } - } - std::sort(m_pkg_list.begin(), m_pkg_list.end()); - ProcessPKGInfo(); - dir_list_std.clear(); - for (auto dir : dir_list) { - dir_list_std.push_back(dir.toStdString()); - } - Config::setPkgViewer(dir_list_std); - } else { - // qDebug() << "Folder selection canceled."; - } -} - -void PKGViewer::CheckPKGFolders() { // Check for new PKG file additions. - m_pkg_list.clear(); - for (const QString& dir : dir_list) { - QDir directory(dir); - QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files); - for (const QFileInfo& fileInfo : fileInfoList) { - QString file_ext = fileInfo.suffix(); - if (fileInfo.isFile() && file_ext == "pkg") { - m_pkg_list.append(fileInfo.absoluteFilePath()); - } - } - } - std::sort(m_pkg_list.begin(), m_pkg_list.end()); -} - -void PKGViewer::ProcessPKGInfo() { - treeWidget->clear(); - map_strings.clear(); - map_integers.clear(); - m_pkg_app_list.clear(); - m_pkg_patch_list.clear(); - m_full_pkg_list.clear(); - for (int i = 0; i < m_pkg_list.size(); i++) { - std::filesystem::path path = Common::FS::PathFromQString(m_pkg_list[i]); - std::string failreason; - if (!package.Open(path, failreason)) { - QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); - return; - } - psf.Open(package.sfo); - QString title_name = QString::fromStdString( - std::string{psf.GetString("TITLE").value_or(std::string{tr("Unknown").toStdString()})}); - QString title_id = QString::fromStdString(std::string{ - psf.GetString("TITLE_ID").value_or(std::string{tr("Unknown").toStdString()})}); - QString app_type = GameListUtils::GetAppType(psf.GetInteger("APP_TYPE").value_or(0)); - QString app_version = QString::fromStdString(std::string{ - psf.GetString("APP_VER").value_or(std::string{tr("Unknown").toStdString()})}); - QString title_category = QString::fromStdString(std::string{ - psf.GetString("CATEGORY").value_or(std::string{tr("Unknown").toStdString()})}); - QString pkg_size = GameListUtils::FormatSize(package.GetPkgHeader().pkg_size); - pkg_content_flag = package.GetPkgHeader().pkg_content_flags; - QString flagss = ""; - for (const auto& flag : package.flagNames) { - if (package.isFlagSet(pkg_content_flag, flag.first)) { - if (!flagss.isEmpty()) - flagss += (", "); - flagss += QString::fromStdString(flag.second.data()); - } - } - - QString fw_ = tr("Unknown"); - if (const auto fw_int_opt = psf.GetInteger("SYSTEM_VER"); fw_int_opt.has_value()) { - const u32 fw_int = *fw_int_opt; - if (fw_int == 0) { - fw_ = "0.00"; - } else { - QString fw = QString::number(fw_int, 16); - fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') - : fw.left(3).insert(1, '.'); - } - } - char region = package.GetPkgHeader().pkg_content_id[0]; - QString pkg_info = ""; - if (title_category == "gd" && !flagss.contains("PATCH")) { - title_category = "App"; - pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category + - ";;" + app_type + ";;" + app_version + ";;" + fw_ + ";;" + - game_list_util.GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i]; - m_pkg_app_list.append(pkg_info); - } else { - title_category = "Patch"; - pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category + - ";;" + app_type + ";;" + app_version + ";;" + fw_ + ";;" + - game_list_util.GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i]; - m_pkg_patch_list.append(pkg_info); - } - } - std::sort(m_pkg_app_list.begin(), m_pkg_app_list.end()); - for (int i = 0; i < m_pkg_app_list.size(); i++) { - QTreeWidgetItem* treeItem = new QTreeWidgetItem(treeWidget); - QStringList pkg_app_ = m_pkg_app_list[i].split(";;"); - m_full_pkg_list.append(m_pkg_app_list[i]); - treeItem->setExpanded(true); - treeItem->setText(0, pkg_app_[0]); - treeItem->setText(1, pkg_app_[1]); - treeItem->setText(3, pkg_app_[2]); - treeItem->setTextAlignment(3, Qt::AlignCenter); - treeItem->setText(4, pkg_app_[3]); - treeItem->setTextAlignment(4, Qt::AlignCenter); - treeItem->setText(5, pkg_app_[4]); - treeItem->setTextAlignment(5, Qt::AlignCenter); - treeItem->setText(6, pkg_app_[5]); - treeItem->setTextAlignment(6, Qt::AlignCenter); - treeItem->setText(7, pkg_app_[6]); - treeItem->setTextAlignment(7, Qt::AlignCenter); - treeItem->setText(8, pkg_app_[7]); - treeItem->setTextAlignment(8, Qt::AlignCenter); - treeItem->setText(9, pkg_app_[8]); - treeItem->setText(10, pkg_app_[9]); - for (const GameInfo& info : m_game_info->m_games) { // Check if game is installed. - if (info.serial == pkg_app_[1].toStdString()) { - treeItem->setText(2, QChar(0x2713)); - treeItem->setTextAlignment(2, Qt::AlignCenter); - } - } - for (const QString& item : m_pkg_patch_list) { - QStringList pkg_patch_ = item.split(";;"); - if (pkg_patch_[1] == pkg_app_[1]) { // check patches with serial. - m_full_pkg_list.append(item); - QTreeWidgetItem* childItem = new QTreeWidgetItem(treeItem); - childItem->setText(0, pkg_patch_[0]); - childItem->setText(1, pkg_patch_[1]); - childItem->setText(3, pkg_patch_[2]); - childItem->setTextAlignment(3, Qt::AlignCenter); - childItem->setText(4, pkg_patch_[3]); - childItem->setTextAlignment(4, Qt::AlignCenter); - childItem->setText(5, pkg_patch_[4]); - childItem->setTextAlignment(5, Qt::AlignCenter); - childItem->setText(6, pkg_patch_[5]); - childItem->setTextAlignment(6, Qt::AlignCenter); - childItem->setText(7, pkg_patch_[6]); - childItem->setTextAlignment(7, Qt::AlignCenter); - childItem->setText(8, pkg_patch_[7]); - childItem->setTextAlignment(8, Qt::AlignCenter); - childItem->setText(9, pkg_patch_[8]); - childItem->setText(10, pkg_patch_[9]); - } - } - } - - for (int column = 0; column < treeWidget->columnCount() - 2; ++column) { - // Resize the column to fit its contents - treeWidget->resizeColumnToContents(column); - } - // Update status bar. - statusBar->clearMessage(); - int numPkgs = m_pkg_list.size(); - QString statusMessage = QString::number(numPkgs) + " " + tr("Package"); - statusBar->showMessage(statusMessage); -} \ No newline at end of file diff --git a/src/qt_gui/pkg_viewer.h b/src/qt_gui/pkg_viewer.h deleted file mode 100644 index 265a03b92..000000000 --- a/src/qt_gui/pkg_viewer.h +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#include "common/io_file.h" -#include "core/file_format/pkg.h" -#include "core/file_format/pkg_type.h" -#include "core/file_format/psf.h" -#include "game_info.h" -#include "game_list_utils.h" -#include "gui_context_menus.h" - -class PKGViewer : public QMainWindow { - Q_OBJECT -public: - explicit PKGViewer( - std::shared_ptr game_info_get, QWidget* parent, - std::function InstallDragDropPkg = nullptr); - ~PKGViewer(); - void OpenPKGFolder(); - void CheckPKGFolders(); - void ProcessPKGInfo(); - -private: - GuiContextMenus m_gui_context_menus; - PKG package; - PSF psf; - PKGHeader pkgheader; - PKGEntry entry; - PSFHeader header; - char pkgTitleID[9]; - std::vector pkg; - u64 pkgSize = 0; - std::unordered_map map_strings; - std::unordered_map map_integers; - - u32_be pkg_content_flag; - std::shared_ptr m_game_info; - GameListUtils game_list_util; - // Status bar - QStatusBar* statusBar; - - std::vector> appTypes = { - {0, "FULL APP"}, - {1, "UPGRADABLE"}, - {2, "DEMO"}, - {3, "FREEMIUM"}, - }; - - QStringList m_full_pkg_list; - QStringList m_pkg_app_list; - QStringList m_pkg_patch_list; - QStringList m_pkg_list; - QStringList dir_list; - std::vector dir_list_std; - QTreeWidget* treeWidget = nullptr; -}; \ No newline at end of file From 31e1d4f839118b59398ca6f871929fc0e286e13c Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 28 Mar 2025 10:32:17 -0700 Subject: [PATCH 079/194] misc: Remove dead code. (#2702) --- CMakeLists.txt | 4 - documents/Quickstart/2.png | Bin 674148 -> 0 bytes documents/Quickstart/Quickstart.md | 9 +- documents/building-linux.md | 2 +- src/common/config.cpp | 12 - src/common/config.h | 2 - src/core/file_format/pkg.cpp | 473 ----------------------------- src/core/loader.cpp | 28 -- src/core/loader.h | 18 -- src/qt_gui/gui_context_menus.h | 25 -- src/qt_gui/install_dir_select.cpp | 94 ------ src/qt_gui/install_dir_select.h | 42 --- src/qt_gui/main_window.cpp | 2 - 13 files changed, 2 insertions(+), 709 deletions(-) delete mode 100644 documents/Quickstart/2.png delete mode 100644 src/core/file_format/pkg.cpp delete mode 100644 src/core/loader.cpp delete mode 100644 src/core/loader.h delete mode 100644 src/qt_gui/install_dir_select.cpp delete mode 100644 src/qt_gui/install_dir_select.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0359246c8..13204f479 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -678,8 +678,6 @@ set(CORE src/core/aerolib/stubs.cpp src/core/file_format/trp.h src/core/file_sys/fs.cpp src/core/file_sys/fs.h - src/core/loader.cpp - src/core/loader.h src/core/loader/dwarf.cpp src/core/loader/dwarf.h src/core/loader/elf.cpp @@ -969,8 +967,6 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/game_grid_frame.h src/qt_gui/game_install_dialog.cpp src/qt_gui/game_install_dialog.h - src/qt_gui/install_dir_select.cpp - src/qt_gui/install_dir_select.h src/qt_gui/trophy_viewer.cpp src/qt_gui/trophy_viewer.h src/qt_gui/elf_viewer.cpp diff --git a/documents/Quickstart/2.png b/documents/Quickstart/2.png deleted file mode 100644 index 7e5bdfb155e572865777f38ef686257cedcc60de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 674148 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%0D&Jaj*t^~An4&Mk>ql6akxljo4M#GxIb)kg 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%0D`scYI^AhKK6L+2_lMh$J2X{*r%hQ#Zf)uO(QM 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 bYD)h<14Wu<5jwU300000NkvXXu0mjf4H#`p diff --git a/documents/Quickstart/Quickstart.md b/documents/Quickstart/Quickstart.md index 2f2751887..9c6bc5a6f 100644 --- a/documents/Quickstart/Quickstart.md +++ b/documents/Quickstart/Quickstart.md @@ -13,7 +13,6 @@ SPDX-License-Identifier: GPL-2.0-or-later - [**RAM**](#ram) - [**OS**](#os) - [**Have the latest WIP version**](#how-to-run-the-latest-work-in-progress-builds-of-shadps4) -- [**Install PKG files (Games and Updates)**](#install-pkg-files) - [**Configure the emulator**](#configure-the-emulator) ## Minimum PC requirements @@ -48,13 +47,7 @@ SPDX-License-Identifier: GPL-2.0-or-later 2. Once downloaded, extract to its own folder, and run shadPS4's executable from the extracted folder. -3. Upon first launch, shadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that shadPS4 can use to install your PKG files to. - -## Install PKG files - -To install PKG files (game and updates), you will need the Qt application (with UI). You will have to go to "File" then to "Install Packages (PKG)", a window will open then you will have to select the files. You can install multiple PKG files at once. Once finished, the game should appear in the application. - - +3. Upon first launch, shadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that contains your dumped games. ## Configure the emulator diff --git a/documents/building-linux.md b/documents/building-linux.md index 18ddab0c6..cdc8ba12f 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -108,7 +108,7 @@ Now run the emulator. If Qt was enabled at configure time: ./build/shadps4 ``` -Otherwise, specify the path to your PKG's boot file: +Otherwise, specify the path to your game's boot file: ```bash ./build/shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin diff --git a/src/common/config.cpp b/src/common/config.cpp index b113ac0ef..8ead58686 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -98,7 +98,6 @@ u32 m_slider_pos_grid = 0; u32 m_table_mode = 0; u32 m_window_size_W = 1280; u32 m_window_size_H = 720; -std::vector m_pkg_viewer; std::vector m_elf_viewer; std::vector m_recent_files; std::string emulator_language = "en_US"; @@ -601,11 +600,6 @@ void setMainWindowHeight(u32 height) { m_window_size_H = height; } -void setPkgViewer(const std::vector& pkgList) { - m_pkg_viewer.resize(pkgList.size()); - m_pkg_viewer = pkgList; -} - void setElfViewer(const std::vector& elfList) { m_elf_viewer.resize(elfList.size()); m_elf_viewer = elfList; @@ -709,10 +703,6 @@ u32 getMainWindowHeight() { return m_window_size_H; } -std::vector getPkgViewer() { - return m_pkg_viewer; -} - std::vector getElfViewer() { return m_elf_viewer; } @@ -886,7 +876,6 @@ void load(const std::filesystem::path& path) { main_window_geometry_y = toml::find_or(gui, "geometry_y", 0); main_window_geometry_w = toml::find_or(gui, "geometry_w", 0); main_window_geometry_h = toml::find_or(gui, "geometry_h", 0); - m_pkg_viewer = toml::find_or>(gui, "pkgDirs", {}); m_elf_viewer = toml::find_or>(gui, "elfDirs", {}); m_recent_files = toml::find_or>(gui, "recentFiles", {}); m_table_mode = toml::find_or(gui, "gameTableMode", 0); @@ -1105,7 +1094,6 @@ void saveMainWindow(const std::filesystem::path& path) { data["GUI"]["geometry_y"] = main_window_geometry_y; data["GUI"]["geometry_w"] = main_window_geometry_w; data["GUI"]["geometry_h"] = main_window_geometry_h; - data["GUI"]["pkgDirs"] = m_pkg_viewer; data["GUI"]["elfDirs"] = m_elf_viewer; data["GUI"]["recentFiles"] = m_recent_files; diff --git a/src/common/config.h b/src/common/config.h index 3a0bf252c..d040aa337 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -154,7 +154,6 @@ void setSliderPositionGrid(u32 pos); void setTableMode(u32 mode); void setMainWindowWidth(u32 width); void setMainWindowHeight(u32 height); -void setPkgViewer(const std::vector& pkgList); void setElfViewer(const std::vector& elfList); void setRecentFiles(const std::vector& recentFiles); void setEmulatorLanguage(std::string language); @@ -174,7 +173,6 @@ u32 getSliderPositionGrid(); u32 getTableMode(); u32 getMainWindowWidth(); u32 getMainWindowHeight(); -std::vector getPkgViewer(); std::vector getElfViewer(); std::vector getRecentFiles(); std::string getEmulatorLanguage(); diff --git a/src/core/file_format/pkg.cpp b/src/core/file_format/pkg.cpp deleted file mode 100644 index ecc5f10a4..000000000 --- a/src/core/file_format/pkg.cpp +++ /dev/null @@ -1,473 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "common/io_file.h" -#include "common/logging/formatter.h" -#include "core/file_format/pkg.h" -#include "core/file_format/pkg_type.h" - -static void DecompressPFSC(std::span compressed_data, std::span decompressed_data) { - z_stream decompressStream; - decompressStream.zalloc = Z_NULL; - decompressStream.zfree = Z_NULL; - decompressStream.opaque = Z_NULL; - - if (inflateInit(&decompressStream) != Z_OK) { - // std::cerr << "Error initializing zlib for deflation." << std::endl; - } - - decompressStream.avail_in = compressed_data.size(); - decompressStream.next_in = reinterpret_cast(compressed_data.data()); - decompressStream.avail_out = decompressed_data.size(); - decompressStream.next_out = reinterpret_cast(decompressed_data.data()); - - if (inflate(&decompressStream, Z_FINISH)) { - } - if (inflateEnd(&decompressStream) != Z_OK) { - // std::cerr << "Error ending zlib inflate" << std::endl; - } -} - -u32 GetPFSCOffset(std::span pfs_image) { - static constexpr u32 PfscMagic = 0x43534650; - u32 value; - for (u32 i = 0x20000; i < pfs_image.size(); i += 0x10000) { - std::memcpy(&value, &pfs_image[i], sizeof(u32)); - if (value == PfscMagic) - return i; - } - return -1; -} - -PKG::PKG() = default; - -PKG::~PKG() = default; - -bool PKG::Open(const std::filesystem::path& filepath, std::string& failreason) { - Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - pkgSize = file.GetSize(); - - file.Read(pkgheader); - if (pkgheader.magic != 0x7F434E54) - return false; - - for (const auto& flag : flagNames) { - if (isFlagSet(pkgheader.pkg_content_flags, flag.first)) { - if (!pkgFlags.empty()) - pkgFlags += (", "); - pkgFlags += (flag.second); - } - } - - // Find title id it is part of pkg_content_id starting at offset 0x40 - file.Seek(0x47); // skip first 7 characters of content_id - file.Read(pkgTitleID); - - u32 offset = pkgheader.pkg_table_entry_offset; - u32 n_files = pkgheader.pkg_table_entry_count; - - if (!file.Seek(offset)) { - failreason = "Failed to seek to PKG table entry offset"; - return false; - } - - for (int i = 0; i < n_files; i++) { - PKGEntry entry{}; - file.Read(entry.id); - file.Read(entry.filename_offset); - file.Read(entry.flags1); - file.Read(entry.flags2); - file.Read(entry.offset); - file.Read(entry.size); - file.Seek(8, Common::FS::SeekOrigin::CurrentPosition); - - // Try to figure out the name - const auto name = GetEntryNameByType(entry.id); - if (name == "param.sfo") { - sfo.clear(); - if (!file.Seek(entry.offset)) { - failreason = "Failed to seek to param.sfo offset"; - return false; - } - sfo.resize(entry.size); - file.ReadRaw(sfo.data(), entry.size); - } - } - file.Close(); - - return true; -} - -bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract, - std::string& failreason) { - extract_path = extract; - pkgpath = filepath; - Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - pkgSize = file.GetSize(); - file.ReadRaw(&pkgheader, sizeof(PKGHeader)); - - if (pkgheader.magic != 0x7F434E54) - return false; - - if (pkgheader.pkg_size > pkgSize) { - failreason = "PKG file size is different"; - return false; - } - if ((pkgheader.pkg_content_size + pkgheader.pkg_content_offset) > pkgheader.pkg_size) { - failreason = "Content size is bigger than pkg size"; - return false; - } - - u32 offset = pkgheader.pkg_table_entry_offset; - u32 n_files = pkgheader.pkg_table_entry_count; - - std::array concatenated_ivkey_dk3; - std::array seed_digest; - std::array, 7> digest1; - std::array, 7> key1; - std::array imgkeydata; - - if (!file.Seek(offset)) { - failreason = "Failed to seek to PKG table entry offset"; - return false; - } - - for (int i = 0; i < n_files; i++) { - PKGEntry entry{}; - file.Read(entry.id); - file.Read(entry.filename_offset); - file.Read(entry.flags1); - file.Read(entry.flags2); - file.Read(entry.offset); - file.Read(entry.size); - file.Seek(8, Common::FS::SeekOrigin::CurrentPosition); - - auto currentPos = file.Tell(); - - // Try to figure out the name - const auto name = GetEntryNameByType(entry.id); - const auto filepath = extract_path / "sce_sys" / name; - std::filesystem::create_directories(filepath.parent_path()); - - if (name.empty()) { - // Just print with id - Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id), - Common::FS::FileAccessMode::Write); - if (!file.Seek(entry.offset)) { - failreason = "Failed to seek to PKG entry offset"; - return false; - } - - std::vector data; - data.resize(entry.size); - file.ReadRaw(data.data(), entry.size); - out.WriteRaw(data.data(), entry.size); - out.Close(); - - file.Seek(currentPos); - continue; - } - - if (entry.id == 0x1) { // DIGESTS, seek; - // file.Seek(entry.offset, fsSeekSet); - } else if (entry.id == 0x10) { // ENTRY_KEYS, seek; - file.Seek(entry.offset); - file.Read(seed_digest); - - for (int i = 0; i < 7; i++) { - file.Read(digest1[i]); - } - - for (int i = 0; i < 7; i++) { - file.Read(key1[i]); - } - - PKG::crypto.RSA2048Decrypt(dk3_, key1[3], true); // decrypt DK3 - } else if (entry.id == 0x20) { // IMAGE_KEY, seek; IV_KEY - file.Seek(entry.offset); - file.Read(imgkeydata); - - // The Concatenated iv + dk3 imagekey for HASH256 - std::memcpy(concatenated_ivkey_dk3.data(), &entry, sizeof(entry)); - std::memcpy(concatenated_ivkey_dk3.data() + sizeof(entry), dk3_.data(), sizeof(dk3_)); - - PKG::crypto.ivKeyHASH256(concatenated_ivkey_dk3, ivKey); // ivkey_ - // imgkey_ to use for last step to get ekpfs - PKG::crypto.aesCbcCfb128Decrypt(ivKey, imgkeydata, imgKey); - // ekpfs key to get data and tweak keys. - PKG::crypto.RSA2048Decrypt(ekpfsKey, imgKey, false); - } else if (entry.id == 0x80) { - // GENERAL_DIGESTS, seek; - // file.Seek(entry.offset, fsSeekSet); - } - - Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write); - if (!file.Seek(entry.offset)) { - failreason = "Failed to seek to PKG entry offset"; - return false; - } - - std::vector data; - data.resize(entry.size); - file.ReadRaw(data.data(), entry.size); - out.WriteRaw(data.data(), entry.size); - out.Close(); - - // Decrypt Np stuff and overwrite. - if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 || - entry.id == 0x403) { // somehow 0x401 is not decrypting - decNp.resize(entry.size); - if (!file.Seek(entry.offset)) { - failreason = "Failed to seek to PKG entry offset"; - return false; - } - - std::vector data; - data.resize(entry.size); - file.ReadRaw(data.data(), entry.size); - - std::span cipherNp(data.data(), entry.size); - std::array concatenated_ivkey_dk3_; - std::memcpy(concatenated_ivkey_dk3_.data(), &entry, sizeof(entry)); - std::memcpy(concatenated_ivkey_dk3_.data() + sizeof(entry), dk3_.data(), sizeof(dk3_)); - PKG::crypto.ivKeyHASH256(concatenated_ivkey_dk3_, ivKey); - PKG::crypto.aesCbcCfb128DecryptEntry(ivKey, cipherNp, decNp); - - Common::FS::IOFile out(extract_path / "sce_sys" / name, - Common::FS::FileAccessMode::Write); - out.Write(decNp); - out.Close(); - } - - file.Seek(currentPos); - } - - // Read the seed - std::array seed; - if (!file.Seek(pkgheader.pfs_image_offset + 0x370)) { - failreason = "Failed to seek to PFS image offset"; - return false; - } - file.Read(seed); - - // Get data and tweak keys. - PKG::crypto.PfsGenCryptoKey(ekpfsKey, seed, dataKey, tweakKey); - const u32 length = pkgheader.pfs_cache_size * 0x2; // Seems to be ok. - - int num_blocks = 0; - std::vector pfsc(length); - if (length != 0) { - // Read encrypted pfs_image - std::vector pfs_encrypted(length); - file.Seek(pkgheader.pfs_image_offset); - file.Read(pfs_encrypted); - file.Close(); - // Decrypt the pfs_image. - std::vector pfs_decrypted(length); - PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0); - - // Retrieve PFSC from decrypted pfs_image. - pfsc_offset = GetPFSCOffset(pfs_decrypted); - std::memcpy(pfsc.data(), pfs_decrypted.data() + pfsc_offset, length - pfsc_offset); - - PFSCHdr pfsChdr; - std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr)); - - num_blocks = (int)(pfsChdr.data_length / pfsChdr.block_sz2); - sectorMap.resize(num_blocks + 1); // 8 bytes, need extra 1 to get the last offset. - - for (int i = 0; i < num_blocks + 1; i++) { - std::memcpy(§orMap[i], pfsc.data() + pfsChdr.block_offsets + i * 8, 8); - } - } - - u32 ent_size = 0; - u32 ndinode = 0; - int ndinode_counter = 0; - bool dinode_reached = false; - bool uroot_reached = false; - std::vector compressedData; - std::vector decompressedData(0x10000); - - // Get iNdoes and Dirents. - for (int i = 0; i < num_blocks; i++) { - const u64 sectorOffset = sectorMap[i]; - const u64 sectorSize = sectorMap[i + 1] - sectorOffset; - - compressedData.resize(sectorSize); - std::memcpy(compressedData.data(), pfsc.data() + sectorOffset, sectorSize); - - if (sectorSize == 0x10000) // Uncompressed data - std::memcpy(decompressedData.data(), compressedData.data(), 0x10000); - else if (sectorSize < 0x10000) // Compressed data - DecompressPFSC(compressedData, decompressedData); - - if (i == 0) { - std::memcpy(&ndinode, decompressedData.data() + 0x30, 4); // number of folders and files - } - - int occupied_blocks = - (ndinode * 0xA8) / 0x10000; // how many blocks(0x10000) are taken by iNodes. - if (((ndinode * 0xA8) % 0x10000) != 0) - occupied_blocks += 1; - - if (i >= 1 && i <= occupied_blocks) { // Get all iNodes, gives type, file size and location. - for (int p = 0; p < 0x10000; p += 0xA8) { - Inode node; - std::memcpy(&node, &decompressedData[p], sizeof(node)); - if (node.Mode == 0) { - break; - } - iNodeBuf.push_back(node); - } - } - - // let's deal with the root/uroot entries here. - // Sometimes it's more than 2 entries (Tomb Raider Remastered) - const std::string_view flat_path_table(&decompressedData[0x10], 15); - if (flat_path_table == "flat_path_table") { - uroot_reached = true; - } - - if (uroot_reached) { - for (int i = 0; i < 0x10000; i += ent_size) { - Dirent dirent; - std::memcpy(&dirent, &decompressedData[i], sizeof(dirent)); - ent_size = dirent.entsize; - if (dirent.ino != 0) { - ndinode_counter++; - } else { - // Set the the folder according to the current inode. - // Can be 2 or more (rarely) - auto parent_path = extract_path.parent_path(); - auto title_id = GetTitleID(); - - if (parent_path.filename() != title_id && - !fmt::UTF(extract_path.u8string()).data.ends_with("-patch")) { - extractPaths[ndinode_counter] = parent_path / title_id; - } else { - // DLCs path has different structure - extractPaths[ndinode_counter] = extract_path; - } - uroot_reached = false; - break; - } - } - } - - const char dot = decompressedData[0x10]; - const std::string_view dotdot(&decompressedData[0x28], 2); - if (dot == '.' && dotdot == "..") { - dinode_reached = true; - } - - // Get folder and file names. - bool end_reached = false; - if (dinode_reached) { - for (int j = 0; j < 0x10000; j += ent_size) { // Skip the first parent and child. - Dirent dirent; - std::memcpy(&dirent, &decompressedData[j], sizeof(dirent)); - - // Stop here and continue the main loop - if (dirent.ino == 0) { - break; - } - - ent_size = dirent.entsize; - auto& table = fsTable.emplace_back(); - table.name = std::string(dirent.name, dirent.namelen); - table.inode = dirent.ino; - table.type = dirent.type; - - if (table.type == PFS_CURRENT_DIR) { - current_dir = extractPaths[table.inode]; - } - extractPaths[table.inode] = current_dir / std::filesystem::path(table.name); - - if (table.type == PFS_FILE || table.type == PFS_DIR) { - if (table.type == PFS_DIR) { // Create dirs. - std::filesystem::create_directory(extractPaths[table.inode]); - } - ndinode_counter++; - if ((ndinode_counter + 1) == ndinode) // 1 for the image itself (root). - end_reached = true; - } - } - if (end_reached) { - break; - } - } - } - return true; -} - -void PKG::ExtractFiles(const int index) { - int inode_number = fsTable[index].inode; - int inode_type = fsTable[index].type; - std::string inode_name = fsTable[index].name; - - if (inode_type == PFS_FILE) { - int sector_loc = iNodeBuf[inode_number].loc; - int nblocks = iNodeBuf[inode_number].Blocks; - int bsize = iNodeBuf[inode_number].Size; - - Common::FS::IOFile inflated; - inflated.Open(extractPaths[inode_number], Common::FS::FileAccessMode::Write); - - Common::FS::IOFile pkgFile; // Open the file for each iteration to avoid conflict. - pkgFile.Open(pkgpath, Common::FS::FileAccessMode::Read); - - int size_decompressed = 0; - std::vector compressedData; - std::vector decompressedData(0x10000); - - u64 pfsc_buf_size = 0x11000; // extra 0x1000 - std::vector pfsc(pfsc_buf_size); - std::vector pfs_decrypted(pfsc_buf_size); - - for (int j = 0; j < nblocks; j++) { - u64 sectorOffset = - sectorMap[sector_loc + j]; // offset into PFSC_image and not pfs_image. - u64 sectorSize = sectorMap[sector_loc + j + 1] - - sectorOffset; // indicates if data is compressed or not. - u64 fileOffset = (pkgheader.pfs_image_offset + pfsc_offset + sectorOffset); - u64 currentSector1 = - (pfsc_offset + sectorOffset) / 0x1000; // block size is 0x1000 for xts decryption. - - int sectorOffsetMask = (sectorOffset + pfsc_offset) & 0xFFFFF000; - int previousData = (sectorOffset + pfsc_offset) - sectorOffsetMask; - - pkgFile.Seek(fileOffset - previousData); - pkgFile.Read(pfsc); - - PKG::crypto.decryptPFS(dataKey, tweakKey, pfsc, pfs_decrypted, currentSector1); - - compressedData.resize(sectorSize); - std::memcpy(compressedData.data(), pfs_decrypted.data() + previousData, sectorSize); - - if (sectorSize == 0x10000) // Uncompressed data - std::memcpy(decompressedData.data(), compressedData.data(), 0x10000); - else if (sectorSize < 0x10000) // Compressed data - DecompressPFSC(compressedData, decompressedData); - - size_decompressed += 0x10000; - - if (j < nblocks - 1) { - inflated.WriteRaw(decompressedData.data(), decompressedData.size()); - } else { - // This is to remove the zeros at the end of the file. - const u32 write_size = decompressedData.size() - (size_decompressed - bsize); - inflated.WriteRaw(decompressedData.data(), write_size); - } - } - pkgFile.Close(); - inflated.Close(); - } -} diff --git a/src/core/loader.cpp b/src/core/loader.cpp deleted file mode 100644 index f80bfbb81..000000000 --- a/src/core/loader.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/io_file.h" -#include "common/types.h" -#include "loader.h" - -namespace Loader { - -FileTypes DetectFileType(const std::filesystem::path& filepath) { - // No file loaded - if (filepath.empty()) { - return FileTypes::Unknown; - } - Common::FS::IOFile file; - file.Open(filepath, Common::FS::FileAccessMode::Read); - file.Seek(0); - u32 magic; - file.Read(magic); - file.Close(); - switch (magic) { - case PkgMagic: - return FileTypes::Pkg; - } - return FileTypes::Unknown; -} - -} // namespace Loader diff --git a/src/core/loader.h b/src/core/loader.h deleted file mode 100644 index 608970dca..000000000 --- a/src/core/loader.h +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -namespace Loader { - -constexpr static u32 PkgMagic = 0x544e437f; - -enum class FileTypes { - Unknown, - Pkg, -}; - -FileTypes DetectFileType(const std::filesystem::path& filepath); -} // namespace Loader diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 0cc0e48dc..7dcb006ba 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -599,30 +598,6 @@ public: return -1; } - void RequestGameMenuPKGViewer( - const QPoint& pos, QStringList m_pkg_app_list, QTreeWidget* treeWidget, - std::function InstallDragDropPkg) { - QPoint global_pos = treeWidget->viewport()->mapToGlobal(pos); // context menu position - QTreeWidgetItem* currentItem = treeWidget->currentItem(); // current clicked item - int itemIndex = GetRowIndex(treeWidget, currentItem); // row - - QMenu menu(treeWidget); - QAction installPackage(tr("Install PKG"), treeWidget); - - menu.addAction(&installPackage); - - auto selected = menu.exec(global_pos); - if (!selected) { - return; - } - - if (selected == &installPackage) { - QStringList pkg_app_ = m_pkg_app_list[itemIndex].split(";;"); - std::filesystem::path path = Common::FS::PathFromQString(pkg_app_[9]); - InstallDragDropPkg(path, 1, 1); - } - } - private: bool convertPngToIco(const QString& pngFilePath, const QString& icoFilePath) { // Load the PNG image diff --git a/src/qt_gui/install_dir_select.cpp b/src/qt_gui/install_dir_select.cpp deleted file mode 100644 index e90a10ee6..000000000 --- a/src/qt_gui/install_dir_select.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "install_dir_select.h" - -InstallDirSelect::InstallDirSelect() : selected_dir() { - auto install_dirs = Config::getGameInstallDirs(); - selected_dir = install_dirs.empty() ? "" : install_dirs.front(); - - if (!install_dirs.empty() && install_dirs.size() == 1) { - accept(); - } - - auto layout = new QVBoxLayout(this); - - layout->addWidget(SetupInstallDirList()); - layout->addStretch(); - layout->addWidget(SetupDialogActions()); - - setWindowTitle(tr("shadPS4 - Choose directory")); - setWindowIcon(QIcon(":images/shadps4.ico")); -} - -InstallDirSelect::~InstallDirSelect() {} - -QWidget* InstallDirSelect::SetupInstallDirList() { - auto group = new QGroupBox(tr("Select which directory you want to install to.")); - auto vlayout = new QVBoxLayout(); - - auto m_path_list = new QListWidget(); - QList qt_list; - for (const auto& str : Config::getGameInstallDirs()) { - QString installDirPath; - Common::FS::PathToQString(installDirPath, str); - qt_list.append(installDirPath); - } - m_path_list->insertItems(0, qt_list); - m_path_list->setSpacing(1); - - connect(m_path_list, &QListWidget::itemClicked, this, &InstallDirSelect::setSelectedDirectory); - connect(m_path_list, &QListWidget::itemActivated, this, - &InstallDirSelect::setSelectedDirectory); - - vlayout->addWidget(m_path_list); - - auto checkbox = new QCheckBox(tr("Install All Queued to Selected Folder")); - connect(checkbox, &QCheckBox::toggled, this, &InstallDirSelect::setUseForAllQueued); - vlayout->addWidget(checkbox); - - auto checkbox2 = new QCheckBox(tr("Delete PKG File on Install")); - connect(checkbox2, &QCheckBox::toggled, this, &InstallDirSelect::setDeleteFileOnInstall); - vlayout->addWidget(checkbox2); - - group->setLayout(vlayout); - return group; -} - -void InstallDirSelect::setSelectedDirectory(QListWidgetItem* item) { - if (item) { - const auto highlighted_path = Common::FS::PathFromQString(item->text()); - if (!highlighted_path.empty()) { - selected_dir = highlighted_path; - } - } -} - -void InstallDirSelect::setUseForAllQueued(bool enabled) { - use_for_all_queued = enabled; -} - -void InstallDirSelect::setDeleteFileOnInstall(bool enabled) { - delete_file_on_install = enabled; -} - -QWidget* InstallDirSelect::SetupDialogActions() { - auto actions = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - connect(actions, &QDialogButtonBox::accepted, this, &InstallDirSelect::accept); - connect(actions, &QDialogButtonBox::rejected, this, &InstallDirSelect::reject); - - return actions; -} diff --git a/src/qt_gui/install_dir_select.h b/src/qt_gui/install_dir_select.h deleted file mode 100644 index e11cbf381..000000000 --- a/src/qt_gui/install_dir_select.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include "common/config.h" -#include "common/path_util.h" - -class QLineEdit; - -class InstallDirSelect final : public QDialog { - Q_OBJECT - -public: - InstallDirSelect(); - ~InstallDirSelect(); - - std::filesystem::path getSelectedDirectory() { - return selected_dir; - } - - bool useForAllQueued() { - return use_for_all_queued; - } - - bool deleteFileOnInstall() { - return delete_file_on_install; - } - -private: - QWidget* SetupInstallDirList(); - QWidget* SetupDialogActions(); - void setSelectedDirectory(QListWidgetItem* item); - void setDeleteFileOnInstall(bool enabled); - void setUseForAllQueued(bool enabled); - std::filesystem::path selected_dir; - bool delete_file_on_install = false; - bool use_for_all_queued = false; -}; diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index e92676c02..5d8f8e717 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -20,9 +20,7 @@ #include "common/string_util.h" #include "common/version.h" #include "control_settings.h" -#include "core/loader.h" #include "game_install_dialog.h" -#include "install_dir_select.h" #include "kbm_gui.h" #include "main_window.h" #include "settings_dialog.h" From 501f46e51518a70db4d394ebe0b1510f1e165179 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 28 Mar 2025 19:48:23 +0200 Subject: [PATCH 080/194] New Crowdin updates (#2695) * New translations en_us.ts (Albanian) * New translations en_us.ts (Polish) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Arabic) * New translations en_us.ts (Turkish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Russian) * New translations en_us.ts (Finnish) * New translations en_us.ts (Finnish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Arabic) --- src/qt_gui/translations/ar_SA.ts | 42 ++++++++- src/qt_gui/translations/da_DK.ts | 40 ++++++++ src/qt_gui/translations/de_DE.ts | 40 ++++++++ src/qt_gui/translations/el_GR.ts | 40 ++++++++ src/qt_gui/translations/es_ES.ts | 40 ++++++++ src/qt_gui/translations/fa_IR.ts | 40 ++++++++ src/qt_gui/translations/fi_FI.ts | 156 +++++++++++++++++++------------ src/qt_gui/translations/fr_FR.ts | 40 ++++++++ src/qt_gui/translations/hu_HU.ts | 40 ++++++++ src/qt_gui/translations/id_ID.ts | 40 ++++++++ src/qt_gui/translations/it_IT.ts | 46 ++++++++- src/qt_gui/translations/ja_JP.ts | 40 ++++++++ src/qt_gui/translations/ko_KR.ts | 40 ++++++++ src/qt_gui/translations/lt_LT.ts | 40 ++++++++ src/qt_gui/translations/nb_NO.ts | 40 ++++++++ src/qt_gui/translations/nl_NL.ts | 40 ++++++++ src/qt_gui/translations/pl_PL.ts | 40 ++++++++ src/qt_gui/translations/pt_BR.ts | 42 ++++++++- src/qt_gui/translations/pt_PT.ts | 40 ++++++++ src/qt_gui/translations/ro_RO.ts | 40 ++++++++ src/qt_gui/translations/ru_RU.ts | 40 ++++++++ src/qt_gui/translations/sq_AL.ts | 40 ++++++++ src/qt_gui/translations/sv_SE.ts | 40 ++++++++ src/qt_gui/translations/tr_TR.ts | 40 ++++++++ src/qt_gui/translations/uk_UA.ts | 40 ++++++++ src/qt_gui/translations/vi_VN.ts | 40 ++++++++ src/qt_gui/translations/zh_CN.ts | 40 ++++++++ src/qt_gui/translations/zh_TW.ts | 40 ++++++++ 28 files changed, 1183 insertions(+), 63 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index ac6920ea0..9808fdbe6 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -1149,7 +1149,7 @@ Deadzone Offset (def 0.50): - + Deadzone Offset (def 0.50): Speed Multiplier (def 1.0): @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + أبدأ اللعب + + + Pause + توقف مؤقت + + + Stop + إيقاف + + + Restart + إعادة تشغيل + + + Full Screen + وضع ملء الشاشة + + + Controllers + أذرعة التحكم + + + Keyboard + لوحة المفاتيح + + + Refresh List + تحديث القائمة + + + Resume + استئناف + + + Show Labels Under Icons + إظهار العلامات أسفل الأيقونات + PKGViewer diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 1835ba84c..1547a0e13 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 6717a93ef..c0e43065b 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index 6e1adaac9..e6fa989aa 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 24844d0a2..288c445c3 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 6b7af042e..1b8813a80 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -1514,6 +1514,46 @@ shadPS4 ShadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 324cb6c49..cd880fb23 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -407,206 +407,206 @@ ControlSettings Configure Controls - Configure Controls + Määritä Kontrollit D-Pad - D-Pad + D-Pad Up - Up + Ylös Left - Left + Vasen Right - Right + Oikea Down - Down + Alas Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + Vasemman Analogin Deadzone (oletus:2 max:127) Left Deadzone - Left Deadzone + Vasen Deadzone Left Stick - Left Stick + Vasen Analogi Config Selection - Config Selection + Asetusten Valinta Common Config - Common Config + Yleinen Asetus Use per-game configs - Use per-game configs + Käytä pelikohtaisia asetuksia L1 / LB - L1 / LB + L1 / LB L2 / LT - L2 / LT + L2 / LT Back - Back + Back R1 / RB - R1 / RB + R1 / RB R2 / RT - R2 / RT + R2 / RT L3 - L3 + L3 Options / Start - Options / Start + Options / Start R3 - R3 + R3 Face Buttons - Face Buttons + Etunäppäimet Triangle / Y - Triangle / Y + Kolmio / Y Square / X - Square / X + Neliö / X Circle / B - Circle / B + Ympyrä / B Cross / A - Cross / A + Rasti / A Right Stick Deadzone (def:2, max:127) - Right Stick Deadzone (def:2, max:127) + Oikean Analogin Deadzone (oletus:2 max:127) Right Deadzone - Right Deadzone + Oikea Deadzone Right Stick - Right Stick + Oikea Analogi Color Adjustment - Color Adjustment + Värinhallinta R: - R: + R: G: - G: + G: B: - B: + B: Override Lightbar Color - Override Lightbar Color + Pakota Ohjaimen Valopalkin Väri Override Color - Override Color + Pakotettava Väri Unable to Save - Unable to Save + Tallentaminen Epäonnistui Cannot bind axis values more than once - Cannot bind axis values more than once + Akseliarvoja ei voi määrittää kertaa useammin Save - Save + Tallenna Apply - Apply + Ota Käyttöön Restore Defaults - Restore Defaults + Palauta Oletukset Cancel - Cancel + Peruuta EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Muokkaa Näppäimistön + Hiiren ja Ohjaimen näppäinasetuksia Use Per-Game configs - Use Per-Game configs + Käytä Pelikohtaisia Asetuksia Error - Error + Virhe Could not open the file for reading - Could not open the file for reading + Tiedostoa ei voitu avata luettavaksi Could not open the file for writing - Could not open the file for writing + Tiedostoa ei voitu avata kirjoitettavaksi Save Changes - Save Changes + Tallenna Muutokset Do you want to save changes? - Do you want to save changes? + Haluatko tallentaa muutokset? Help - Help + Tietoa Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Haluatko nollata oletusasetuksiin tekemäsi muutokset? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Haluato palauttaa nämä asetukset takaisin määrittämiisi oletuksiin? Reset to Default @@ -1077,35 +1077,35 @@ L3 - L3 + L3 Touchpad Click - Touchpad Click + Kosketuslevyn Klikkaus Mouse to Joystick - Mouse to Joystick + Hiiri Joystickinä *press F7 ingame to activate - *press F7 ingame to activate + *paina F7 pelissä aktivoidaksesi R3 - R3 + R3 Options - Options + Options Mouse Movement Parameters - Mouse Movement Parameters + Hiiren Liikkeen Parametrit note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + huomio: klikkaa apunappia/näppäintä saadaksesi lisää tietoa Face Buttons @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index ec3f9f8b5..1f7a726cd 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 6672337a6..8746058b3 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index e43d31976..5960715ae 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index e63da05b8..3e95e3df6 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1308,11 +1308,11 @@ Trophy Viewer - Trophy Viewer + Visualizzatore Trofei No games found. Please add your games to your library first. - No games found. Please add your games to your library first. + Nessun gioco trovato. Aggiungi prima i tuoi giochi alla tua libreria. PKG Viewer @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer @@ -2201,7 +2241,7 @@ Select Game: - Select Game: + Seleziona Gioco: Progress diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 7cf9fc5c2..1aaa3fa7c 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index d5289ace9..9dd06028d 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 17133da35..6e98ddc45 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index e8ce99f90..6faff415e 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 5c1725bd5..376eea5ef 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 033412efa..a77b43e09 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index d44efce5d..ea82086f3 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -912,7 +912,7 @@ Are you sure you want to delete %1's %2 directory? - Tem certeza de que deseja excluir o diretório %2 de %1? + Tem certeza de que deseja excluir o diretório do %2 %1? Open Update Folder @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Jogar + + + Pause + Pausar + + + Stop + Parar + + + Restart + Reiniciar + + + Full Screen + Tela Cheia + + + Controllers + Controles + + + Keyboard + Teclado + + + Refresh List + Atualizar Lista + + + Resume + Continuar + + + Show Labels Under Icons + Mostrar Rótulos Sob Ícones + PKGViewer diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index 455955fad..7ca3eebb5 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 9c7720e17..6e008ac20 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 68eaabc34..560c8c110 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Играть + + + Pause + Пауза + + + Stop + Остановить + + + Restart + Перезапустить + + + Full Screen + Полный экран + + + Controllers + Контроллеры + + + Keyboard + Клавиатура + + + Refresh List + Обновить список + + + Resume + Продолжить + + + Show Labels Under Icons + Показывать метки под значками + PKGViewer diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 657f78d0d..8a2c34c60 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index a002b150a..91b544e05 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 48ce4254d..4946874c9 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Başlat + + + Pause + Duraklat + + + Stop + Durdur + + + Restart + Yeniden Başlat + + + Full Screen + Tam Ekran + + + Controllers + Kontrolcüler + + + Keyboard + Klavye + + + Refresh List + Listeyi Yenile + + + Resume + Devam Et + + + Show Labels Under Icons + Simgelerin Altında Etiketleri Göster + PKGViewer diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 06c88428b..69e6c5fc7 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index c16604b85..14bd29896 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 6364ae1d6..7536b7d17 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + 开始游戏 + + + Pause + 暂停 + + + Stop + 关闭 + + + Restart + 重新启动 + + + Full Screen + 全屏 + + + Controllers + 控制器 + + + Keyboard + 键盘 + + + Refresh List + 刷新列表 + + + Resume + 继续游戏 + + + Show Labels Under Icons + 显示图标下的标签 + PKGViewer diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index fb42a43b0..f195ec1b7 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -1514,6 +1514,46 @@ shadPS4 shadPS4 + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + PKGViewer From 751a23af0f5a9612b8e28af1400896a3026ee331 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 28 Mar 2025 19:49:04 +0200 Subject: [PATCH 081/194] [ci skip] Qt GUI: Update Translation. (#2703) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 174 ------------------------------- 1 file changed, 174 deletions(-) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index d18609295..28d31b200 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Game @@ -978,25 +974,6 @@ - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - - - - Delete PKG File on Install - - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ Only one file can be selected! Only one file can be selected! - - PKG Extraction - PKG Extraction - - - Patch detected! - Patch detected! - - - PKG and Game versions match: - PKG and Game versions match: - - - Would you like to overwrite? - Would you like to overwrite? - - - PKG Version %1 is older than installed version: - PKG Version %1 is older than installed version: - - - Game is installed: - Game is installed: - - - Would you like to install Patch: - Would you like to install Patch: - - - DLC Installation - DLC Installation - - - Would you like to install DLC: %1? - Would you like to install DLC: %1? - - - DLC already installed: - DLC already installed: - - - Game already installed - Game already installed - - - PKG ERROR - PKG ERROR - - - Extracting PKG %1/%2 - Extracting PKG %1/%2 - - - Extraction Finished - Extraction Finished - - - Game successfully installed at %1 - Game successfully installed at %1 - - - File doesn't appear to be a valid PKG file - File doesn't appear to be a valid PKG file - Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found - - PKG File (*.PKG *.pkg) - - - - PKG is a patch or DLC, please install the game first! - - Game is already running! @@ -1555,73 +1448,6 @@ - - PKGViewer - - Open Folder - Open Folder - - - PKG ERROR - PKG ERROR - - - Name - Name - - - Serial - Serial - - - Installed - - - - Size - Size - - - Category - - - - Type - - - - App Ver - - - - FW - - - - Region - Region - - - Flags - - - - Path - Path - - - File - File - - - Unknown - Unknown - - - Package - - - SettingsDialog From 78c8bca2bb7a1da452ba67f4813b4d504b8b4e7b Mon Sep 17 00:00:00 2001 From: Ked <58560148+k3dr1@users.noreply.github.com> Date: Sat, 29 Mar 2025 05:14:52 +0800 Subject: [PATCH 082/194] Fix support for unicode paths for game install directories (#2699) * Slightly changed how allInstallDirsDisabled is determined * Show a dialog only if no game directories are set * Changed a comment * Fixed formatting * Support for unicode paths for game install directories * Fixed game picture path conversion --- src/common/config.cpp | 2 +- src/qt_gui/game_list_frame.cpp | 3 ++- src/qt_gui/settings_dialog.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 8ead58686..d1bb89897 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -850,7 +850,7 @@ void load(const std::filesystem::path& path) { m_window_size_H = toml::find_or(gui, "mw_height", 0); const auto install_dir_array = - toml::find_or>(gui, "installDirs", {}); + toml::find_or>(gui, "installDirs", {}); try { install_dirs_enabled = toml::find>(gui, "installDirsEnabled"); diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index 4c0607571..170215f3d 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -185,7 +185,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { // 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())); + auto image_path = game.pic_path.u8string(); + QImage original_image(QString::fromStdString({image_path.begin(), image_path.end()})); if (!original_image.isNull()) { backgroundImage = m_game_list_utils.ChangeImageOpacity( original_image, original_image.rect(), opacity / 100.0f); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index d789f6f48..383cad8fa 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -817,7 +817,7 @@ void SettingsDialog::ResetInstallFolders() { if (data.contains("GUI")) { const toml::value& gui = data.at("GUI"); const auto install_dir_array = - toml::find_or>(gui, "installDirs", {}); + toml::find_or>(gui, "installDirs", {}); std::vector install_dirs_enabled; try { From be7d646e8314ccf1f125818f3589b78d8e3262eb Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sat, 29 Mar 2025 01:32:06 -0700 Subject: [PATCH 083/194] externals: Remove need for cryptopp build. (#2707) --- .gitmodules | 8 - CMakeLists.txt | 4 +- externals/CMakeLists.txt | 16 +- externals/cryptopp | 1 - externals/cryptopp-cmake | 1 - src/common/aes.h | 1195 ++++++++++++++++++++++++++++++++++ src/common/sha1.h | 180 +++++ src/core/file_format/trp.cpp | 37 +- src/core/module.cpp | 11 +- 9 files changed, 1398 insertions(+), 55 deletions(-) delete mode 160000 externals/cryptopp delete mode 160000 externals/cryptopp-cmake create mode 100644 src/common/aes.h create mode 100644 src/common/sha1.h diff --git a/.gitmodules b/.gitmodules index ca229bedd..98fba2098 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,11 +1,3 @@ -[submodule "externals/cryptopp-cmake"] - path = externals/cryptopp-cmake - url = https://github.com/shadps4-emu/ext-cryptopp-cmake.git - shallow = true -[submodule "externals/cryptopp"] - path = externals/cryptopp - url = https://github.com/shadps4-emu/ext-cryptopp.git - shallow = true [submodule "externals/zlib-ng"] path = externals/zlib-ng url = https://github.com/shadps4-emu/ext-zlib-ng.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 13204f479..bd458f04e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -583,6 +583,7 @@ set(COMMON src/common/logging/backend.cpp src/common/logging/text_formatter.cpp src/common/logging/text_formatter.h src/common/logging/types.h + src/common/aes.h src/common/alignment.h src/common/arch.h src/common/assert.cpp @@ -614,6 +615,7 @@ set(COMMON src/common/logging/backend.cpp src/common/polyfill_thread.h src/common/rdtsc.cpp src/common/rdtsc.h + src/common/sha1.h src/common/signal_context.h src/common/signal_context.cpp src/common/singleton.h @@ -1022,7 +1024,7 @@ endif() create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) -target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers cryptopp::cryptopp) +target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 3b29a838e..d6bdda023 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -26,21 +26,7 @@ if (NOT TARGET fmt::fmt) add_subdirectory(fmt) endif() -# CryptoPP -if (NOT TARGET cryptopp::cryptopp) - set(CRYPTOPP_INSTALL OFF) - set(CRYPTOPP_BUILD_TESTING OFF) - set(CRYPTOPP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp) - # cryptopp instruction set checks do not account for added compile options, - # so disable extensions in the library config to match our chosen target CPU. - set(CRYPTOPP_DISABLE_AESNI ON) - set(CRYPTOPP_DISABLE_AVX2 ON) - add_subdirectory(cryptopp-cmake) - file(COPY cryptopp DESTINATION cryptopp FILES_MATCHING PATTERN "*.h") - # remove externals/cryptopp from include directories because it contains a conflicting zlib.h file - set_target_properties(cryptopp PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/cryptopp") -endif() - +# FFmpeg if (NOT TARGET FFmpeg::ffmpeg) add_subdirectory(ffmpeg-core) add_library(FFmpeg::ffmpeg ALIAS ffmpeg) diff --git a/externals/cryptopp b/externals/cryptopp deleted file mode 160000 index effed0d0b..000000000 --- a/externals/cryptopp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit effed0d0b865afc23ed67e0916f83734e4b9b3b7 diff --git a/externals/cryptopp-cmake b/externals/cryptopp-cmake deleted file mode 160000 index 2c384c282..000000000 --- a/externals/cryptopp-cmake +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2c384c28265a93358a2455e610e76393358794df diff --git a/src/common/aes.h b/src/common/aes.h new file mode 100644 index 000000000..5ca0096bd --- /dev/null +++ b/src/common/aes.h @@ -0,0 +1,1195 @@ +// SPDX-FileCopyrightText: 2015 kkAyataka +// SPDX-License-Identifier: BSL-1.0 + +#pragma once + +#include +#include +#include +#include +#include +#include + +/** AES cipher APIs */ +namespace aes { +namespace detail { + +const int kWordSize = 4; +typedef unsigned int Word; + +const int kBlockSize = 4; +/** @private */ +struct State { + Word w[4]; + Word& operator[](const int index) { + return w[index]; + } + const Word& operator[](const int index) const { + return w[index]; + } +}; + +const int kStateSize = 16; // Word * BlockSize +typedef State RoundKey; +typedef std::vector RoundKeys; + +inline void add_round_key(const RoundKey& key, State& state) { + for (int i = 0; i < kBlockSize; ++i) { + state[i] ^= key[i]; + } +} + +const unsigned char kSbox[] = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16}; + +const unsigned char kInvSbox[] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d}; + +inline Word sub_word(const Word w) { + return kSbox[(w >> 0) & 0xFF] << 0 | kSbox[(w >> 8) & 0xFF] << 8 | + kSbox[(w >> 16) & 0xFF] << 16 | kSbox[(w >> 24) & 0xFF] << 24; +} + +inline Word inv_sub_word(const Word w) { + return kInvSbox[(w >> 0) & 0xFF] << 0 | kInvSbox[(w >> 8) & 0xFF] << 8 | + kInvSbox[(w >> 16) & 0xFF] << 16 | kInvSbox[(w >> 24) & 0xFF] << 24; +} + +inline void sub_bytes(State& state) { + for (int i = 0; i < kBlockSize; ++i) { + state[i] = sub_word(state[i]); + } +} + +inline void inv_sub_bytes(State& state) { + for (int i = 0; i < kBlockSize; ++i) { + state[i] = inv_sub_word(state[i]); + } +} + +inline void shift_rows(State& state) { + const State ori = {state[0], state[1], state[2], state[3]}; + for (int r = 1; r < kWordSize; ++r) { + const Word m2 = 0xFF << (r * 8); + const Word m1 = ~m2; + for (int c = 0; c < kBlockSize; ++c) { + state[c] = (state[c] & m1) | (ori[(c + r) % kBlockSize] & m2); + } + } +} + +inline void inv_shift_rows(State& state) { + const State ori = {state[0], state[1], state[2], state[3]}; + for (int r = 1; r < kWordSize; ++r) { + const Word m2 = 0xFF << (r * 8); + const Word m1 = ~m2; + for (int c = 0; c < kBlockSize; ++c) { + state[c] = (state[c] & m1) | (ori[(c + kBlockSize - r) % kWordSize] & m2); + } + } +} + +inline unsigned char mul2(const unsigned char b) { + unsigned char m2 = b << 1; + if (b & 0x80) { + m2 ^= 0x011B; + } + + return m2; +} + +inline unsigned char mul(const unsigned char b, const unsigned char m) { + unsigned char v = 0; + unsigned char t = b; + for (int i = 0; i < 8; ++i) { // 8-bits + if ((m >> i) & 0x01) { + v ^= t; + } + + t = mul2(t); + } + + return v; +} + +inline void mix_columns(State& state) { + for (int i = 0; i < kBlockSize; ++i) { + const unsigned char v0_1 = (state[i] >> 0) & 0xFF; + const unsigned char v1_1 = (state[i] >> 8) & 0xFF; + const unsigned char v2_1 = (state[i] >> 16) & 0xFF; + const unsigned char v3_1 = (state[i] >> 24) & 0xFF; + + const unsigned char v0_2 = mul2(v0_1); + const unsigned char v1_2 = mul2(v1_1); + const unsigned char v2_2 = mul2(v2_1); + const unsigned char v3_2 = mul2(v3_1); + + const unsigned char v0_3 = v0_2 ^ v0_1; + const unsigned char v1_3 = v1_2 ^ v1_1; + const unsigned char v2_3 = v2_2 ^ v2_1; + const unsigned char v3_3 = v3_2 ^ v3_1; + + state[i] = (v0_2 ^ v1_3 ^ v2_1 ^ v3_1) << 0 | (v0_1 ^ v1_2 ^ v2_3 ^ v3_1) << 8 | + (v0_1 ^ v1_1 ^ v2_2 ^ v3_3) << 16 | (v0_3 ^ v1_1 ^ v2_1 ^ v3_2) << 24; + } +} + +inline void inv_mix_columns(State& state) { + for (int i = 0; i < kBlockSize; ++i) { + const unsigned char v0 = (state[i] >> 0) & 0xFF; + const unsigned char v1 = (state[i] >> 8) & 0xFF; + const unsigned char v2 = (state[i] >> 16) & 0xFF; + const unsigned char v3 = (state[i] >> 24) & 0xFF; + + state[i] = (mul(v0, 0x0E) ^ mul(v1, 0x0B) ^ mul(v2, 0x0D) ^ mul(v3, 0x09)) << 0 | + (mul(v0, 0x09) ^ mul(v1, 0x0E) ^ mul(v2, 0x0B) ^ mul(v3, 0x0D)) << 8 | + (mul(v0, 0x0D) ^ mul(v1, 0x09) ^ mul(v2, 0x0E) ^ mul(v3, 0x0B)) << 16 | + (mul(v0, 0x0B) ^ mul(v1, 0x0D) ^ mul(v2, 0x09) ^ mul(v3, 0x0E)) << 24; + } +} + +inline Word rot_word(const Word v) { + return ((v >> 8) & 0x00FFFFFF) | ((v & 0xFF) << 24); +} + +/** + * @private + * @throws std::invalid_argument + */ +inline unsigned int get_round_count(const int key_size) { + switch (key_size) { + case 16: + return 10; + case 24: + return 12; + case 32: + return 14; + default: + throw std::invalid_argument("Invalid key size"); + } +} + +/** + * @private + * @throws std::invalid_argument + */ +inline RoundKeys expand_key(const unsigned char* key, const int key_size) { + if (key_size != 16 && key_size != 24 && key_size != 32) { + throw std::invalid_argument("Invalid key size"); + } + + const Word rcon[] = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36}; + + const int nb = kBlockSize; + const int nk = key_size / nb; + const int nr = get_round_count(key_size); + + std::vector w(nb * (nr + 1)); + for (int i = 0; i < nk; ++i) { + memcpy(&w[i], key + (i * kWordSize), kWordSize); + } + + for (int i = nk; i < nb * (nr + 1); ++i) { + Word t = w[i - 1]; + if (i % nk == 0) { + t = sub_word(rot_word(t)) ^ rcon[i / nk]; + } else if (nk > 6 && i % nk == 4) { + t = sub_word(t); + } + + w[i] = t ^ w[i - nk]; + } + + RoundKeys keys(nr + 1); + memcpy(&keys[0], &w[0], w.size() * kWordSize); + + return keys; +} + +inline void copy_bytes_to_state(const unsigned char data[16], State& state) { + memcpy(&state[0], data + 0, kWordSize); + memcpy(&state[1], data + 4, kWordSize); + memcpy(&state[2], data + 8, kWordSize); + memcpy(&state[3], data + 12, kWordSize); +} + +inline void copy_state_to_bytes(const State& state, unsigned char buf[16]) { + memcpy(buf + 0, &state[0], kWordSize); + memcpy(buf + 4, &state[1], kWordSize); + memcpy(buf + 8, &state[2], kWordSize); + memcpy(buf + 12, &state[3], kWordSize); +} + +inline void xor_data(unsigned char data[kStateSize], const unsigned char v[kStateSize]) { + for (int i = 0; i < kStateSize; ++i) { + data[i] ^= v[i]; + } +} + +/** increment counter (128-bit int) by 1 */ +inline void incr_counter(unsigned char counter[kStateSize]) { + unsigned n = kStateSize, c = 1; + do { + --n; + c += counter[n]; + counter[n] = c; + c >>= 8; + } while (n); +} + +inline void encrypt_state(const RoundKeys& rkeys, const unsigned char data[16], + unsigned char encrypted[16]) { + State s; + copy_bytes_to_state(data, s); + + add_round_key(rkeys[0], s); + + for (unsigned int i = 1; i < rkeys.size() - 1; ++i) { + sub_bytes(s); + shift_rows(s); + mix_columns(s); + add_round_key(rkeys[i], s); + } + + sub_bytes(s); + shift_rows(s); + add_round_key(rkeys.back(), s); + + copy_state_to_bytes(s, encrypted); +} + +inline void decrypt_state(const RoundKeys& rkeys, const unsigned char data[16], + unsigned char decrypted[16]) { + State s; + copy_bytes_to_state(data, s); + + add_round_key(rkeys.back(), s); + inv_shift_rows(s); + inv_sub_bytes(s); + + for (std::size_t i = rkeys.size() - 2; i > 0; --i) { + add_round_key(rkeys[i], s); + inv_mix_columns(s); + inv_shift_rows(s); + inv_sub_bytes(s); + } + + add_round_key(rkeys[0], s); + + copy_state_to_bytes(s, decrypted); +} + +template +std::vector key_from_string(const char (*key_str)[KeyLen]) { + std::vector key(KeyLen - 1); + memcpy(&key[0], *key_str, KeyLen - 1); + return key; +} + +inline bool is_valid_key_size(const std::size_t key_size) { + if (key_size != 16 && key_size != 24 && key_size != 32) { + return false; + } else { + return true; + } +} + +namespace gcm { + +const int kBlockBitSize = 128; +const int kBlockByteSize = kBlockBitSize / 8; + +/** + * @private + * GCM operation unit as bit. + * This library handles 128 bit little endian bit array. + * e.g. 0^127 || 1 == "000...0001" (bit string) == 1 + */ +typedef std::bitset bitset128; + +/** + * @private + * GCM operation unit. + * Little endian byte array + * + * If bitset128 is 1: 0^127 || 1 == "000...0001" (bit string) == 1 + * byte array is 0x00, 0x00, 0x00 ... 0x01 (low -> high). + * Byte array is NOT 0x01, 0x00 ... 0x00. + * + * This library handles GCM bit string in two ways. + * One is an array of bitset, which is a little endian 128-bit array's array. + * + * <- first byte + * bitset128 || bitset128 || bitset128... + * + * The other one is a byte array. + * <- first byte + * byte || byte || byte... + */ +class Block { +public: + Block() { + init_v(0, 0); + } + + Block(const unsigned char* bytes, const unsigned long bytes_size) { + init_v(bytes, bytes_size); + } + + Block(const std::vector& bytes) { + init_v(&bytes[0], bytes.size()); + } + + Block(const std::bitset<128>& bits); // implementation below + + inline unsigned char* data() { + return v_; + } + + inline const unsigned char* data() const { + return v_; + } + + inline std::bitset<128> to_bits() const { + std::bitset<128> bits; + for (int i = 0; i < 16; ++i) { + bits <<= 8; + bits |= v_[i]; + } + + return bits; + } + + inline Block operator^(const Block& b) const { + Block r; + for (int i = 0; i < 16; ++i) { + r.data()[i] = data()[i] ^ b.data()[i]; + } + return r; + } + +private: + unsigned char v_[16]; + + inline void init_v(const unsigned char* bytes, const std::size_t bytes_size) { + memset(v_, 0, sizeof(v_)); + + const std::size_t cs = (std::min)(bytes_size, static_cast(16)); + for (std::size_t i = 0; i < cs; ++i) { + v_[i] = bytes[i]; + } + } +}; + +// Workaround for clang optimization in 32-bit build via Visual Studio producing incorrect results +// (https://github.com/kkAyataka/plusaes/issues/43) +#if defined(__clang__) && defined(_WIN32) && !defined(_WIN64) +#pragma optimize("", off) +#endif +inline Block::Block(const std::bitset<128>& bits) { + init_v(0, 0); + const std::bitset<128> mask(0xFF); // 1 byte mask + for (std::size_t i = 0; i < 16; ++i) { + v_[15 - i] = static_cast(((bits >> (i * 8)) & mask).to_ulong()); + } +} +#if defined(__clang__) && defined(_WIN32) && !defined(_WIN64) +#pragma optimize("", on) +#endif + +template +unsigned long ceil(const T v) { + return static_cast(std::ceil(v) + 0.5); +} + +template +std::bitset operator||(const std::bitset& v1, const std::bitset& v2) { + std::bitset ret(v1.to_string() + v2.to_string()); + return ret; +} + +template +std::bitset lsb(const std::bitset& X) { + std::bitset r; + for (std::size_t i = 0; i < S; ++i) { + r[i] = X[i]; + } + return r; +} + +template +std::bitset msb(const std::bitset& X) { + std::bitset r; + for (std::size_t i = 0; i < S; ++i) { + r[S - 1 - i] = X[X.size() - 1 - i]; + } + return r; +} + +template +std::bitset inc32(const std::bitset X) { + const std::size_t S = 32; + + const auto a = msb(X); + const std::bitset b( + (lsb(X).to_ulong() + 1)); // % (2^32); + // lsb<32> is low 32-bit value + // Spec.'s "mod 2^S" is not necessary when S is 32 (inc32). + // ...and 2^32 is over 32-bit integer. + + return a || b; +} + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wself-assign" +#endif // __clang__ + +/** Algorithm 1 @private */ +inline Block mul_blocks(const Block X, const Block Y) { + const bitset128 R = (std::bitset<8>("11100001") || std::bitset<120>()); + + bitset128 X_bits = X.to_bits(); + bitset128 Z; + bitset128 V = Y.to_bits(); + for (int i = 127; i >= 0; --i) { + // Z + if (X_bits[i] == false) { + Z = Z; + } else { + Z = Z ^ V; + } + + // V + if (V[0] == false) { + V = V >> 1; + } else { + V = (V >> 1) ^ R; + } + } + + return Z; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif // __clang__ + +/** Algorithm 2 @private */ +inline Block ghash(const Block& H, const std::vector& X) { + const std::size_t m = X.size() / kBlockByteSize; + Block Ym; + for (std::size_t i = 0; i < m; ++i) { + const Block Xi(&X[i * kBlockByteSize], kBlockByteSize); + Ym = mul_blocks((Ym ^ Xi), H); + } + + return Ym; +} + +template +std::bitset make_bitset(const unsigned char* bytes, const std::size_t bytes_size) { + std::bitset bits; + for (auto i = 0u; i < bytes_size; ++i) { + bits <<= 8; + bits |= bytes[i]; + } + return bits; +} + +/** Algorithm 3 @private */ +inline std::vector gctr(const detail::RoundKeys& rkeys, const Block& ICB, + const unsigned char* X, const std::size_t X_size) { + if (!X || X_size == 0) { + return std::vector(); + } else { + const unsigned long n = ceil(X_size * 8.0 / kBlockBitSize); + std::vector Y(X_size); + + Block CB; + for (std::size_t i = 0; i < n; ++i) { + // CB + if (i == 0) { // first + CB = ICB; + } else { + CB = inc32(CB.to_bits()); + } + + // CIPH + Block eCB; + encrypt_state(rkeys, CB.data(), eCB.data()); + + // Y + int op_size = 0; + if (i < n - 1) { + op_size = kBlockByteSize; + } else { // last + op_size = (X_size % kBlockByteSize) ? (X_size % kBlockByteSize) : kBlockByteSize; + } + const Block Yi = Block(X + i * kBlockBitSize / 8, op_size) ^ eCB; + memcpy(&Y[i * kBlockByteSize], Yi.data(), op_size); + } + + return Y; + } +} + +inline void push_back(std::vector& bytes, const unsigned char* data, + const std::size_t data_size) { + bytes.insert(bytes.end(), data, data + data_size); +} + +inline void push_back(std::vector& bytes, const std::bitset<64>& bits) { + const std::bitset<64> mask(0xFF); // 1 byte mask + for (std::size_t i = 0; i < 8; ++i) { + bytes.push_back(static_cast(((bits >> ((7 - i) * 8)) & mask).to_ulong())); + } +} + +inline void push_back_zero_bits(std::vector& bytes, + const std::size_t zero_bits_size) { + const std::vector zero_bytes(zero_bits_size / 8); + bytes.insert(bytes.end(), zero_bytes.begin(), zero_bytes.end()); +} + +inline Block calc_H(const RoundKeys& rkeys) { + std::vector H_raw(gcm::kBlockByteSize); + encrypt_state(rkeys, &H_raw[0], &H_raw[0]); + return gcm::Block(H_raw); +} + +inline Block calc_J0(const Block& H, const unsigned char* iv, const std::size_t iv_size) { + if (iv_size == 12) { + const std::bitset<96> iv_bits = gcm::make_bitset<96>(iv, iv_size); + return iv_bits || std::bitset<31>() || std::bitset<1>(1); + } else { + const auto len_iv = iv_size * 8; + const auto s = 128 * gcm::ceil(len_iv / 128.0) - len_iv; + std::vector ghash_in; + gcm::push_back(ghash_in, iv, iv_size); + gcm::push_back_zero_bits(ghash_in, s + 64); + gcm::push_back(ghash_in, std::bitset<64>(len_iv)); + + return gcm::ghash(H, ghash_in); + } +} + +inline void calc_gcm_tag(const unsigned char* data, const std::size_t data_size, + const unsigned char* aadata, const std::size_t aadata_size, + const unsigned char* key, const std::size_t key_size, + const unsigned char* iv, const std::size_t iv_size, unsigned char* tag, + const std::size_t tag_size) { + const detail::RoundKeys rkeys = detail::expand_key(key, static_cast(key_size)); + const gcm::Block H = gcm::calc_H(rkeys); + const gcm::Block J0 = gcm::calc_J0(H, iv, iv_size); + + const auto lenC = data_size * 8; + const auto lenA = aadata_size * 8; + const std::size_t u = 128 * gcm::ceil(lenC / 128.0) - lenC; + const std::size_t v = 128 * gcm::ceil(lenA / 128.0) - lenA; + + std::vector ghash_in; + ghash_in.reserve((aadata_size + v / 8) + (data_size + u / 8) + 8 + 8); + gcm::push_back(ghash_in, aadata, aadata_size); + gcm::push_back_zero_bits(ghash_in, v); + gcm::push_back(ghash_in, data, data_size); + gcm::push_back_zero_bits(ghash_in, u); + gcm::push_back(ghash_in, std::bitset<64>(lenA)); + gcm::push_back(ghash_in, std::bitset<64>(lenC)); + const gcm::Block S = gcm::ghash(H, ghash_in); + const std::vector T = gcm::gctr(rkeys, J0, S.data(), gcm::kBlockByteSize); + + // return + memcpy(tag, &T[0], (std::min)(tag_size, static_cast(16))); +} + +/** Algorithm 4 and 5 @private */ +inline void crypt_gcm(const unsigned char* data, const std::size_t data_size, + const unsigned char* key, const std::size_t key_size, const unsigned char* iv, + const std::size_t iv_size, unsigned char* crypted) { + const detail::RoundKeys rkeys = detail::expand_key(key, static_cast(key_size)); + const gcm::Block H = gcm::calc_H(rkeys); + const gcm::Block J0 = gcm::calc_J0(H, iv, iv_size); + + const std::vector C = + gcm::gctr(rkeys, gcm::inc32(J0.to_bits()), data, data_size); + + if (crypted) { + memcpy(crypted, &C[0], data_size); + } +} + +} // namespace gcm + +} // namespace detail + +/** @defgroup Base Base + * Base definitions and convenient functions + * @{ */ + +/** Create 128-bit key from string. */ +inline std::vector key_from_string(const char (*key_str)[17]) { + return detail::key_from_string<17>(key_str); +} + +/** Create 192-bit key from string. */ +inline std::vector key_from_string(const char (*key_str)[25]) { + return detail::key_from_string<25>(key_str); +} + +/** Create 256-bit key from string. */ +inline std::vector key_from_string(const char (*key_str)[33]) { + return detail::key_from_string<33>(key_str); +} + +/** Calculates encrypted data size when padding is enabled. */ +inline unsigned long get_padded_encrypted_size(const unsigned long data_size) { + return data_size + detail::kStateSize - (data_size % detail::kStateSize); +} + +/** Error code */ +typedef enum { + kErrorOk = 0, + kErrorInvalidDataSize = 1, + kErrorInvalidKeySize, + kErrorInvalidBufferSize, + kErrorInvalidKey, + kErrorDeprecated, // kErrorInvalidNonceSize + kErrorInvalidIvSize, + kErrorInvalidTagSize, + kErrorInvalidTag +} Error; + +/** @} */ + +namespace detail { + +inline Error check_encrypt_cond(const unsigned long data_size, const unsigned long key_size, + const unsigned long encrypted_size, const bool pads) { + // check data size + if (!pads && (data_size % kStateSize != 0)) { + return kErrorInvalidDataSize; + } + + // check key size + if (!detail::is_valid_key_size(key_size)) { + return kErrorInvalidKeySize; + } + + // check encrypted buffer size + if (pads) { + const unsigned long required_size = get_padded_encrypted_size(data_size); + if (encrypted_size < required_size) { + return kErrorInvalidBufferSize; + } + } else { + if (encrypted_size < data_size) { + return kErrorInvalidBufferSize; + } + } + return kErrorOk; +} + +inline Error check_decrypt_cond(const unsigned long data_size, const unsigned long key_size, + const unsigned long decrypted_size, + const unsigned long* padded_size) { + // check data size + if (data_size % 16 != 0) { + return kErrorInvalidDataSize; + } + + // check key size + if (!detail::is_valid_key_size(key_size)) { + return kErrorInvalidKeySize; + } + + // check decrypted buffer size + if (!padded_size) { + if (decrypted_size < data_size) { + return kErrorInvalidBufferSize; + } + } else { + if (decrypted_size < (data_size - kStateSize)) { + return kErrorInvalidBufferSize; + } + } + + return kErrorOk; +} + +inline bool check_padding(const unsigned long padding, const unsigned char data[kStateSize]) { + if (padding > kStateSize) { + return false; + } + + for (unsigned long i = 0; i < padding; ++i) { + if (data[kStateSize - 1 - i] != padding) { + return false; + } + } + + return true; +} + +inline Error check_gcm_cond(const std::size_t key_size, const std::size_t iv_size, + const std::size_t tag_size) { + // check key size + if (!detail::is_valid_key_size(key_size)) { + return kErrorInvalidKeySize; + } + + if (iv_size < 1) { + return kErrorInvalidIvSize; + } + + // check tag size + if ((tag_size < 12 || 16 < tag_size) && (tag_size != 8) && (tag_size != 4)) { + return kErrorInvalidTagSize; + } + + return kErrorOk; +} + +} // namespace detail + +/** @defgroup ECB ECB + * ECB mode functions + * @{ */ + +/** + * Encrypts data with ECB mode. + * @param [in] data Data. + * @param [in] data_size Data size. + * If the pads is false, data size must be multiple of 16. + * @param [in] key key bytes. The key length must be 16 (128-bit), 24 (192-bit) or 32 (256-bit). + * @param [in] key_size key size. + * @param [out] encrypted Encrypted data buffer. + * @param [in] encrypted_size Encrypted data buffer size. + * @param [in] pads If this value is true, encrypted data is padded by PKCS. + * Encrypted data size must be multiple of 16. + * If the pads is true, encrypted data is padded with PKCS. + * So the data is multiple of 16, encrypted data size needs additonal 16 bytes. + * @since 1.0.0 + */ +inline Error encrypt_ecb(const unsigned char* data, const unsigned long data_size, + const unsigned char* key, const unsigned long key_size, + unsigned char* encrypted, const unsigned long encrypted_size, + const bool pads) { + const Error e = detail::check_encrypt_cond(data_size, key_size, encrypted_size, pads); + if (e != kErrorOk) { + return e; + } + + const detail::RoundKeys rkeys = detail::expand_key(key, static_cast(key_size)); + + const unsigned long bc = data_size / detail::kStateSize; + for (unsigned long i = 0; i < bc; ++i) { + detail::encrypt_state(rkeys, data + (i * detail::kStateSize), + encrypted + (i * detail::kStateSize)); + } + + if (pads) { + const int rem = data_size % detail::kStateSize; + const char pad_v = detail::kStateSize - rem; + + std::vector ib(detail::kStateSize, pad_v), ob(detail::kStateSize); + memcpy(&ib[0], data + data_size - rem, rem); + + detail::encrypt_state(rkeys, &ib[0], &ob[0]); + memcpy(encrypted + (data_size - rem), &ob[0], detail::kStateSize); + } + + return kErrorOk; +} + +/** + * Decrypts data with ECB mode. + * @param [in] data Data bytes. + * @param [in] data_size Data size. + * @param [in] key Key bytes. + * @param [in] key_size Key size. + * @param [out] decrypted Decrypted data buffer. + * @param [in] decrypted_size Decrypted data buffer size. + * @param [out] padded_size If this value is NULL, this function does not remove padding. + * If this value is not NULL, this function removes padding by PKCS + * and returns padded size using padded_size. + * @since 1.0.0 + */ +inline Error decrypt_ecb(const unsigned char* data, const unsigned long data_size, + const unsigned char* key, const unsigned long key_size, + unsigned char* decrypted, const unsigned long decrypted_size, + unsigned long* padded_size) { + const Error e = detail::check_decrypt_cond(data_size, key_size, decrypted_size, padded_size); + if (e != kErrorOk) { + return e; + } + + const detail::RoundKeys rkeys = detail::expand_key(key, static_cast(key_size)); + + const unsigned long bc = data_size / detail::kStateSize - 1; + for (unsigned long i = 0; i < bc; ++i) { + detail::decrypt_state(rkeys, data + (i * detail::kStateSize), + decrypted + (i * detail::kStateSize)); + } + + unsigned char last[detail::kStateSize] = {}; + detail::decrypt_state(rkeys, data + (bc * detail::kStateSize), last); + + if (padded_size) { + *padded_size = last[detail::kStateSize - 1]; + const unsigned long cs = detail::kStateSize - *padded_size; + + if (!detail::check_padding(*padded_size, last)) { + return kErrorInvalidKey; + } else if (decrypted_size >= (bc * detail::kStateSize) + cs) { + memcpy(decrypted + (bc * detail::kStateSize), last, cs); + } else { + return kErrorInvalidBufferSize; + } + } else { + memcpy(decrypted + (bc * detail::kStateSize), last, sizeof(last)); + } + + return kErrorOk; +} + +/** @} */ + +/** @defgroup CBC CBC + * CBC mode functions + * @{ */ + +/** + * Encrypt data with CBC mode. + * @param [in] data Data. + * @param [in] data_size Data size. + * If the pads is false, data size must be multiple of 16. + * @param [in] key key bytes. The key length must be 16 (128-bit), 24 (192-bit) or 32 (256-bit). + * @param [in] key_size key size. + * @param [in] iv Initialize vector. + * @param [out] encrypted Encrypted data buffer. + * @param [in] encrypted_size Encrypted data buffer size. + * @param [in] pads If this value is true, encrypted data is padded by PKCS. + * Encrypted data size must be multiple of 16. + * If the pads is true, encrypted data is padded with PKCS. + * So the data is multiple of 16, encrypted data size needs additonal 16 bytes. + * @since 1.0.0 + */ +inline Error encrypt_cbc(const unsigned char* data, const unsigned long data_size, + const unsigned char* key, const unsigned long key_size, + const unsigned char iv[16], unsigned char* encrypted, + const unsigned long encrypted_size, const bool pads) { + const Error e = detail::check_encrypt_cond(data_size, key_size, encrypted_size, pads); + if (e != kErrorOk) { + return e; + } + + const detail::RoundKeys rkeys = detail::expand_key(key, static_cast(key_size)); + + unsigned char s[detail::kStateSize] = {}; // encrypting data + + // calculate padding value + const bool ge16 = (data_size >= detail::kStateSize); + const int rem = data_size % detail::kStateSize; + const unsigned char pad_v = detail::kStateSize - rem; + + // encrypt 1st state + if (ge16) { + memcpy(s, data, detail::kStateSize); + } else { + memset(s, pad_v, detail::kStateSize); + memcpy(s, data, data_size); + } + if (iv) { + detail::xor_data(s, iv); + } + detail::encrypt_state(rkeys, s, encrypted); + + // encrypt mid + const unsigned long bc = data_size / detail::kStateSize; + for (unsigned long i = 1; i < bc; ++i) { + const long offset = i * detail::kStateSize; + memcpy(s, data + offset, detail::kStateSize); + detail::xor_data(s, encrypted + offset - detail::kStateSize); + + detail::encrypt_state(rkeys, s, encrypted + offset); + } + + // enctypt last + if (pads && ge16) { + std::vector ib(detail::kStateSize, pad_v), ob(detail::kStateSize); + memcpy(&ib[0], data + data_size - rem, rem); + + detail::xor_data(&ib[0], encrypted + (bc - 1) * detail::kStateSize); + + detail::encrypt_state(rkeys, &ib[0], &ob[0]); + memcpy(encrypted + (data_size - rem), &ob[0], detail::kStateSize); + } + + return kErrorOk; +} + +/** + * Decrypt data with CBC mode. + * @param [in] data Data bytes. + * @param [in] data_size Data size. + * @param [in] key Key bytes. + * @param [in] key_size Key size. + * @param [in] iv Initialize vector. + * @param [out] decrypted Decrypted data buffer. + * @param [in] decrypted_size Decrypted data buffer size. + * @param [out] padded_size If this value is NULL, this function does not remove padding. + * If this value is not NULL, this function removes padding by PKCS + * and returns padded size using padded_size. + * @since 1.0.0 + */ +inline Error decrypt_cbc(const unsigned char* data, const unsigned long data_size, + const unsigned char* key, const unsigned long key_size, + const unsigned char iv[16], unsigned char* decrypted, + const unsigned long decrypted_size, unsigned long* padded_size) { + const Error e = detail::check_decrypt_cond(data_size, key_size, decrypted_size, padded_size); + if (e != kErrorOk) { + return e; + } + + const detail::RoundKeys rkeys = detail::expand_key(key, static_cast(key_size)); + + // decrypt 1st state + detail::decrypt_state(rkeys, data, decrypted); + if (iv) { + detail::xor_data(decrypted, iv); + } + + // decrypt mid + const unsigned long bc = data_size / detail::kStateSize - 1; + for (unsigned long i = 1; i < bc; ++i) { + const long offset = i * detail::kStateSize; + detail::decrypt_state(rkeys, data + offset, decrypted + offset); + detail::xor_data(decrypted + offset, data + offset - detail::kStateSize); + } + + // decrypt last + unsigned char last[detail::kStateSize] = {}; + if (data_size > detail::kStateSize) { + detail::decrypt_state(rkeys, data + (bc * detail::kStateSize), last); + detail::xor_data(last, data + (bc * detail::kStateSize - detail::kStateSize)); + } else { + memcpy(last, decrypted, data_size); + memset(decrypted, 0, decrypted_size); + } + + if (padded_size) { + *padded_size = last[detail::kStateSize - 1]; + const unsigned long cs = detail::kStateSize - *padded_size; + + if (!detail::check_padding(*padded_size, last)) { + return kErrorInvalidKey; + } else if (decrypted_size >= (bc * detail::kStateSize) + cs) { + memcpy(decrypted + (bc * detail::kStateSize), last, cs); + } else { + return kErrorInvalidBufferSize; + } + } else { + memcpy(decrypted + (bc * detail::kStateSize), last, sizeof(last)); + } + + return kErrorOk; +} + +/** @} */ + +/** @defgroup GCM GCM + * GCM mode functions + * @{ */ + +/** + * Encrypts data with GCM mode and gets an authentication tag. + * + * You can specify iv size and tag size. + * But usually you should use the other overloaded function whose iv and tag size is fixed. + * + * @returns kErrorOk + * @returns kErrorInvalidKeySize + * @returns kErrorInvalidIvSize + * @returns kErrorInvalidTagSize + */ +inline Error encrypt_gcm(unsigned char* data, const std::size_t data_size, + const unsigned char* aadata, const std::size_t aadata_size, + const unsigned char* key, const std::size_t key_size, + const unsigned char* iv, const std::size_t iv_size, unsigned char* tag, + const std::size_t tag_size) { + const Error err = detail::check_gcm_cond(key_size, iv_size, tag_size); + if (err != kErrorOk) { + return err; + } + + detail::gcm::crypt_gcm(data, data_size, key, key_size, iv, iv_size, data); + detail::gcm::calc_gcm_tag(data, data_size, aadata, aadata_size, key, key_size, iv, iv_size, tag, + tag_size); + + return kErrorOk; +} + +/** + * Encrypts data with GCM mode and gets an authentication tag. + * + * @param [in,out] data Input data and output buffer. + * This buffer is replaced with encrypted data. + * @param [in] data_size data size + * @param [in] aadata Additional Authenticated data + * @param [in] aadata_size aadata size + * @param [in] key Cipher key + * @param [in] key_size Cipher key size. This value must be 16 (128-bit), 24 (192-bit), or 32 + * (256-bit). + * @param [in] iv Initialization vector + * @param [out] tag Calculated authentication tag data + * + * @returns kErrorOk + * @returns kErrorInvalidKeySize + */ +inline Error encrypt_gcm(unsigned char* data, const std::size_t data_size, + const unsigned char* aadata, const std::size_t aadata_size, + const unsigned char* key, const std::size_t key_size, + const unsigned char (*iv)[12], unsigned char (*tag)[16]) { + return encrypt_gcm(data, data_size, aadata, aadata_size, key, key_size, *iv, 12, *tag, 16); +} + +/** + * Decrypts data with GCM mode and checks an authentication tag. + * + * You can specify iv size and tag size. + * But usually you should use the other overloaded function whose iv and tag size is fixed. + * + * @returns kErrorOk + * @returns kErrorInvalidKeySize + * @returns kErrorInvalidIvSize + * @returns kErrorInvalidTagSize + * @returns kErrorInvalidTag + */ +inline Error decrypt_gcm(unsigned char* data, const std::size_t data_size, + const unsigned char* aadata, const std::size_t aadata_size, + const unsigned char* key, const std::size_t key_size, + const unsigned char* iv, const std::size_t iv_size, + const unsigned char* tag, const std::size_t tag_size) { + const Error err = detail::check_gcm_cond(key_size, iv_size, tag_size); + if (err != kErrorOk) { + return err; + } + + unsigned char* C = data; + const auto C_size = data_size; + unsigned char tagd[16] = {}; + detail::gcm::calc_gcm_tag(C, C_size, aadata, aadata_size, key, key_size, iv, iv_size, tagd, 16); + + if (memcmp(tag, tagd, tag_size) != 0) { + return kErrorInvalidTag; + } else { + detail::gcm::crypt_gcm(C, C_size, key, key_size, iv, iv_size, C); + + return kErrorOk; + } +} + +/** + * Decrypts data with GCM mode and checks an authentication tag. + * + * @param [in,out] data Input data and output buffer. + * This buffer is replaced with decrypted data. + * @param [in] data_size data size + * @param [in] aadata Additional Authenticated data + * @param [in] aadata_size aadata size + * @param [in] key Cipher key + * @param [in] key_size Cipher key size. This value must be 16 (128-bit), 24 (192-bit), or 32 + * (256-bit). + * @param [in] iv Initialization vector + * @param [in] tag Authentication tag data + * + * @returns kErrorOk + * @returns kErrorInvalidKeySize + * @returns kErrorInvalidTag + */ +inline Error decrypt_gcm(unsigned char* data, const std::size_t data_size, + const unsigned char* aadata, const std::size_t aadata_size, + const unsigned char* key, const std::size_t key_size, + const unsigned char (*iv)[12], const unsigned char (*tag)[16]) { + return decrypt_gcm(data, data_size, aadata, aadata_size, key, key_size, *iv, 12, *tag, 16); +} + +/** @} */ + +/** @defgroup CTR CTR + * CTR mode function + * @{ */ + +/** + * Encrypts or decrypt data in-place with CTR mode. + * + * @param [in,out] data Input data and output buffer. + * This buffer is replaced with encrypted / decrypted data. + * @param [in] data_size Data size. + * @param [in] key Cipher key + * @param [in] key_size Cipher key size. This value must be 16 (128-bit), 24 (192-bit), or 32 + * (256-bit). + * @param [in] nonce Nonce of the counter initialization. + * + * @returns kErrorOk + * @returns kErrorInvalidKeySize + * @since 1.0.0 + */ +inline Error crypt_ctr(unsigned char* data, const std::size_t data_size, const unsigned char* key, + const std::size_t key_size, const unsigned char (*nonce)[16]) { + if (!detail::is_valid_key_size(key_size)) + return kErrorInvalidKeySize; + const detail::RoundKeys rkeys = detail::expand_key(key, static_cast(key_size)); + + unsigned long pos = 0; + unsigned long blkpos = detail::kStateSize; + unsigned char blk[detail::kStateSize] = {}; + unsigned char counter[detail::kStateSize] = {}; + memcpy(counter, nonce, 16); + + while (pos < data_size) { + if (blkpos == detail::kStateSize) { + detail::encrypt_state(rkeys, counter, blk); + detail::incr_counter(counter); + blkpos = 0; + } + data[pos++] ^= blk[blkpos++]; + } + + return kErrorOk; +} + +/** @} */ + +} // namespace aes diff --git a/src/common/sha1.h b/src/common/sha1.h new file mode 100644 index 000000000..fad849dcc --- /dev/null +++ b/src/common/sha1.h @@ -0,0 +1,180 @@ +// SPDX-FileCopyrightText: 2012 SAURAV MOHAPATRA +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +namespace sha1 { +class SHA1 { +public: + typedef uint32_t digest32_t[5]; + typedef uint8_t digest8_t[20]; + inline static uint32_t LeftRotate(uint32_t value, size_t count) { + return (value << count) ^ (value >> (32 - count)); + } + SHA1() { + reset(); + } + virtual ~SHA1() {} + SHA1(const SHA1& s) { + *this = s; + } + const SHA1& operator=(const SHA1& s) { + memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t)); + memcpy(m_block, s.m_block, 64); + m_blockByteIndex = s.m_blockByteIndex; + m_byteCount = s.m_byteCount; + return *this; + } + SHA1& reset() { + m_digest[0] = 0x67452301; + m_digest[1] = 0xEFCDAB89; + m_digest[2] = 0x98BADCFE; + m_digest[3] = 0x10325476; + m_digest[4] = 0xC3D2E1F0; + m_blockByteIndex = 0; + m_byteCount = 0; + return *this; + } + SHA1& processByte(uint8_t octet) { + this->m_block[this->m_blockByteIndex++] = octet; + ++this->m_byteCount; + if (m_blockByteIndex == 64) { + this->m_blockByteIndex = 0; + processBlock(); + } + return *this; + } + SHA1& processBlock(const void* const start, const void* const end) { + const uint8_t* begin = static_cast(start); + const uint8_t* finish = static_cast(end); + while (begin != finish) { + processByte(*begin); + begin++; + } + return *this; + } + SHA1& processBytes(const void* const data, size_t len) { + const uint8_t* block = static_cast(data); + processBlock(block, block + len); + return *this; + } + const uint32_t* getDigest(digest32_t digest) { + size_t bitCount = this->m_byteCount * 8; + processByte(0x80); + if (this->m_blockByteIndex > 56) { + while (m_blockByteIndex != 0) { + processByte(0); + } + while (m_blockByteIndex < 56) { + processByte(0); + } + } else { + while (m_blockByteIndex < 56) { + processByte(0); + } + } + processByte(0); + processByte(0); + processByte(0); + processByte(0); + processByte(static_cast((bitCount >> 24) & 0xFF)); + processByte(static_cast((bitCount >> 16) & 0xFF)); + processByte(static_cast((bitCount >> 8) & 0xFF)); + processByte(static_cast((bitCount) & 0xFF)); + + memcpy(digest, m_digest, 5 * sizeof(uint32_t)); + return digest; + } + const uint8_t* getDigestBytes(digest8_t digest) { + digest32_t d32; + getDigest(d32); + size_t di = 0; + digest[di++] = ((d32[0] >> 24) & 0xFF); + digest[di++] = ((d32[0] >> 16) & 0xFF); + digest[di++] = ((d32[0] >> 8) & 0xFF); + digest[di++] = ((d32[0]) & 0xFF); + + digest[di++] = ((d32[1] >> 24) & 0xFF); + digest[di++] = ((d32[1] >> 16) & 0xFF); + digest[di++] = ((d32[1] >> 8) & 0xFF); + digest[di++] = ((d32[1]) & 0xFF); + + digest[di++] = ((d32[2] >> 24) & 0xFF); + digest[di++] = ((d32[2] >> 16) & 0xFF); + digest[di++] = ((d32[2] >> 8) & 0xFF); + digest[di++] = ((d32[2]) & 0xFF); + + digest[di++] = ((d32[3] >> 24) & 0xFF); + digest[di++] = ((d32[3] >> 16) & 0xFF); + digest[di++] = ((d32[3] >> 8) & 0xFF); + digest[di++] = ((d32[3]) & 0xFF); + + digest[di++] = ((d32[4] >> 24) & 0xFF); + digest[di++] = ((d32[4] >> 16) & 0xFF); + digest[di++] = ((d32[4] >> 8) & 0xFF); + digest[di++] = ((d32[4]) & 0xFF); + return digest; + } + +protected: + void processBlock() { + uint32_t w[80]; + for (size_t i = 0; i < 16; i++) { + w[i] = (m_block[i * 4 + 0] << 24); + w[i] |= (m_block[i * 4 + 1] << 16); + w[i] |= (m_block[i * 4 + 2] << 8); + w[i] |= (m_block[i * 4 + 3]); + } + for (size_t i = 16; i < 80; i++) { + w[i] = LeftRotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1); + } + + uint32_t a = m_digest[0]; + uint32_t b = m_digest[1]; + uint32_t c = m_digest[2]; + uint32_t d = m_digest[3]; + uint32_t e = m_digest[4]; + + for (std::size_t i = 0; i < 80; ++i) { + uint32_t f = 0; + uint32_t k = 0; + + if (i < 20) { + f = (b & c) | (~b & d); + k = 0x5A827999; + } else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = LeftRotate(b, 30); + b = a; + a = temp; + } + + m_digest[0] += a; + m_digest[1] += b; + m_digest[2] += c; + m_digest[3] += d; + m_digest[4] += e; + } + +private: + digest32_t m_digest; + uint8_t m_block[64]; + size_t m_blockByteIndex; + size_t m_byteCount; +}; +} // namespace sha1 diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index 311bd0b9d..a5d11b0eb 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -1,35 +1,24 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include - +#include "common/aes.h" #include "common/config.h" #include "common/logging/log.h" #include "common/path_util.h" #include "core/file_format/trp.h" -static void DecryptEFSM(std::span trophyKey, - std::span NPcommID, - std::span efsmIv, std::span ciphertext, - std::span decrypted) { +static void DecryptEFSM(std::span trophyKey, std::span NPcommID, + std::span efsmIv, std::span ciphertext, + std::span decrypted) { + // Step 1: Encrypt NPcommID + std::array trophyIv{}; + std::array trpKey; + aes::encrypt_cbc(NPcommID.data(), NPcommID.size(), trophyKey.data(), trophyKey.size(), + trophyIv.data(), trpKey.data(), trpKey.size(), false); - // step 1: Encrypt NPcommID - CryptoPP::CBC_Mode::Encryption encrypt; - - std::vector trophyIv(16, 0); - std::vector trpKey(16); - - encrypt.SetKeyWithIV(trophyKey.data(), trophyKey.size(), trophyIv.data()); - encrypt.ProcessData(trpKey.data(), NPcommID.data(), 16); - - // step 2: decrypt efsm. - CryptoPP::CBC_Mode::Decryption decrypt; - decrypt.SetKeyWithIV(trpKey.data(), trpKey.size(), efsmIv.data()); - - for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) { - decrypt.ProcessData(decrypted.data() + i, ciphertext.data() + i, CryptoPP::AES::BLOCKSIZE); - } + // Step 2: Decrypt EFSM + aes::decrypt_cbc(ciphertext.data(), ciphertext.size(), trpKey.data(), trpKey.size(), + efsmIv.data(), decrypted.data(), decrypted.size(), nullptr); } TRP::TRP() = default; @@ -80,7 +69,7 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit return false; } - std::array user_key{}; + std::array user_key{}; hexToBytes(user_key_str.c_str(), user_key.data()); for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) { diff --git a/src/core/module.cpp b/src/core/module.cpp index a18c1141a..1004f4404 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -1,13 +1,12 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include - #include "common/alignment.h" #include "common/arch.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/memory_patcher.h" +#include "common/sha1.h" #include "common/string_util.h" #include "core/aerolib/aerolib.h" #include "core/cpu_patches.h" @@ -65,11 +64,13 @@ static std::string StringToNid(std::string_view symbol) { std::memcpy(input.data(), symbol.data(), symbol.size()); std::memcpy(input.data() + symbol.size(), Salt.data(), Salt.size()); - std::array hash; - CryptoPP::SHA1().CalculateDigest(hash.data(), input.data(), input.size()); + sha1::SHA1::digest8_t hash; + sha1::SHA1 sha; + sha.processBytes(input.data(), input.size()); + sha.getDigestBytes(hash); u64 digest; - std::memcpy(&digest, hash.data(), sizeof(digest)); + std::memcpy(&digest, hash, sizeof(digest)); static constexpr std::string_view codes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"; From df9151481c56512d05fc939313e3c944eac87819 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Sat, 29 Mar 2025 08:32:57 +0000 Subject: [PATCH 084/194] Fix the "Open Update Folder" for folders ending with "-patch" (#2712) * fix open update folder * Update gui_context_menus.h --- src/qt_gui/gui_context_menus.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 7dcb006ba..c13388bbc 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -141,14 +141,16 @@ public: Common::FS::PathToQString(open_update_path, m_games[itemID].path); open_update_path += "-UPDATE"; if (!std::filesystem::exists(Common::FS::PathFromQString(open_update_path))) { + QDesktopServices::openUrl(QUrl::fromLocalFile(open_update_path)); + } else { Common::FS::PathToQString(open_update_path, m_games[itemID].path); open_update_path += "-patch"; if (!std::filesystem::exists(Common::FS::PathFromQString(open_update_path))) { + QDesktopServices::openUrl(QUrl::fromLocalFile(open_update_path)); + } else { QMessageBox::critical(nullptr, tr("Error"), QString(tr("This game has no update folder to open!"))); } - } else { - QDesktopServices::openUrl(QUrl::fromLocalFile(open_update_path)); } } From 9dbc79dc96a4cf439adbead5563e46d1eb301391 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 29 Mar 2025 11:03:40 +0200 Subject: [PATCH 085/194] New Crowdin updates (#2705) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Swedish) * New translations en_us.ts (French) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Norwegian Bokmal) --- src/qt_gui/translations/ar_SA.ts | 174 --------------------------- src/qt_gui/translations/da_DK.ts | 174 --------------------------- src/qt_gui/translations/de_DE.ts | 174 --------------------------- src/qt_gui/translations/el_GR.ts | 174 --------------------------- src/qt_gui/translations/es_ES.ts | 194 ++---------------------------- src/qt_gui/translations/fa_IR.ts | 174 --------------------------- src/qt_gui/translations/fi_FI.ts | 174 --------------------------- src/qt_gui/translations/fr_FR.ts | 200 ++----------------------------- src/qt_gui/translations/hu_HU.ts | 174 --------------------------- src/qt_gui/translations/id_ID.ts | 200 ++----------------------------- src/qt_gui/translations/it_IT.ts | 174 --------------------------- src/qt_gui/translations/ja_JP.ts | 174 --------------------------- src/qt_gui/translations/ko_KR.ts | 174 --------------------------- src/qt_gui/translations/lt_LT.ts | 174 --------------------------- src/qt_gui/translations/nb_NO.ts | 194 ++---------------------------- src/qt_gui/translations/nl_NL.ts | 174 --------------------------- src/qt_gui/translations/pl_PL.ts | 174 --------------------------- src/qt_gui/translations/pt_BR.ts | 174 --------------------------- src/qt_gui/translations/pt_PT.ts | 174 --------------------------- src/qt_gui/translations/ro_RO.ts | 174 --------------------------- src/qt_gui/translations/ru_RU.ts | 174 --------------------------- src/qt_gui/translations/sq_AL.ts | 174 --------------------------- src/qt_gui/translations/sv_SE.ts | 194 ++---------------------------- src/qt_gui/translations/tr_TR.ts | 174 --------------------------- src/qt_gui/translations/uk_UA.ts | 174 --------------------------- src/qt_gui/translations/vi_VN.ts | 174 --------------------------- src/qt_gui/translations/zh_CN.ts | 174 --------------------------- src/qt_gui/translations/zh_TW.ts | 174 --------------------------- 28 files changed, 56 insertions(+), 4928 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 9808fdbe6..f5503e189 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -882,10 +882,6 @@ Error creating shortcut! خطأ في إنشاء الاختصار - - Install PKG - PKG تثبيت - Game اللعبة @@ -978,25 +974,6 @@ أزرار التحكم - - 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 عند التثبيت - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Elf فتح/إضافة مجلد - - Install Packages (PKG) - (PKG) تثبيت الحزم - Boot Game تشغيل اللعبة @@ -1234,10 +1207,6 @@ Configure... ...تكوين - - Install application from a .pkg file - .pkg تثبيت التطبيق من ملف - Recent Games الألعاب الأخيرة @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. لم يتم العثور على ألعاب. الرجاء إضافة ألعابك إلى مكتبتك أولاً. - - PKG Viewer - عارض PKG - Search... ...بحث @@ -1426,70 +1391,6 @@ 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 Version %1 - - - Game is installed: - :اللعبة مثبتة - - - Would you like to install Patch: - :هل ترغب في تثبيت التصحيح - - - DLC Installation - تثبيت المحتوى القابل للتنزيل - - - Would you like to install DLC: %1? - هل ترغب في تثبيت المحتوى القابل للتنزيل: 1%؟ - - - DLC already installed: - :المحتوى القابل للتنزيل مثبت بالفعل - - - Game already installed - اللعبة مثبتة بالفعل - - - 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 تشغيل اللعبة @@ -1498,14 +1399,6 @@ 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! اللعبة قيد التشغيل بالفعل! @@ -1555,73 +1448,6 @@ إظهار العلامات أسفل الأيقونات - - PKGViewer - - Open Folder - فتح المجلد - - - PKG ERROR - PKG خطأ في - - - Name - اسم - - - Serial - سيريال - - - Installed - مثبت - - - Size - حجم - - - Category - الفئة - - - Type - النوع - - - App Ver - إصدار - - - FW - FW - - - Region - منطقة - - - Flags - Flags - - - Path - مسار - - - File - ملف - - - Unknown - غير معروف - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 1547a0e13..658ac118f 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Game @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ Only one file can be selected! Kun én fil kan vælges! - - PKG Extraction - PKG-udtrækning - - - Patch detected! - Opdatering detekteret! - - - PKG and Game versions match: - PKG og spilversioner matcher: - - - Would you like to overwrite? - Vil du overskrive? - - - PKG Version %1 is older than installed version: - PKG Version %1 er ældre end den installerede version: - - - Game is installed: - Spillet er installeret: - - - Would you like to install Patch: - Vil du installere opdateringen: - - - DLC Installation - DLC Installation - - - Would you like to install DLC: %1? - Vil du installere DLC: %1? - - - DLC already installed: - DLC allerede installeret: - - - Game already installed - Spillet er allerede installeret - - - PKG ERROR - PKG FEJL - - - Extracting PKG %1/%2 - Udvinding af PKG %1/%2 - - - Extraction Finished - Udvinding afsluttet - - - Game successfully installed at %1 - Spillet blev installeret succesfuldt på %1 - - - File doesn't appear to be a valid PKG file - Filen ser ikke ud til at være en gyldig PKG-fil - Run Game Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Open Folder - - - PKG ERROR - PKG FEJL - - - Name - Navn - - - Serial - Seriel - - - Installed - Installed - - - Size - Størrelse - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Region - - - Flags - Flags - - - Path - Sti - - - File - File - - - Unknown - Ukendt - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index c0e43065b..b6cd3105f 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -882,10 +882,6 @@ Error creating shortcut! Fehler beim Erstellen der Verknüpfung! - - Install PKG - PKG installieren - Game Spiel @@ -978,25 +974,6 @@ Tastenbelegung - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Wähle Ordner - - - Select which directory you want to install to. - Wählen Sie das Verzeichnis aus, in das Sie installieren möchten. - - - Install All Queued to Selected Folder - Installieren Sie alles aus der Warteschlange in den ausgewählten Ordner - - - Delete PKG File on Install - PKG-Datei beim Installieren löschen - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Elf-Ordner öffnen/hinzufügen - - Install Packages (PKG) - Pakete installieren (PKG) - Boot Game Spiel starten @@ -1234,10 +1207,6 @@ Configure... Konfigurieren... - - Install application from a .pkg file - Installiere Anwendung aus .pkg-Datei - Recent Games Zuletzt gespielt @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG-Anschauer - Search... Suchen... @@ -1426,70 +1391,6 @@ Only one file can be selected! Es kann nur eine Datei ausgewählt werden! - - PKG Extraction - PKG-Extraktion - - - Patch detected! - Patch erkannt! - - - PKG and Game versions match: - PKG- und Spielversionen stimmen überein: - - - Would you like to overwrite? - Willst du überschreiben? - - - PKG Version %1 is older than installed version: - PKG-Version %1 ist älter als die installierte Version: - - - Game is installed: - Spiel ist installiert: - - - Would you like to install Patch: - Willst du den Patch installieren: - - - DLC Installation - DLC-Installation - - - Would you like to install DLC: %1? - Willst du das DLC installieren: %1? - - - DLC already installed: - DLC bereits installiert: - - - Game already installed - Spiel bereits installiert - - - PKG ERROR - PKG-FEHLER - - - Extracting PKG %1/%2 - Extrahiere PKG %1/%2 - - - Extraction Finished - Extraktion abgeschlossen - - - Game successfully installed at %1 - Spiel erfolgreich installiert auf %1 - - - File doesn't appear to be a valid PKG file - Die Datei scheint keine gültige PKG-Datei zu sein - Run Game Spiel ausführen @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin Datei nicht gefunden - - PKG File (*.PKG *.pkg) - PKG-Datei (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG ist ein Patch oder DLC, bitte installieren Sie zuerst das Spiel! - Game is already running! Spiel läuft bereits! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Ordner öffnen - - - PKG ERROR - PKG-FEHLER - - - Name - Name - - - Serial - Seriennummer - - - Installed - Installiert - - - Size - Größe - - - Category - Kategorie - - - Type - Typ - - - App Ver - App Ver - - - FW - FW - - - Region - Region - - - Flags - Markierungen - - - Path - Pfad - - - File - Datei - - - Unknown - Unbekannt - - - Package - Paket - - SettingsDialog diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index e6fa989aa..d1cf0d4a6 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Game @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ 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 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 Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Open Folder - - - PKG ERROR - ΣΦΑΛΜΑ PKG - - - Name - Όνομα - - - Serial - Σειριακός αριθμός - - - Installed - Installed - - - Size - Μέγεθος - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Περιοχή - - - Flags - Flags - - - Path - Διαδρομή - - - File - File - - - Unknown - Άγνωστο - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 288c445c3..bbd49f61d 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -882,10 +882,6 @@ Error creating shortcut! ¡Error al crear el acceso directo! - - Install PKG - Instalar PKG - Game Juego @@ -978,25 +974,6 @@ Asignación de Teclas - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Elegir carpeta - - - Select which directory you want to install to. - Selecciona el directorio de instalación. - - - Install All Queued to Selected Folder - Instalar toda la cola en la carpeta seleccionada - - - Delete PKG File on Install - Eliminar archivo PKG tras la instalación - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Abrir/Agregar carpeta Elf - - Install Packages (PKG) - Instalar paquetes (PKG) - Boot Game Iniciar juego @@ -1234,10 +1207,6 @@ Configure... Configurar... - - Install application from a .pkg file - Instalar aplicación desde un archivo .pkg - Recent Games Juegos recientes @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No se encontraron juegos. Por favor, añade tus juegos a tu biblioteca primero. - - PKG Viewer - Vista PKG - Search... Buscar... @@ -1426,70 +1391,6 @@ Only one file can be selected! ¡Solo se puede seleccionar un archivo! - - PKG Extraction - Extracción de PKG - - - Patch detected! - ¡Actualización detectada! - - - PKG and Game versions match: - Las versiones de PKG y del juego coinciden: - - - Would you like to overwrite? - ¿Desea sobrescribir? - - - PKG Version %1 is older than installed version: - La versión de PKG %1 es más antigua que la versión instalada: - - - Game is installed: - El juego está instalado: - - - Would you like to install Patch: - ¿Desea instalar la actualización: - - - DLC Installation - Instalación de DLC - - - Would you like to install DLC: %1? - ¿Desea instalar el DLC: %1? - - - DLC already installed: - DLC ya instalado: - - - Game already installed - Juego ya instalado - - - PKG ERROR - ERROR PKG - - - Extracting PKG %1/%2 - Extrayendo PKG %1/%2 - - - Extraction Finished - Extracción terminada - - - Game successfully installed at %1 - Juego instalado exitosamente en %1 - - - File doesn't appear to be a valid PKG file - El archivo parece no ser un archivo PKG válido - Run Game Ejecutar juego @@ -1498,14 +1399,6 @@ Eboot.bin file not found Archivo Eboot.bin no encontrado - - PKG File (*.PKG *.pkg) - Archivo PKG (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - El archivo PKG es un parche o DLC, ¡debes instalar el juego primero! - Game is already running! ¡El juego ya se está ejecutando! @@ -1516,110 +1409,43 @@ Play - Play + Jugar Pause - Pause + Pausar Stop - Stop + Detener Restart - Restart + Reiniciar Full Screen - Full Screen + Pantalla completa Controllers - Controllers + Controles Keyboard - Keyboard + Teclado Refresh List - Refresh List + Actualizar lista Resume - Resume + Reanudar Show Labels Under Icons - Show Labels Under Icons - - - - PKGViewer - - Open Folder - Abrir Carpeta - - - PKG ERROR - ERROR PKG - - - Name - Nombre - - - Serial - Número de Serie - - - Installed - Instalado - - - Size - Tamaño - - - Category - Categoría - - - Type - Tipo - - - App Ver - Versión de la Aplicación - - - FW - FW - - - Region - Región - - - Flags - Etiquetas - - - Path - Ruta - - - File - Archivo - - - Unknown - Desconocido - - - Package - Paquete + Mostrar etiquetas debajo de los iconos diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 1b8813a80..f1f2c62ab 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -882,10 +882,6 @@ Error creating shortcut! مشکلی در هنگام ساخت میانبر بوجود آمد! - - Install PKG - نصب PKG - Game بازی @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - ShadPS4 - انتخاب محل نصب بازی - - - Select which directory you want to install to. - محلی را که می‌خواهید در آن نصب شود، انتخاب کنید. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder ELF بازکردن/ساختن پوشه - - Install Packages (PKG) - نصب بسته (PKG) - Boot Game اجرای بازی @@ -1234,10 +1207,6 @@ Configure... ...تنظیمات - - Install application from a .pkg file - .PKG نصب بازی از فایل - Recent Games بازی های اخیر @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG مشاهده گر - Search... جست و جو... @@ -1426,70 +1391,6 @@ 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 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 Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - بازکردن پوشه - - - PKG ERROR - PKG ارور فایل - - - Name - نام - - - Serial - سریال - - - Installed - Installed - - - Size - اندازه - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - منطقه - - - Flags - Flags - - - Path - مسیر - - - File - فایل - - - Unknown - ناشناخته - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index cd880fb23..d07f82bd5 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -882,10 +882,6 @@ Error creating shortcut! Virhe pikakuvakkeen luonnissa! - - Install PKG - Asenna PKG - Game Peli @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Valitse hakemisto - - - Select which directory you want to install to. - Valitse, mihin hakemistoon haluat asentaa. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Avaa/Lisää Elf Hakemisto - - Install Packages (PKG) - Asenna Paketteja (PKG) - Boot Game Käynnistä Peli @@ -1234,10 +1207,6 @@ Configure... Asetukset... - - Install application from a .pkg file - Asenna sovellus .pkg tiedostosta - Recent Games Viimeisimmät Pelit @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Selain - Search... Hae... @@ -1426,70 +1391,6 @@ Only one file can be selected! Vain yksi tiedosto voi olla valittuna! - - PKG Extraction - PKG:n purku - - - Patch detected! - Päivitys havaittu! - - - PKG and Game versions match: - PKG- ja peliversiot vastaavat: - - - Would you like to overwrite? - Haluatko korvata? - - - PKG Version %1 is older than installed version: - PKG-versio %1 on vanhempi kuin asennettu versio: - - - Game is installed: - Peli on asennettu: - - - Would you like to install Patch: - Haluatko asentaa päivityksen: - - - DLC Installation - Lisäsisällön asennus - - - Would you like to install DLC: %1? - Haluatko asentaa lisäsisällön: %1? - - - DLC already installed: - Lisäsisältö on jo asennettu: - - - Game already installed - Peli on jo asennettu - - - PKG ERROR - PKG VIRHE - - - Extracting PKG %1/%2 - Purkaminen PKG %1/%2 - - - Extraction Finished - Purku valmis - - - Game successfully installed at %1 - Peli asennettu onnistuneesti kohtaan %1 - - - File doesn't appear to be a valid PKG file - Tiedosto ei vaikuta olevan kelvollinen PKG-tiedosto - Run Game Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Avaa Hakemisto - - - PKG ERROR - PKG VIRHE - - - Name - Nimi - - - Serial - Sarjanumero - - - Installed - Installed - - - Size - Koko - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Alue - - - Flags - Flags - - - Path - Polku - - - File - Tiedosto - - - Unknown - Tuntematon - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 1f7a726cd..89599e32c 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -882,10 +882,6 @@ Error creating shortcut! Erreur lors de la création du raccourci ! - - Install PKG - Installer un PKG - Game Jeu @@ -978,25 +974,6 @@ Raccourcis - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choisir un répertoire - - - Select which directory you want to install to. - Sélectionnez le répertoire où vous souhaitez effectuer l'installation. - - - Install All Queued to Selected Folder - Installer toute la file d’attente dans le dossier sélectionné - - - Delete PKG File on Install - Supprimer le fichier PKG à l'installation - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Ouvrir/Ajouter un dossier ELF - - Install Packages (PKG) - Installer des packages (PKG) - Boot Game Démarrer un jeu @@ -1234,10 +1207,6 @@ Configure... Configurer... - - Install application from a .pkg file - Installer une application depuis un fichier .pkg - Recent Games Jeux récents @@ -1308,15 +1277,11 @@ Trophy Viewer - Trophy Viewer + Visionneuse de trophées No games found. Please add your games to your library first. - No games found. Please add your games to your library first. - - - PKG Viewer - Visionneuse PKG + Aucun jeu trouvé. Veuillez d'abord ajouter vos jeux à votre bibliothèque. Search... @@ -1426,70 +1391,6 @@ Only one file can be selected! Un seul fichier peut être sélectionné ! - - PKG Extraction - Extraction du PKG - - - Patch detected! - Patch détecté ! - - - PKG and Game versions match: - Les versions PKG et jeu correspondent: - - - Would you like to overwrite? - Souhaitez-vous remplacer ? - - - PKG Version %1 is older than installed version: - La version PKG %1 est plus ancienne que la version installée: - - - Game is installed: - Jeu installé: - - - Would you like to install Patch: - Souhaitez-vous installer le patch: - - - DLC Installation - Installation du DLC - - - Would you like to install DLC: %1? - Souhaitez-vous installer le DLC: %1 ? - - - DLC already installed: - DLC déjà installé: - - - Game already installed - Jeu déjà installé - - - PKG ERROR - Erreur PKG - - - Extracting PKG %1/%2 - Extraction PKG %1/%2 - - - Extraction Finished - Extraction terminée - - - Game successfully installed at %1 - Jeu installé avec succès dans %1 - - - File doesn't appear to be a valid PKG file - Le fichier ne semble pas être un PKG valide - Run Game Lancer le jeu @@ -1498,14 +1399,6 @@ Eboot.bin file not found Fichier Eboot.bin introuvable - - PKG File (*.PKG *.pkg) - Fichier PKG (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG est un patch ou DLC, veuillez d'abord installer le jeu ! - Game is already running! Le jeu est déjà en cours ! @@ -1516,110 +1409,43 @@ Play - Play + Jouer Pause - Pause + Pause Stop - Stop + Stop Restart - Restart + Redémarrer Full Screen - Full Screen + Plein écran Controllers - Controllers + Contrôleurs Keyboard - Keyboard + Clavier Refresh List - Refresh List + Rafraîchir la liste Resume - Resume + Reprendre Show Labels Under Icons - Show Labels Under Icons - - - - PKGViewer - - Open Folder - Ouvrir un dossier - - - PKG ERROR - Erreur PKG - - - Name - Nom - - - Serial - Numéro de série - - - Installed - Installé - - - Size - Taille - - - Category - Catégorie - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Région - - - Flags - Les indicateurs - - - Path - Répertoire - - - File - Fichier - - - Unknown - Inconnu - - - Package - Package + Afficher les libellés sous les icônes @@ -2241,7 +2067,7 @@ Select Game: - Select Game: + Sélectionnez un jeu: Progress diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 8746058b3..85b6f2c95 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -882,10 +882,6 @@ Error creating shortcut! Hiba a parancsikon létrehozásával! - - Install PKG - PKG telepítése - Game Játék @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Mappa kiválasztása - - - Select which directory you want to install to. - Válassza ki a mappát a játékok telepítésére. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder ELF Mappa Megnyitása/Hozzáadása - - Install Packages (PKG) - PKG-k Telepítése (PKG) - Boot Game Játék Indítása @@ -1234,10 +1207,6 @@ Configure... Konfigurálás... - - Install application from a .pkg file - Program telepítése egy .pkg fájlból - Recent Games Legutóbbi Játékok @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Nézegető - Search... Keresés... @@ -1426,70 +1391,6 @@ Only one file can be selected! Csak egy fájl választható ki! - - PKG Extraction - PKG kicsomagolás - - - Patch detected! - Frissítés észlelve! - - - PKG and Game versions match: - A PKG és a játék verziói egyeznek: - - - Would you like to overwrite? - Szeretné felülírni? - - - PKG Version %1 is older than installed version: - A(z) %1-es PKG verzió régebbi, mint a telepített verzió: - - - Game is installed: - A játék telepítve van: - - - Would you like to install Patch: - Szeretné telepíteni a frissítést: - - - DLC Installation - DLC Telepítés - - - Would you like to install DLC: %1? - Szeretné telepíteni a %1 DLC-t? - - - DLC already installed: - DLC már telepítve: - - - Game already installed - A játék már telepítve van - - - PKG ERROR - PKG HIBA - - - Extracting PKG %1/%2 - PKG kicsomagolása %1/%2 - - - Extraction Finished - Kicsomagolás befejezve - - - Game successfully installed at %1 - A játék sikeresen telepítve itt: %1 - - - File doesn't appear to be a valid PKG file - A fájl nem tűnik érvényes PKG fájlnak - Run Game Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Mappa Megnyitása - - - PKG ERROR - PKG HIBA - - - Name - Név - - - Serial - Sorozatszám - - - Installed - Installed - - - Size - Méret - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Régió - - - Flags - Flags - - - Path - Útvonal - - - File - Fájl - - - Unknown - Ismeretlen - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index 5960715ae..1858da2a7 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -7,22 +7,22 @@ AboutDialog About shadPS4 - About shadPS4 + Tentang shadPS4 shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 adalah emulator sumber terbuka eksperimental untuk PlayStation 4. This software should not be used to play games you have not legally obtained. - This software should not be used to play games you have not legally obtained. + Perangkat lunak ini tidak boleh digunakan untuk memainkan permainan yang tidak Anda peroleh secara legal. CheatsPatches Cheats / Patches for - Cheats / Patches for + Kecurangan / Tambalan untuk Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n @@ -34,7 +34,7 @@ Serial: - Serial: + Seri: Version: @@ -400,38 +400,38 @@ Playable - Playable + Dapat dimainkan ControlSettings Configure Controls - Configure Controls + Konfigurasi Kontrol D-Pad - D-Pad + Tombol arah Up - Up + Atas Left - Left + Kiri Right - Right + Kanan Down - Down + Bawah Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + Zona Mati Stik Kiri (standar: 2, maksimum: 127) Left Deadzone @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Game @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ Only one file can be selected! Hanya satu file yang bisa dipilih! - - PKG Extraction - Ekstraksi PKG - - - Patch detected! - Patch terdeteksi! - - - PKG and Game versions match: - Versi PKG dan Game cocok: - - - Would you like to overwrite? - Apakah Anda ingin menimpa? - - - PKG Version %1 is older than installed version: - Versi PKG %1 lebih lama dari versi yang terpasang: - - - Game is installed: - Game telah terpasang: - - - Would you like to install Patch: - Apakah Anda ingin menginstal patch: - - - DLC Installation - Instalasi DLC - - - Would you like to install DLC: %1? - Apakah Anda ingin menginstal DLC: %1? - - - DLC already installed: - DLC sudah terpasang: - - - Game already installed - Game sudah terpasang - - - PKG ERROR - KESALAHAN PKG - - - Extracting PKG %1/%2 - Mengekstrak PKG %1/%2 - - - Extraction Finished - Ekstraksi Selesai - - - Game successfully installed at %1 - Game berhasil dipasang di %1 - - - File doesn't appear to be a valid PKG file - File tampaknya bukan file PKG yang valid - Run Game Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Open Folder - - - PKG ERROR - KESALAHAN PKG - - - Name - Nama - - - Serial - Serial - - - Installed - Installed - - - Size - Ukuran - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Wilayah - - - Flags - Flags - - - Path - Jalur - - - File - File - - - Unknown - Tidak Dikenal - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 3e95e3df6..af321bd92 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -882,10 +882,6 @@ Error creating shortcut! Errore nella creazione della scorciatoia! - - Install PKG - Installa PKG - Game Gioco @@ -978,25 +974,6 @@ Associazioni dei pulsanti - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Scegli cartella - - - Select which directory you want to install to. - Seleziona in quale cartella vuoi effettuare l'installazione. - - - Install All Queued to Selected Folder - Installa tutto in coda nella Cartella Selezionata - - - Delete PKG File on Install - Elimina file PKG dopo Installazione - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Apri/Aggiungi cartella Elf - - Install Packages (PKG) - Installa Pacchetti (PKG) - Boot Game Avvia Gioco @@ -1234,10 +1207,6 @@ Configure... Configura... - - Install application from a .pkg file - Installa applicazione da un file .pkg - Recent Games Giochi Recenti @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. Nessun gioco trovato. Aggiungi prima i tuoi giochi alla tua libreria. - - PKG Viewer - Visualizzatore PKG - Search... Cerca... @@ -1426,70 +1391,6 @@ Only one file can be selected! Si può selezionare solo un file! - - PKG Extraction - Estrazione file PKG - - - Patch detected! - Patch rilevata! - - - PKG and Game versions match: - Le versioni di PKG e del Gioco corrispondono: - - - Would you like to overwrite? - Vuoi sovrascrivere? - - - PKG Version %1 is older than installed version: - La versione PKG %1 è più vecchia rispetto alla versione installata: - - - Game is installed: - Gioco installato: - - - Would you like to install Patch: - Vuoi installare la patch: - - - DLC Installation - Installazione DLC - - - Would you like to install DLC: %1? - Vuoi installare il DLC: %1? - - - DLC already installed: - DLC già installato: - - - Game already installed - Gioco già installato - - - PKG ERROR - ERRORE PKG - - - Extracting PKG %1/%2 - Estrazione file PKG %1/%2 - - - Extraction Finished - Estrazione Completata - - - Game successfully installed at %1 - Gioco installato correttamente in %1 - - - File doesn't appear to be a valid PKG file - Il file sembra non essere un file PKG valido - Run Game Esegui Gioco @@ -1498,14 +1399,6 @@ Eboot.bin file not found File Eboot.bin non trovato - - PKG File (*.PKG *.pkg) - File PKG (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - Il file PKG è una patch o DLC, si prega di installare prima il gioco! - Game is already running! Il gioco è già in esecuzione! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Apri Cartella - - - PKG ERROR - ERRORE PKG - - - Name - Nome - - - Serial - Seriale - - - Installed - Installato - - - Size - Dimensione - - - Category - Categoria - - - Type - Tipo - - - App Ver - Vers. App. - - - FW - FW - - - Region - Regione - - - Flags - Segnalazioni - - - Path - Percorso - - - File - File - - - Unknown - Sconosciuto - - - Package - Pacchetto - - SettingsDialog diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 1aaa3fa7c..d4b8fa5b7 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -882,10 +882,6 @@ Error creating shortcut! ショートカットの作成に失敗しました! - - Install PKG - PKGをインストール - Game ゲーム @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - ディレクトリを選択 - - - Select which directory you want to install to. - インストール先のディレクトリを選択してください。 - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - インストール時にPKGファイルを削除 - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Elfフォルダを開く/追加する - - Install Packages (PKG) - パッケージをインストール (PKG) - Boot Game ゲームを起動 @@ -1234,10 +1207,6 @@ Configure... 設定... - - Install application from a .pkg file - .pkgファイルからアプリケーションをインストール - Recent Games 最近プレイしたゲーム @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKGビューアー - Search... 検索... @@ -1426,70 +1391,6 @@ Only one file can be selected! 1つのファイルしか選択できません! - - 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 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 ゲームを実行 @@ -1498,14 +1399,6 @@ 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 is a patch or DLC, please install the game first! - Game is already running! ゲームは既に実行されています! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - フォルダーを開く - - - PKG ERROR - PKGエラー - - - Name - 名前 - - - Serial - シリアル - - - Installed - Installed - - - Size - サイズ - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - 地域 - - - Flags - Flags - - - Path - パス - - - File - ファイル - - - Unknown - 不明 - - - Package - パッケージ - - SettingsDialog diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 9dd06028d..9d4b58c79 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Game @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ Only one file can be selected! Only one file can be selected! - - PKG Extraction - PKG Extraction - - - Patch detected! - Patch detected! - - - PKG and Game versions match: - PKG and Game versions match: - - - Would you like to overwrite? - Would you like to overwrite? - - - PKG Version %1 is older than installed version: - PKG Version %1 is older than installed version: - - - Game is installed: - Game is installed: - - - Would you like to install Patch: - Would you like to install Patch: - - - DLC Installation - DLC Installation - - - Would you like to install DLC: %1? - Would you like to install DLC: %1? - - - DLC already installed: - DLC already installed: - - - Game already installed - Game already installed - - - PKG ERROR - PKG ERROR - - - Extracting PKG %1/%2 - Extracting PKG %1/%2 - - - Extraction Finished - Extraction Finished - - - Game successfully installed at %1 - Game successfully installed at %1 - - - File doesn't appear to be a valid PKG file - File doesn't appear to be a valid PKG file - Run Game Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Open Folder - - - PKG ERROR - PKG ERROR - - - Name - Name - - - Serial - Serial - - - Installed - Installed - - - Size - Size - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Region - - - Flags - Flags - - - Path - Path - - - File - File - - - Unknown - 알 수 없음 - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 6e98ddc45..55a9fac0c 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Žaidimas @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ Only one file can be selected! Galite pasirinkti tik vieną failą! - - PKG Extraction - PKG ištraukimas - - - Patch detected! - Rasta atnaujinimą! - - - PKG and Game versions match: - PKG ir žaidimo versijos sutampa: - - - Would you like to overwrite? - Ar norite perrašyti? - - - PKG Version %1 is older than installed version: - PKG versija %1 yra senesnė nei įdiegta versija: - - - Game is installed: - Žaidimas įdiegtas: - - - Would you like to install Patch: - Ar norite įdiegti atnaujinimą: - - - DLC Installation - DLC diegimas - - - Would you like to install DLC: %1? - Ar norite įdiegti DLC: %1? - - - DLC already installed: - DLC jau įdiegtas: - - - Game already installed - Žaidimas jau įdiegtas - - - PKG ERROR - PKG KLAIDA - - - Extracting PKG %1/%2 - Ekstrakcinis PKG %1/%2 - - - Extraction Finished - Ekstrakcija baigta - - - Game successfully installed at %1 - Žaidimas sėkmingai įdiegtas %1 - - - File doesn't appear to be a valid PKG file - Failas atrodo, kad nėra galiojantis PKG failas - Run Game Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Open Folder - - - PKG ERROR - PKG KLAIDA - - - Name - Vardas - - - Serial - Serijinis numeris - - - Installed - Installed - - - Size - Dydis - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Regionas - - - Flags - Flags - - - Path - Kelias - - - File - File - - - Unknown - Nežinoma - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 6faff415e..4a0835c1c 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -882,10 +882,6 @@ Error creating shortcut! Feil ved opprettelse av snarvei! - - Install PKG - Installer PKG - Game Spill @@ -978,25 +974,6 @@ Hurtigtast - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Velg mappe - - - Select which directory you want to install to. - Velg hvilken mappe du vil installere til. - - - Install All Queued to Selected Folder - Installer alle i kø til den valgte mappa - - - Delete PKG File on Install - Slett PKG-fila ved installering - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Åpne eller legg til Elf-mappe - - Install Packages (PKG) - Installer pakker (PKG) - Boot Game Start spill @@ -1234,10 +1207,6 @@ Configure... Sett opp … - - Install application from a .pkg file - Installer fra en .pkg fil - Recent Games Nylige spill @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. Fant ingen spill. Legg til spillene dine i biblioteket først. - - PKG Viewer - PKG-viser - Search... Søk … @@ -1426,70 +1391,6 @@ Only one file can be selected! Kun én fil kan velges! - - PKG Extraction - PKG-utpakking - - - Patch detected! - Programrettelse oppdaget! - - - PKG and Game versions match: - PKG og spillversjoner stemmer overens: - - - Would you like to overwrite? - Ønsker du å overskrive? - - - PKG Version %1 is older than installed version: - PKG-versjon %1 er eldre enn installert versjon: - - - Game is installed: - Spillet er installert: - - - Would you like to install Patch: - Ønsker du å installere programrettelsen: - - - DLC Installation - DLC installasjon - - - Would you like to install DLC: %1? - Ønsker du å installere DLC: %1? - - - DLC already installed: - DLC allerede installert: - - - Game already installed - Spillet er allerede installert - - - PKG ERROR - PKG FEIL - - - Extracting PKG %1/%2 - Pakker ut PKG %1/%2 - - - Extraction Finished - Utpakking fullført - - - Game successfully installed at %1 - Spillet ble installert i %1 - - - File doesn't appear to be a valid PKG file - Fila ser ikke ut til å være en gyldig PKG-fil - Run Game Kjør spill @@ -1498,14 +1399,6 @@ Eboot.bin file not found Klarte ikke finne Eboot.bin-fila - - PKG File (*.PKG *.pkg) - PKG-fil (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG er en programrettelse eller DLC. Installer spillet først! - Game is already running! Spillet kjører allerede! @@ -1516,110 +1409,43 @@ Play - Play + Spill Pause - Pause + Pause Stop - Stop + Stopp Restart - Restart + Start på nytt Full Screen - Full Screen + Fullskjerm Controllers - Controllers + Kontroller Keyboard - Keyboard + Tastatur Refresh List - Refresh List + Oppdater lista Resume - Resume + Gjenoppta Show Labels Under Icons - Show Labels Under Icons - - - - PKGViewer - - Open Folder - Åpne mappe - - - PKG ERROR - PKG FEIL - - - Name - Navn - - - Serial - Serienummer - - - Installed - Installert - - - Size - Størrelse - - - Category - Kategori - - - Type - Type - - - App Ver - Programversjon - - - FW - FV - - - Region - Region - - - Flags - Flagg - - - Path - Adresse - - - File - Fil - - - Unknown - Ukjent - - - Package - Pakke + Vis merkelapp under ikoner diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 376eea5ef..918da6a67 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Game @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ Only one file can be selected! Je kunt slechts één bestand selecteren! - - PKG Extraction - PKG-extractie - - - Patch detected! - Patch gedetecteerd! - - - PKG and Game versions match: - PKG- en gameversies komen overeen: - - - Would you like to overwrite? - Wilt u overschrijven? - - - PKG Version %1 is older than installed version: - PKG-versie %1 is ouder dan de geïnstalleerde versie: - - - Game is installed: - Game is geïnstalleerd: - - - Would you like to install Patch: - Wilt u de patch installeren: - - - DLC Installation - DLC-installatie - - - Would you like to install DLC: %1? - Wilt u DLC installeren: %1? - - - DLC already installed: - DLC al geïnstalleerd: - - - Game already installed - Game al geïnstalleerd - - - PKG ERROR - PKG FOUT - - - Extracting PKG %1/%2 - PKG %1/%2 aan het extraheren - - - Extraction Finished - Extractie voltooid - - - Game successfully installed at %1 - Spel succesvol geïnstalleerd op %1 - - - File doesn't appear to be a valid PKG file - Het bestand lijkt geen geldig PKG-bestand te zijn - Run Game Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Open Folder - - - PKG ERROR - PKG FOUT - - - Name - Naam - - - Serial - Serienummer - - - Installed - Installed - - - Size - Grootte - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Regio - - - Flags - Flags - - - Path - Pad - - - File - File - - - Unknown - Onbekend - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index a77b43e09..39950d6ec 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -882,10 +882,6 @@ Error creating shortcut! Utworzenie skrótu zakończone niepowodzeniem! - - Install PKG - Zainstaluj PKG - Game Gra @@ -978,25 +974,6 @@ Przypisanie klawiszy - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Wybierz katalog - - - Select which directory you want to install to. - Wybierz katalog, do którego chcesz zainstalować. - - - Install All Queued to Selected Folder - Zainstaluj wszystkie oczekujące do wybranego folderu - - - Delete PKG File on Install - Usuń plik PKG po instalacji - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Otwórz/Dodaj folder Elf - - Install Packages (PKG) - Zainstaluj paczkę (PKG) - Boot Game Uruchom grę @@ -1234,10 +1207,6 @@ Configure... Konfiguruj... - - Install application from a .pkg file - Zainstaluj aplikacje z pliku .pkg - Recent Games Ostatnie gry @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. Nie znaleziono gier. Najpierw dodaj swoje gry do swojej biblioteki. - - PKG Viewer - Menedżer plików PKG - Search... Szukaj... @@ -1426,70 +1391,6 @@ Only one file can be selected! Można wybrać tylko jeden plik! - - PKG Extraction - Wypakowywanie PKG - - - Patch detected! - Wykryto łatkę! - - - PKG and Game versions match: - Wersje PKG i gry są zgodne: - - - Would you like to overwrite? - Czy chcesz nadpisać? - - - PKG Version %1 is older than installed version: - Wersja PKG %1 jest starsza niż zainstalowana wersja: - - - Game is installed: - Gra jest zainstalowana: - - - Would you like to install Patch: - Czy chcesz zainstalować łatkę: - - - DLC Installation - Instalacja dodatkowej zawartości (DLC) - - - Would you like to install DLC: %1? - Czy chcesz zainstalować dodatkową zawartość (DLC): %1? - - - DLC already installed: - Dodatkowa zawartość (DLC) już zainstalowana: - - - Game already installed - Gra już zainstalowana - - - PKG ERROR - BŁĄD PKG - - - Extracting PKG %1/%2 - Wypakowywanie PKG %1/%2 - - - Extraction Finished - Wypakowywanie zakończone - - - Game successfully installed at %1 - Gra pomyślnie zainstalowana w %1 - - - File doesn't appear to be a valid PKG file - Plik nie wydaje się być prawidłowym plikiem PKG - Run Game Uruchom grę @@ -1498,14 +1399,6 @@ Eboot.bin file not found Nie znaleziono pliku EBOOT.BIN - - PKG File (*.PKG *.pkg) - Plik PKG (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG jest aktualizacją lub dodatkową zawartością (DLC), najpierw zainstaluj grę! - Game is already running! Gra jest już uruchomiona! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Otwórz folder - - - PKG ERROR - BŁĄD PKG - - - Name - Nazwa - - - Serial - Numer seryjny - - - Installed - Zainstalowano - - - Size - Rozmiar - - - Category - Kategoria - - - Type - Typ - - - App Ver - Wersja aplikacji - - - FW - Oprogramowanie - - - Region - Region - - - Flags - Flagi - - - Path - Ścieżka - - - File - Plik - - - Unknown - Nieznany - - - Package - Paczka - - SettingsDialog diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index ea82086f3..5d1582b5a 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -882,10 +882,6 @@ Error creating shortcut! Erro ao criar atalho! - - Install PKG - Instalar PKG - Game Jogo @@ -978,25 +974,6 @@ Teclas de atalho - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Escolha o diretório - - - Select which directory you want to install to. - Selecione o diretório em que você deseja instalar. - - - Install All Queued to Selected Folder - Instalar Tudo da Fila para a Pasta Selecionada - - - Delete PKG File on Install - Excluir o PKG após a Instalação - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Abrir/Adicionar pasta Elf - - Install Packages (PKG) - Instalar Pacotes (PKG) - Boot Game Iniciar Jogo @@ -1234,10 +1207,6 @@ Configure... Configurar... - - Install application from a .pkg file - Instalar aplicativo de um arquivo .pkg - Recent Games Jogos Recentes @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. Nenhum jogo encontrado. Adicione seus jogos à sua biblioteca primeiro. - - PKG Viewer - Visualizador de PKG - Search... Pesquisar... @@ -1426,70 +1391,6 @@ Only one file can be selected! Apenas um arquivo pode ser selecionado! - - PKG Extraction - Extração de PKG - - - Patch detected! - Atualização detectada! - - - PKG and Game versions match: - As versões do PKG e do Jogo são iguais: - - - Would you like to overwrite? - Você gostaria de sobrescrever? - - - PKG Version %1 is older than installed version: - A Versão do PKG %1 é mais antiga do que a versão instalada: - - - Game is installed: - Jogo instalado: - - - Would you like to install Patch: - Você gostaria de instalar a atualização: - - - DLC Installation - Instalação de DLC - - - Would you like to install DLC: %1? - Você gostaria de instalar o DLC: %1? - - - DLC already installed: - DLC já está instalado: - - - Game already installed - O jogo já está instalado: - - - PKG ERROR - ERRO DE PKG - - - Extracting PKG %1/%2 - Extraindo PKG %1/%2 - - - Extraction Finished - Extração Concluída - - - Game successfully installed at %1 - Jogo instalado com sucesso em %1 - - - File doesn't appear to be a valid PKG file - O arquivo não parece ser um arquivo PKG válido - Run Game Executar Jogo @@ -1498,14 +1399,6 @@ Eboot.bin file not found Arquivo Eboot.bin não encontrado - - PKG File (*.PKG *.pkg) - Arquivo PKG (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - O PKG é um patch ou DLC, por favor instale o jogo primeiro! - Game is already running! O jogo já está executando! @@ -1555,73 +1448,6 @@ Mostrar Rótulos Sob Ícones - - PKGViewer - - Open Folder - Abrir Pasta - - - PKG ERROR - ERRO DE PKG - - - Name - Nome - - - Serial - Serial - - - Installed - Instalado - - - Size - Tamanho - - - Category - Categoria - - - Type - Tipo - - - App Ver - Versão do App - - - FW - FW - - - Region - Região - - - Flags - Flags - - - Path - Caminho - - - File - Arquivo - - - Unknown - Desconhecido - - - Package - Pacote - - SettingsDialog diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index 7ca3eebb5..a155a6324 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -882,10 +882,6 @@ Error creating shortcut! Erro ao criar atalho! - - Install PKG - Instalar PKG - Game Jogo @@ -978,25 +974,6 @@ Combinações de Teclas - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Escolher diretório - - - Select which directory you want to install to. - Selecione o diretório em que deseja instalar. - - - Install All Queued to Selected Folder - Instalar Todos os Pendentes para a Pasta Selecionada - - - Delete PKG File on Install - Eliminar Ficheiro PKG após Instalação - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Abrir/Adicionar pasta Elf - - Install Packages (PKG) - Instalar Pacotes (PKG) - Boot Game Iniciar Jogo @@ -1234,10 +1207,6 @@ Configure... Configurar... - - Install application from a .pkg file - Instalar aplicação através de um ficheiro .pkg - Recent Games Jogos Recentes @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. Nenhum jogo encontrado. Por favor, adicione os seus jogos à sua biblioteca primeiro. - - PKG Viewer - Visualizador PKG - Search... Procurar... @@ -1426,70 +1391,6 @@ Only one file can be selected! Apenas um ficheiro pode ser selecionado! - - PKG Extraction - Extração de PKG - - - Patch detected! - Patch detetado! - - - PKG and Game versions match: - As versões do PKG e do Jogo coincidem: - - - Would you like to overwrite? - Gostaria de substituir? - - - PKG Version %1 is older than installed version: - A versão do PKG %1 é mais antiga do que a versão instalada: - - - Game is installed: - O jogo está instalado: - - - Would you like to install Patch: - Gostaria de instalar o Patch: - - - DLC Installation - Instalação de DLC - - - Would you like to install DLC: %1? - Deseja instalar o DLC: %1? - - - DLC already installed: - DLC já está instalado: - - - Game already installed - O jogo já está instalado - - - PKG ERROR - ERRO PKG - - - Extracting PKG %1/%2 - A extrair PKG %1/%2 - - - Extraction Finished - Extração Finalizada - - - Game successfully installed at %1 - Jogo instalado com sucesso em %1 - - - File doesn't appear to be a valid PKG file - O ficheiro não aparenta ser um ficheiro PKG válido - Run Game Executar Jogo @@ -1498,14 +1399,6 @@ Eboot.bin file not found Ficheiro eboot.bin não encontrado - - PKG File (*.PKG *.pkg) - Ficheiro PKG (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - Este PKG é um patch ou DLC, por favor instale o respetivo jogo primeiro! - Game is already running! O jogo já está a ser executado! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Abrir Pasta - - - PKG ERROR - ERRO PKG - - - Name - Nome - - - Serial - Número de Série - - - Installed - Instalado - - - Size - Tamanho - - - Category - Categoria - - - Type - Tipo - - - App Ver - App Ver - - - FW - FW - - - Region - Região - - - Flags - Flags - - - Path - Caminho - - - File - Ficheiro - - - Unknown - Desconhecido - - - Package - Pacote - - SettingsDialog diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 6e008ac20..18121a204 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Game @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ Only one file can be selected! Numai un fișier poate fi selectat! - - PKG Extraction - Extracție PKG - - - Patch detected! - Patch detectat! - - - PKG and Game versions match: - Versiunile PKG și ale jocului sunt compatibile: - - - Would you like to overwrite? - Doriți să suprascrieți? - - - PKG Version %1 is older than installed version: - Versiunea PKG %1 este mai veche decât versiunea instalată: - - - Game is installed: - Jocul este instalat: - - - Would you like to install Patch: - Doriți să instalați patch-ul: - - - DLC Installation - Instalare DLC - - - Would you like to install DLC: %1? - Doriți să instalați DLC-ul: %1? - - - DLC already installed: - DLC deja instalat: - - - Game already installed - Jocul deja instalat - - - PKG ERROR - EROARE PKG - - - Extracting PKG %1/%2 - Extracție PKG %1/%2 - - - Extraction Finished - Extracție terminată - - - Game successfully installed at %1 - Jocul a fost instalat cu succes la %1 - - - File doesn't appear to be a valid PKG file - Fișierul nu pare să fie un fișier PKG valid - Run Game Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Deschide Folder - - - PKG ERROR - EROARE PKG - - - Name - Nume - - - Serial - Serie - - - Installed - Installed - - - Size - Dimensiune - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Regiune - - - Flags - Flags - - - Path - Drum - - - File - File - - - Unknown - Necunoscut - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 560c8c110..3944af589 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -882,10 +882,6 @@ Error creating shortcut! Ошибка создания ярлыка! - - Install PKG - Установить PKG - Game Игры @@ -978,25 +974,6 @@ Бинды клавиш - - 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 при установке - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Открыть/Добавить папку Elf - - Install Packages (PKG) - Установить пакеты (PKG) - Boot Game Запустить игру @@ -1234,10 +1207,6 @@ Configure... Настроить... - - Install application from a .pkg file - Установить приложение из файла .pkg - Recent Games Недавние игры @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. Не найдено ни одной игры. Пожалуйста, сначала добавьте игры в библиотеку. - - PKG Viewer - Просмотр PKG - Search... Поиск... @@ -1426,70 +1391,6 @@ 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 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 Запустить игру @@ -1498,14 +1399,6 @@ 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! Игра уже запущена! @@ -1555,73 +1448,6 @@ Показывать метки под значками - - PKGViewer - - Open Folder - Открыть папку - - - PKG ERROR - ОШИБКА PKG - - - Name - Название - - - Serial - Серийный номер - - - Installed - Установлено - - - Size - Размер - - - Category - Категория - - - Type - Тип - - - App Ver - Версия приложения - - - FW - Прошивка - - - Region - Регион - - - Flags - Флаги - - - Path - Путь - - - File - Файл - - - Unknown - Неизвестно - - - Package - Пакет - - SettingsDialog diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 8a2c34c60..d59fd8c3e 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -882,10 +882,6 @@ Error creating shortcut! Gabim në krijimin e shkurtores! - - Install PKG - Instalo PKG - Game Loja @@ -978,25 +974,6 @@ Caktimet e Tasteve - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Përzgjidh dosjen - - - Select which directory you want to install to. - Përzgjidh në cilën dosje do që të instalosh. - - - Install All Queued to Selected Folder - Instalo të gjitha të radhiturat në dosjen e zgjedhur - - - Delete PKG File on Install - Fshi skedarin PKG pas instalimit - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Hap/Shto Dosje ELF - - Install Packages (PKG) - Instalo Paketat (PKG) - Boot Game Nis Lojën @@ -1234,10 +1207,6 @@ Configure... Konfiguro... - - Install application from a .pkg file - Instalo aplikacionin nga një skedar .pkg - Recent Games Lojërat e fundit @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. Nuk u gjetën lojëra. Shto lojërat në librarinë tënde fillimisht. - - PKG Viewer - Shikuesi i PKG - Search... Kërko... @@ -1426,70 +1391,6 @@ Only one file can be selected! Mund të përzgjidhet vetëm një skedar! - - PKG Extraction - Nxjerrja e PKG-së - - - Patch detected! - U zbulua një arnë! - - - PKG and Game versions match: - PKG-ja dhe versioni i Lojës përputhen: - - - Would you like to overwrite? - Dëshiron të mbishkruash? - - - PKG Version %1 is older than installed version: - Versioni %1 i PKG-së është më i vjetër se versioni i instaluar: - - - Game is installed: - Loja është instaluar: - - - Would you like to install Patch: - Dëshiron të instalosh Arnën: - - - DLC Installation - Instalimi i DLC-ve - - - Would you like to install DLC: %1? - Dëshiron të instalosh DLC-në: %1? - - - DLC already installed: - DLC-ja është instaluar tashmë: - - - Game already installed - Loja është instaluar tashmë - - - PKG ERROR - GABIM PKG - - - Extracting PKG %1/%2 - Po nxirret PKG-ja %1/%2 - - - Extraction Finished - Nxjerrja Përfundoi - - - Game successfully installed at %1 - Loja u instalua me sukses në %1 - - - File doesn't appear to be a valid PKG file - Skedari nuk duket si skedar PKG i vlefshëm - Run Game Ekzekuto lojën @@ -1498,14 +1399,6 @@ Eboot.bin file not found Skedari Eboot.bin nuk u gjet - - PKG File (*.PKG *.pkg) - Skedar PKG (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG-ja është një arnë ose DLC, të lutem instalo lojën fillimisht! - Game is already running! Loja tashmë është duke u ekzekutuar! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Hap Dosjen - - - PKG ERROR - GABIM PKG - - - Name - Emri - - - Serial - Seriku - - - Installed - Instaluar - - - Size - Madhësia - - - Category - Kategoria - - - Type - Lloji - - - App Ver - Versioni i aplikacionit - - - FW - Firmueri - - - Region - Rajoni - - - Flags - Flamurët - - - Path - Shtegu - - - File - Skedari - - - Unknown - E panjohur - - - Package - Paketa - - SettingsDialog diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index 91b544e05..b570c420e 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -882,10 +882,6 @@ Error creating shortcut! Fel vid skapandet av genväg! - - Install PKG - Installera PKG - Game Spel @@ -978,25 +974,6 @@ Tangentbindningar - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Välj katalog - - - Select which directory you want to install to. - Välj vilken katalog som du vill installera till. - - - Install All Queued to Selected Folder - Installera alla köade till markerad mapp - - - Delete PKG File on Install - Ta bort PKG-fil efter installation - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Öppna/Lägg till Elf-mapp - - Install Packages (PKG) - Installera paket (PKG) - Boot Game Starta spel @@ -1234,10 +1207,6 @@ Configure... Konfigurera... - - Install application from a .pkg file - Installera program från en .pkg-fil - Recent Games Senaste spel @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. Inga spel hittades. Lägg till dina spel till biblioteket först. - - PKG Viewer - PKG-visare - Search... Sök... @@ -1426,70 +1391,6 @@ Only one file can be selected! Endast en fil kan väljas! - - PKG Extraction - PKG-extrahering - - - Patch detected! - Patch upptäcktes! - - - PKG and Game versions match: - PKG och spelversioner matchar: - - - Would you like to overwrite? - Vill du skriva över? - - - PKG Version %1 is older than installed version: - PKG-versionen %1 är äldre än installerad version: - - - Game is installed: - Spelet är installerat: - - - Would you like to install Patch: - Vill du installera patch: - - - DLC Installation - DLC-installation - - - Would you like to install DLC: %1? - Vill du installera DLC: %1? - - - DLC already installed: - DLC redan installerat: - - - Game already installed - Spelet redan installerat - - - PKG ERROR - PKG-FEL - - - Extracting PKG %1/%2 - Extraherar PKG %1/%2 - - - Extraction Finished - Extrahering färdig - - - Game successfully installed at %1 - Spelet installerades i %1 - - - File doesn't appear to be a valid PKG file - Filen verkar inte vara en giltig PKG-fil - Run Game Kör spel @@ -1498,14 +1399,6 @@ Eboot.bin file not found Filen eboot.bin hittades inte - - PKG File (*.PKG *.pkg) - PKG-fil (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG är en patch eller DLC. Installera spelet först! - Game is already running! Spelet är redan igång! @@ -1516,110 +1409,43 @@ Play - Play + Spela Pause - Pause + Paus Stop - Stop + Stoppa Restart - Restart + Starta om Full Screen - Full Screen + Helskärm Controllers - Controllers + Kontroller Keyboard - Keyboard + Tangentbord Refresh List - Refresh List + Uppdatera lista Resume - Resume + Återuppta Show Labels Under Icons - Show Labels Under Icons - - - - PKGViewer - - Open Folder - Öppna mapp - - - PKG ERROR - PKG-FEL - - - Name - Namn - - - Serial - Serienummer - - - Installed - Installerat - - - Size - Storlek - - - Category - Kategori - - - Type - Typ - - - App Ver - Appver - - - FW - FW - - - Region - Region - - - Flags - Flaggor - - - Path - Sökväg - - - File - Arkiv - - - Unknown - Okänt - - - Package - Paket + Visa etiketter under ikoner diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 4946874c9..d2d018116 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -882,10 +882,6 @@ Error creating shortcut! Kısayol oluşturulurken hata oluştu! - - Install PKG - PKG Yükle - Game Oyun @@ -978,25 +974,6 @@ Tuş Atamaları - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Klasörü Seç - - - Select which directory you want to install to. - Hangi dizine yüklemek istediğinizi seçin. - - - Install All Queued to Selected Folder - Tüm Kuyruktakileri Seçili Klasöre Yükle - - - Delete PKG File on Install - Yüklemede PKG Dosyasını Sil - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Elf Klasörü Aç/Ekle - - Install Packages (PKG) - Paketleri Kur (PKG) - Boot Game Oyunu Başlat @@ -1234,10 +1207,6 @@ Configure... Yapılandır... - - Install application from a .pkg file - .pkg dosyasından uygulama yükle - Recent Games Son Oyunlar @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. Oyun bulunamadı. Oyunlarınızı lütfen önce kütüphanenize ekleyin. - - PKG Viewer - PKG Görüntüleyici - Search... Ara... @@ -1426,70 +1391,6 @@ Only one file can be selected! Sadece bir dosya seçilebilir! - - PKG Extraction - PKG Çıkartma - - - Patch detected! - Yama tespit edildi! - - - PKG and Game versions match: - PKG ve oyun sürümleri uyumlu: - - - Would you like to overwrite? - Üzerine yazmak ister misiniz? - - - PKG Version %1 is older than installed version: - PKG Sürümü %1, kurulu sürümden daha eski: - - - Game is installed: - Oyun yüklendi: - - - Would you like to install Patch: - Yamanın yüklenmesini ister misiniz: - - - DLC Installation - DLC Yükleme - - - Would you like to install DLC: %1? - DLC'yi yüklemek ister misiniz: %1? - - - DLC already installed: - DLC zaten yüklü: - - - Game already installed - Oyun zaten yüklü - - - PKG ERROR - PKG HATASI - - - Extracting PKG %1/%2 - PKG Çıkarılıyor %1/%2 - - - Extraction Finished - Çıkarma Tamamlandı - - - Game successfully installed at %1 - Oyun başarıyla %1 konumuna yüklendi - - - File doesn't appear to be a valid PKG file - Dosya geçerli bir PKG dosyası gibi görünmüyor - Run Game Oyunu Çalıştır @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin dosyası bulunamadı - - PKG File (*.PKG *.pkg) - PKG Dosyası (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG bir yama ya da DLC, lütfen önce oyunu yükleyin! - Game is already running! Oyun zaten çalışıyor! @@ -1555,73 +1448,6 @@ Simgelerin Altında Etiketleri Göster - - PKGViewer - - Open Folder - Klasörü Aç - - - PKG ERROR - PKG HATASI - - - Name - Ad - - - Serial - Seri Numarası - - - Installed - Yüklü - - - Size - Boyut - - - Category - Kategori - - - Type - Tür - - - App Ver - Uygulama Sürümü - - - FW - Sistem Yazılımı - - - Region - Bölge - - - Flags - Bayraklar - - - Path - Yol - - - File - Dosya - - - Unknown - Bilinmeyen - - - Package - Paket - - SettingsDialog diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 69e6c5fc7..890fa163e 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -882,10 +882,6 @@ Error creating shortcut! Помилка при створенні ярлика! - - Install PKG - Встановити PKG - Game гри @@ -978,25 +974,6 @@ Призначення клавіш - - 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 під час встановлення - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Відкрити/Додати папку Elf - - Install Packages (PKG) - Встановити пакети (PKG) - Boot Game Запустити гру @@ -1234,10 +1207,6 @@ Configure... Налаштувати... - - Install application from a .pkg file - Встановити додаток з файлу .pkg - Recent Games Нещодавні ігри @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - Перегляд PKG - Search... Пошук... @@ -1426,70 +1391,6 @@ 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 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 Запустити гру @@ -1498,14 +1399,6 @@ Eboot.bin file not found Файл Boot.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! Гра вже запущена! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Відкрити папку - - - PKG ERROR - ПОМИЛКА PKG - - - Name - Назва - - - Serial - Серійний номер - - - Installed - Встановлені - - - Size - Розмір - - - Category - Категорія - - - Type - Тип - - - App Ver - Версія додатку - - - FW - ПЗ - - - Region - Регіон - - - Flags - Мітки - - - Path - Шлях - - - File - Файл - - - Unknown - Невідомо - - - Package - Пакет - - SettingsDialog diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 14bd29896..b8c1759be 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Game @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ Only one file can be selected! Chỉ có thể chọn một tệp duy nhất! - - PKG Extraction - Giải nén PKG - - - Patch detected! - Đã phát hiện bản vá! - - - PKG and Game versions match: - Các phiên bản PKG và trò chơi khớp nhau: - - - Would you like to overwrite? - Bạn có muốn ghi đè không? - - - PKG Version %1 is older than installed version: - Phiên bản PKG %1 cũ hơn phiên bản đã cài đặt: - - - Game is installed: - Trò chơi đã được cài đặt: - - - Would you like to install Patch: - Bạn có muốn cài đặt bản vá: - - - DLC Installation - Cài đặt DLC - - - Would you like to install DLC: %1? - Bạn có muốn cài đặt DLC: %1? - - - DLC already installed: - DLC đã được cài đặt: - - - Game already installed - Trò chơi đã được cài đặt - - - PKG ERROR - LOI PKG - - - Extracting PKG %1/%2 - Đang giải nén PKG %1/%2 - - - Extraction Finished - Giải nén hoàn tất - - - Game successfully installed at %1 - Trò chơi đã được cài đặt thành công tại %1 - - - File doesn't appear to be a valid PKG file - Tệp không có vẻ là tệp PKG hợp lệ - Run Game Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Open Folder - - - PKG ERROR - LOI PKG - - - Name - Tên - - - Serial - Số seri - - - Installed - Installed - - - Size - Kích thước - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - Khu vực - - - Flags - Flags - - - Path - Đường dẫn - - - File - File - - - Unknown - Không xác định - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 7536b7d17..7d414a493 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -882,10 +882,6 @@ Error creating shortcut! 创建快捷方式出错! - - Install PKG - 安装 PKG - Game 游戏 @@ -978,25 +974,6 @@ 按键绑定 - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - 选择文件目录 - - - Select which directory you want to install to. - 选择您想要安装到的目录。 - - - Install All Queued to Selected Folder - 安装所有 PKG 到选定的文件夹 - - - Delete PKG File on Install - 安装后删除 PKG 文件 - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder 打开/添加 Elf 文件夹 - - Install Packages (PKG) - 安装 Packages (PKG) - Boot Game 启动游戏 @@ -1234,10 +1207,6 @@ Configure... 设置... - - Install application from a .pkg file - 从 .pkg 文件安装应用程序 - Recent Games 最近启动的游戏 @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. 未找到游戏。请先将您的游戏添加到您的资料库。 - - PKG Viewer - PKG 查看器 - Search... 搜索... @@ -1426,70 +1391,6 @@ 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 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 运行游戏 @@ -1498,14 +1399,6 @@ 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! 游戏已经在运行中! @@ -1555,73 +1448,6 @@ 显示图标下的标签 - - PKGViewer - - Open Folder - 打开文件夹 - - - PKG ERROR - PKG 错误 - - - Name - 名称 - - - Serial - 序列号 - - - Installed - 已安装 - - - Size - 大小 - - - Category - 分类 - - - Type - 类型 - - - App Ver - 版本 - - - FW - 固件 - - - Region - 区域 - - - Flags - 标志 - - - Path - 路径 - - - File - 文件 - - - Unknown - 未知 - - - Package - Package - - SettingsDialog diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index f195ec1b7..08aa812fb 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -882,10 +882,6 @@ Error creating shortcut! Error creating shortcut! - - Install PKG - Install PKG - Game Game @@ -978,25 +974,6 @@ Keybindings - - InstallDirSelect - - shadPS4 - Choose directory - shadPS4 - Choose directory - - - Select which directory you want to install to. - Select which directory you want to install to. - - - Install All Queued to Selected Folder - Install All Queued to Selected Folder - - - Delete PKG File on Install - Delete PKG File on Install - - KBMSettings @@ -1214,10 +1191,6 @@ Open/Add Elf Folder Open/Add Elf Folder - - Install Packages (PKG) - Install Packages (PKG) - Boot Game Boot Game @@ -1234,10 +1207,6 @@ Configure... Configure... - - Install application from a .pkg file - Install application from a .pkg file - Recent Games Recent Games @@ -1314,10 +1283,6 @@ No games found. Please add your games to your library first. No games found. Please add your games to your library first. - - PKG Viewer - PKG Viewer - Search... Search... @@ -1426,70 +1391,6 @@ 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 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 Run Game @@ -1498,14 +1399,6 @@ Eboot.bin file not found Eboot.bin file not found - - PKG File (*.PKG *.pkg) - PKG File (*.PKG *.pkg) - - - PKG is a patch or DLC, please install the game first! - PKG is a patch or DLC, please install the game first! - Game is already running! Game is already running! @@ -1555,73 +1448,6 @@ Show Labels Under Icons - - PKGViewer - - Open Folder - Open Folder - - - PKG ERROR - PKG 錯誤 - - - Name - 名稱 - - - Serial - 序號 - - - Installed - Installed - - - Size - 大小 - - - Category - Category - - - Type - Type - - - App Ver - App Ver - - - FW - FW - - - Region - 區域 - - - Flags - Flags - - - Path - 路徑 - - - File - File - - - Unknown - 未知 - - - Package - Package - - SettingsDialog From 8122f8ecbfc8d8a26ef616530befd381057ec59e Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sat, 29 Mar 2025 11:30:59 +0100 Subject: [PATCH 086/194] Diplay PR actions as `pr-{number}-{branchname}` (#2713) --- CMakeLists.txt | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd458f04e..804d5528f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,7 @@ git_branch_name(GIT_BRANCH) string(TIMESTAMP BUILD_DATE "%Y-%m-%d %H:%M:%S") message("start git things") + # Try to get the upstream remote and branch message("check for remote and branch") execute_process( @@ -123,6 +124,7 @@ execute_process( ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE ) + # If there's no upstream set or the command failed, check remote.pushDefault if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") message("check default push") @@ -134,15 +136,38 @@ if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") OUTPUT_STRIP_TRAILING_WHITESPACE ) endif() + # If running in GitHub Actions and the above fails if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") message("check github") set(GIT_REMOTE_NAME "origin") - if (DEFINED ENV{GITHUB_HEAD_REF}) # PR branch name - set(GIT_BRANCH "pr-$ENV{GITHUB_HEAD_REF}") - elseif (DEFINED ENV{GITHUB_REF}) # Normal branch name - string(REGEX REPLACE "^refs/[^/]*/" "" GIT_BRANCH "$ENV{GITHUB_REF}") + # Retrieve environment variables + if (DEFINED ENV{GITHUB_HEAD_REF}) + set(GITHUB_HEAD_REF "$ENV{GITHUB_HEAD_REF}") + else() + set(GITHUB_HEAD_REF "") + endif() + + if (DEFINED ENV{GITHUB_REF}) + string(REGEX REPLACE "^refs/[^/]*/" "" GITHUB_BRANCH "$ENV{GITHUB_REF}") + else() + set(GITHUB_BRANCH "") + endif() + + if (DEFINED ENV{GITHUB_EVENT_PATH}) + file(READ "$ENV{GITHUB_EVENT_PATH}" GITHUB_EVENT_JSON) + string(JSON PR_NUMBER GET ${GITHUB_EVENT_JSON} "number") + else() + set(PR_NUMBER "") + endif() + + if (NOT "${PR_NUMBER}" STREQUAL "" AND NOT "${GITHUB_HEAD_REF}" STREQUAL "") + set(GIT_BRANCH "pr-${PR_NUMBER}-${GITHUB_HEAD_REF}") + elseif (NOT "${PR_NUMBER}" STREQUAL "" AND NOT "${GITHUB_BRANCH}" STREQUAL "") + set(GIT_BRANCH "pr-${PR_NUMBER}-${GITHUB_BRANCH}") + elseif (NOT "${PR_NUMBER}" STREQUAL "") + set(GIT_BRANCH "pr-${PR_NUMBER}") else() message("couldn't find branch") set(GIT_BRANCH "detached-head") From 064c3161c3e0a4ddc2cbdf70fe4d307ad9d188c1 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Sat, 29 Mar 2025 15:58:10 +0000 Subject: [PATCH 087/194] real fix this time ? (#2717) --- src/qt_gui/gui_context_menus.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index c13388bbc..b5732d0ca 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -140,12 +140,12 @@ public: QString open_update_path; Common::FS::PathToQString(open_update_path, m_games[itemID].path); open_update_path += "-UPDATE"; - if (!std::filesystem::exists(Common::FS::PathFromQString(open_update_path))) { + if (std::filesystem::exists(Common::FS::PathFromQString(open_update_path))) { QDesktopServices::openUrl(QUrl::fromLocalFile(open_update_path)); } else { Common::FS::PathToQString(open_update_path, m_games[itemID].path); open_update_path += "-patch"; - if (!std::filesystem::exists(Common::FS::PathFromQString(open_update_path))) { + if (std::filesystem::exists(Common::FS::PathFromQString(open_update_path))) { QDesktopServices::openUrl(QUrl::fromLocalFile(open_update_path)); } else { QMessageBox::critical(nullptr, tr("Error"), From faae1218fa0b590e4e3f55b7d41780eec8c281f9 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Sat, 29 Mar 2025 22:50:22 +0000 Subject: [PATCH 088/194] Getting rid of the "Separate Update Folder" option (#2719) * real fix this time ? * should be all of it * Update en_US.ts --- src/common/config.cpp | 12 ------------ src/common/config.h | 2 -- src/qt_gui/settings_dialog.cpp | 6 ------ src/qt_gui/settings_dialog.ui | 7 ------- src/qt_gui/translations/en_US.ts | 8 -------- 5 files changed, 35 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index d1bb89897..2657cd12a 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -75,7 +75,6 @@ static double trophyNotificationDuration = 6.0; static bool useUnifiedInputConfig = true; static bool overrideControllerColor = false; static int controllerCustomColorRGB[3] = {0, 0, 255}; -static bool separateupdatefolder = false; static bool compatibilityData = false; static bool checkCompatibilityOnStartup = false; static std::string trophyKey; @@ -352,10 +351,6 @@ void setVkGuestMarkersEnabled(bool enable) { vkGuestMarkers = enable; } -bool getSeparateUpdateEnabled() { - return separateupdatefolder; -} - bool getCompatibilityEnabled() { return compatibilityData; } @@ -517,10 +512,6 @@ void setIsMotionControlsEnabled(bool use) { isMotionControlsEnabled = use; } -void setSeparateUpdateEnabled(bool use) { - separateupdatefolder = use; -} - void setCompatibilityEnabled(bool use) { compatibilityData = use; } @@ -781,7 +772,6 @@ void load(const std::filesystem::path& path) { isAutoUpdate = toml::find_or(general, "autoUpdate", false); isAlwaysShowChangelog = toml::find_or(general, "alwaysShowChangelog", false); isSideTrophy = toml::find_or(general, "sideTrophy", "right"); - separateupdatefolder = toml::find_or(general, "separateUpdateEnabled", false); compatibilityData = toml::find_or(general, "compatibilityEnabled", false); checkCompatibilityOnStartup = toml::find_or(general, "checkCompatibilityOnStartup", false); @@ -977,7 +967,6 @@ void save(const std::filesystem::path& path) { data["General"]["autoUpdate"] = isAutoUpdate; data["General"]["alwaysShowChangelog"] = isAlwaysShowChangelog; data["General"]["sideTrophy"] = isSideTrophy; - data["General"]["separateUpdateEnabled"] = separateupdatefolder; data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; data["Input"]["cursorState"] = cursorState; @@ -1150,7 +1139,6 @@ void setDefaultValues() { emulator_language = "en_US"; m_language = 1; gpuId = -1; - separateupdatefolder = false; compatibilityData = false; checkCompatibilityOnStartup = false; backgroundImageOpacity = 50; diff --git a/src/common/config.h b/src/common/config.h index d040aa337..aba23621c 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -35,7 +35,6 @@ bool getPlayBGM(); int getBGMvolume(); bool getisTrophyPopupDisabled(); bool getEnableDiscordRPC(); -bool getSeparateUpdateEnabled(); bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); int getBackgroundImageOpacity(); @@ -105,7 +104,6 @@ void setNeoMode(bool enable); void setUserName(const std::string& type); void setUpdateChannel(const std::string& type); void setChooseHomeTab(const std::string& type); -void setSeparateUpdateEnabled(bool use); void setGameInstallDirs(const std::vector& dirs_config); void setAllGameInstallDirs(const std::vector& dirs_config); void setSaveDataPath(const std::filesystem::path& path); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 383cad8fa..25c27fef3 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -318,7 +318,6 @@ SettingsDialog::SettingsDialog(std::span physical_devices, // General ui->consoleLanguageGroupBox->installEventFilter(this); ui->emulatorLanguageGroupBox->installEventFilter(this); - ui->separateUpdatesCheckBox->installEventFilter(this); ui->showSplashCheckBox->installEventFilter(this); ui->discordRPCCheckbox->installEventFilter(this); ui->userName->installEventFilter(this); @@ -450,8 +449,6 @@ void SettingsDialog::LoadValuesFromConfig() { QString translatedText_FullscreenMode = screenModeMap.key(QString::fromStdString(Config::getFullscreenMode())); ui->displayModeComboBox->setCurrentText(translatedText_FullscreenMode); - ui->separateUpdatesCheckBox->setChecked( - toml::find_or(data, "General", "separateUpdateEnabled", false)); ui->gameSizeCheckBox->setChecked(toml::find_or(data, "GUI", "loadGameSizeEnabled", true)); ui->showSplashCheckBox->setChecked(toml::find_or(data, "General", "showSplash", false)); QString translatedText_logType = logTypeMap.key(QString::fromStdString(Config::getLogType())); @@ -600,8 +597,6 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("Console Language:\\nSets the language that the PS4 game uses.\\nIt's recommended to set this to a language the game supports, which will vary by region."); } else if (elementName == "emulatorLanguageGroupBox") { text = tr("Emulator Language:\\nSets the language of the emulator's user interface."); - } else if (elementName == "separateUpdatesCheckBox") { - text = tr("Enable Separate Update Folder:\\nEnables installing game updates into a separate folder for easy management.\\nThis can be manually created by adding the extracted update to the game folder with the name \"CUSA00000-UPDATE\" where the CUSA ID matches the game's ID."); } else if (elementName == "showSplashCheckBox") { text = tr("Show Splash Screen:\\nShows the game's splash screen (a special image) while the game is starting."); } else if (elementName == "discordRPCCheckbox") { @@ -760,7 +755,6 @@ void SettingsDialog::UpdateSettings() { Config::setVblankDiv(ui->vblankSpinBox->value()); Config::setDumpShaders(ui->dumpShadersCheckBox->isChecked()); Config::setNullGpu(ui->nullGpuCheckBox->isChecked()); - Config::setSeparateUpdateEnabled(ui->separateUpdatesCheckBox->isChecked()); Config::setLoadGameSizeEnabled(ui->gameSizeCheckBox->isChecked()); Config::setShowSplash(ui->showSplashCheckBox->isChecked()); Config::setDebugDump(ui->debugDump->isChecked()); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 5600a0db7..20e26775d 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -135,13 +135,6 @@ 10 - - - - Enable Separate Update Folder - - - diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 28d31b200..780f089e8 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings Default tab when opening settings @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emulator Language:\nSets the language of the emulator's user interface. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. From bad82e72046b137d6592d7ebfb769d4ec3b1e257 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sat, 29 Mar 2025 19:50:41 -0300 Subject: [PATCH 089/194] Fix Translation on Button in MainWindow (#2720) --- src/qt_gui/main_window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 5d8f8e717..072ad70e5 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -45,11 +45,11 @@ MainWindow::~MainWindow() { bool MainWindow::Init() { auto start = std::chrono::steady_clock::now(); // setup ui + LoadTranslation(); AddUiWidgets(); CreateActions(); CreateRecentGameActions(); ConfigureGuiFromSettings(); - LoadTranslation(); CreateDockWindows(); CreateConnects(); SetLastUsedTheme(); From 99b90cbd5c5b8e24b0aa45630a4f8658267e8a6f Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 29 Mar 2025 18:27:33 -0500 Subject: [PATCH 090/194] Minor libkernel changes (#2721) * sceKernelDebugOutText Some homebrew use this for logging, and these logs do show up in console klogs. I wasn't sure where the most suitable place for this function would be, so I made a separate file for these debug functions. * Implement kernel exit Some homebrew I have use this exit when an error occurs. Since actually closing the emulator isn't implemented yet, I've used an unreachable message that logs the status code. I've placed it in process.cpp for now, let me know if I should change that. * Improved implementations for sceKernelDebugRaiseException functions These functions take in two parameters, an error code and some other value that I have no idea what is for. If that second parameter is not zero, they return ORBIS_KERNEL_ERROR_EINVAL before any calls to mdbg_service. These improved implementations add the early error return and a message with the error code to the unreachable. * Add missing exports Homebrew apps like to use these kernel exports of posix functions instead. * Clang --- CMakeLists.txt | 2 ++ src/core/libraries/kernel/debug.cpp | 20 +++++++++++++++++++ src/core/libraries/kernel/debug.h | 14 +++++++++++++ src/core/libraries/kernel/kernel.cpp | 2 ++ src/core/libraries/kernel/process.cpp | 6 ++++++ .../libraries/kernel/threads/exception.cpp | 17 +++++++++++----- .../libraries/kernel/threads/pthread_spec.cpp | 5 +++++ 7 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 src/core/libraries/kernel/debug.cpp create mode 100644 src/core/libraries/kernel/debug.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 804d5528f..73a0e2c4c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,6 +324,8 @@ set(KERNEL_LIB src/core/libraries/kernel/sync/mutex.cpp src/core/libraries/kernel/threads/thread_state.h src/core/libraries/kernel/process.cpp src/core/libraries/kernel/process.h + src/core/libraries/kernel/debug.cpp + src/core/libraries/kernel/debug.h src/core/libraries/kernel/equeue.cpp src/core/libraries/kernel/equeue.h src/core/libraries/kernel/file_system.cpp diff --git a/src/core/libraries/kernel/debug.cpp b/src/core/libraries/kernel/debug.cpp new file mode 100644 index 000000000..0de67b8b5 --- /dev/null +++ b/src/core/libraries/kernel/debug.cpp @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "core/libraries/kernel/file_system.h" +#include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/libs.h" + +namespace Libraries::Kernel { + +void PS4_SYSV_ABI sceKernelDebugOutText(void* unk, char* text) { + sceKernelWrite(1, text, strlen(text)); + return; +} + +void RegisterDebug(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("9JYNqN6jAKI", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugOutText); +} + +} // namespace Libraries::Kernel \ No newline at end of file diff --git a/src/core/libraries/kernel/debug.h b/src/core/libraries/kernel/debug.h new file mode 100644 index 000000000..177046862 --- /dev/null +++ b/src/core/libraries/kernel/debug.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Kernel { + +void RegisterDebug(Core::Loader::SymbolsResolver* sym); + +} // namespace Libraries::Kernel \ No newline at end of file diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index 9227cf45a..33602bfe8 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -12,6 +12,7 @@ #include "common/va_ctx.h" #include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/debug.h" #include "core/libraries/kernel/equeue.h" #include "core/libraries/kernel/file_system.h" #include "core/libraries/kernel/kernel.h" @@ -219,6 +220,7 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) { Libraries::Kernel::RegisterProcess(sym); Libraries::Kernel::RegisterException(sym); Libraries::Kernel::RegisterAio(sym); + Libraries::Kernel::RegisterDebug(sym); LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard); LIB_FUNCTION("PfccT7qURYE", "libkernel", 1, "libkernel", 1, 1, kernel_ioctl); diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index d61ee37ac..02f8a538d 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -127,6 +127,11 @@ int PS4_SYSV_ABI sceKernelGetModuleInfoFromAddr(VAddr addr, int flags, return ORBIS_OK; } +s32 PS4_SYSV_ABI exit(s32 status) { + UNREACHABLE_MSG("Exiting with status code {}", status); + return 0; +} + void RegisterProcess(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("WB66evu8bsU", "libkernel", 1, "libkernel", 1, 1, sceKernelGetCompiledSdkVersion); LIB_FUNCTION("WslcK1FQcGI", "libkernel", 1, "libkernel", 1, 1, sceKernelIsNeoMode); @@ -136,6 +141,7 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("LwG8g3niqwA", "libkernel", 1, "libkernel", 1, 1, sceKernelDlsym); LIB_FUNCTION("RpQJJVKTiFM", "libkernel", 1, "libkernel", 1, 1, sceKernelGetModuleInfoForUnwind); LIB_FUNCTION("f7KBOafysXo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetModuleInfoFromAddr); + LIB_FUNCTION("6Z83sYWFlA8", "libkernel", 1, "libkernel", 1, 1, exit); } } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index 5e2f35d69..e257cbea0 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "core/libraries/kernel/orbis_error.h" #include "core/libraries/kernel/threads/exception.h" #include "core/libraries/kernel/threads/pthread.h" #include "core/libraries/libs.h" @@ -148,13 +149,19 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { return 0; } -int PS4_SYSV_ABI sceKernelDebugRaiseException() { - UNREACHABLE(); +s32 PS4_SYSV_ABI sceKernelDebugRaiseException(s32 error, s64 unk) { + if (unk != 0) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + UNREACHABLE_MSG("error {:#x}", error); return 0; } -int PS4_SYSV_ABI sceKernelDebugRaiseExceptionOnReleaseMode() { - UNREACHABLE(); +s32 PS4_SYSV_ABI sceKernelDebugRaiseExceptionOnReleaseMode(s32 error, s64 unk) { + if (unk != 0) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + UNREACHABLE_MSG("error {:#x}", error); return 0; } @@ -163,7 +170,7 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("WkwEd3N7w0Y", "libkernel_unity", 1, "libkernel", 1, 1, sceKernelInstallExceptionHandler); LIB_FUNCTION("Qhv5ARAoOEc", "libkernel_unity", 1, "libkernel", 1, 1, - sceKernelRemoveExceptionHandler) + sceKernelRemoveExceptionHandler); LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugRaiseException); LIB_FUNCTION("zE-wXIZjLoM", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugRaiseExceptionOnReleaseMode); diff --git a/src/core/libraries/kernel/threads/pthread_spec.cpp b/src/core/libraries/kernel/threads/pthread_spec.cpp index 9e625da32..b36e302d4 100644 --- a/src/core/libraries/kernel/threads/pthread_spec.cpp +++ b/src/core/libraries/kernel/threads/pthread_spec.cpp @@ -147,6 +147,11 @@ void RegisterSpec(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("0-KXaS70xy4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_getspecific); LIB_FUNCTION("WrOLvHU0yQM", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setspecific); + // Posix-Kernel + LIB_FUNCTION("mqULNdimTn0", "libkernel", 1, "libkernel", 1, 1, posix_pthread_key_create); + LIB_FUNCTION("0-KXaS70xy4", "libkernel", 1, "libkernel", 1, 1, posix_pthread_getspecific); + LIB_FUNCTION("WrOLvHU0yQM", "libkernel", 1, "libkernel", 1, 1, posix_pthread_setspecific); + // Orbis LIB_FUNCTION("geDaqgH9lTg", "libkernel", 1, "libkernel", 1, 1, ORBIS(posix_pthread_key_create)); LIB_FUNCTION("PrdHuuDekhY", "libkernel", 1, "libkernel", 1, 1, ORBIS(posix_pthread_key_delete)); From 3f33d218b35fcf7af7ca445c80a1f711eb77e474 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 30 Mar 2025 01:28:02 +0200 Subject: [PATCH 091/194] New Crowdin updates (#2715) * New translations en_us.ts (Italian) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Ukrainian) --- src/qt_gui/translations/it_IT.ts | 20 ++++++++++---------- src/qt_gui/translations/uk_UA.ts | 26 +++++++++++++------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index af321bd92..895908193 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1409,43 +1409,43 @@ Play - Play + Riproduci Pause - Pause + Pausa Stop - Stop + Arresta Restart - Restart + Riavvia Full Screen - Full Screen + Schermo Intero Controllers - Controllers + Controller Keyboard - Keyboard + Tastiera Refresh List - Refresh List + Aggiorna Lista Resume - Resume + Riprendi Show Labels Under Icons - Show Labels Under Icons + Mostra Etichette Sotto Icone diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 890fa163e..5e2572449 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -1277,11 +1277,11 @@ Trophy Viewer - Trophy Viewer + Переглядач трофеїв No games found. Please add your games to your library first. - No games found. Please add your games to your library first. + Не знайдено жодної гри. Будь ласка, спочатку додайте свої ігри до бібліотеки. Search... @@ -1409,43 +1409,43 @@ Play - Play + Грати Pause - Pause + Пауза Stop - Stop + Стоп Restart - Restart + Перезапуск Full Screen - Full Screen + На повний екран Controllers - Controllers + Контролери Keyboard - Keyboard + Клавіатура Refresh List - Refresh List + Оновити список Resume - Resume + Продовжити Show Labels Under Icons - Show Labels Under Icons + Показати найменування під іконками @@ -2067,7 +2067,7 @@ Select Game: - Select Game: + Виберіть гру: Progress From f1e0a096d54ca2db48da67fb5210b3542fc1b858 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sun, 30 Mar 2025 14:37:00 +0200 Subject: [PATCH 092/194] casually testing in production (#2718) --- CMakeLists.txt | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 73a0e2c4c..021167d58 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,22 +143,26 @@ if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") set(GIT_REMOTE_NAME "origin") # Retrieve environment variables - if (DEFINED ENV{GITHUB_HEAD_REF}) + if (DEFINED ENV{GITHUB_HEAD_REF} AND NOT "$ENV{GITHUB_HEAD_REF}" STREQUAL "") + message("github head ref: $ENV{GITHUB_HEAD_REF}") set(GITHUB_HEAD_REF "$ENV{GITHUB_HEAD_REF}") else() set(GITHUB_HEAD_REF "") endif() - if (DEFINED ENV{GITHUB_REF}) + if (DEFINED ENV{GITHUB_REF} AND NOT "$ENV{GITHUB_REF}" STREQUAL "") + message("github ref: $ENV{GITHUB_REF}") string(REGEX REPLACE "^refs/[^/]*/" "" GITHUB_BRANCH "$ENV{GITHUB_REF}") + string(REGEX MATCH "refs/pull/([0-9]+)/merge" MATCHED_REF "$ENV{GITHUB_REF}") + if (MATCHED_REF) + set(PR_NUMBER "${CMAKE_MATCH_1}") + set(GITHUB_BRANCH "") + message("PR number: ${PR_NUMBER}") + else() + set(PR_NUMBER "") + endif() else() set(GITHUB_BRANCH "") - endif() - - if (DEFINED ENV{GITHUB_EVENT_PATH}) - file(READ "$ENV{GITHUB_EVENT_PATH}" GITHUB_EVENT_JSON) - string(JSON PR_NUMBER GET ${GITHUB_EVENT_JSON} "number") - else() set(PR_NUMBER "") endif() @@ -168,6 +172,12 @@ if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") set(GIT_BRANCH "pr-${PR_NUMBER}-${GITHUB_BRANCH}") elseif (NOT "${PR_NUMBER}" STREQUAL "") set(GIT_BRANCH "pr-${PR_NUMBER}") + elseif ("${PR_NUMBER}" STREQUAL "" AND NOT "${GITHUB_HEAD_REF}" STREQUAL "") + set(GIT_BRANCH "${GITHUB_HEAD_REF}") + elseif ("${PR_NUMBER}" STREQUAL "" AND NOT "${GITHUB_BRANCH}" STREQUAL "") + set(GIT_BRANCH "${GITHUB_BRANCH}") + elseif ("${PR_NUMBER}" STREQUAL "" AND NOT "${GITHUB_REF}" STREQUAL "") + set(GIT_BRANCH "${GITHUB_REF}") else() message("couldn't find branch") set(GIT_BRANCH "detached-head") From a707d31a4c23fdd8aefb146ae04d25ecbca246d0 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 30 Mar 2025 15:37:16 +0300 Subject: [PATCH 093/194] New Crowdin updates (#2722) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Spanish) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Korean) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Arabic) --- src/qt_gui/translations/ar_SA.ts | 56 ++++++++++++++------------------ src/qt_gui/translations/da_DK.ts | 8 ----- src/qt_gui/translations/de_DE.ts | 8 ----- src/qt_gui/translations/el_GR.ts | 8 ----- src/qt_gui/translations/es_ES.ts | 8 ----- src/qt_gui/translations/fa_IR.ts | 8 ----- src/qt_gui/translations/fi_FI.ts | 8 ----- src/qt_gui/translations/fr_FR.ts | 8 ----- src/qt_gui/translations/hu_HU.ts | 8 ----- src/qt_gui/translations/id_ID.ts | 8 ----- src/qt_gui/translations/it_IT.ts | 8 ----- src/qt_gui/translations/ja_JP.ts | 8 ----- src/qt_gui/translations/ko_KR.ts | 8 ----- src/qt_gui/translations/lt_LT.ts | 8 ----- src/qt_gui/translations/nb_NO.ts | 8 ----- src/qt_gui/translations/nl_NL.ts | 8 ----- src/qt_gui/translations/pl_PL.ts | 8 ----- src/qt_gui/translations/pt_BR.ts | 8 ----- src/qt_gui/translations/pt_PT.ts | 8 ----- src/qt_gui/translations/ro_RO.ts | 8 ----- src/qt_gui/translations/ru_RU.ts | 8 ----- src/qt_gui/translations/sq_AL.ts | 8 ----- src/qt_gui/translations/sv_SE.ts | 8 ----- src/qt_gui/translations/tr_TR.ts | 8 ----- src/qt_gui/translations/uk_UA.ts | 8 ----- src/qt_gui/translations/vi_VN.ts | 8 ----- src/qt_gui/translations/zh_CN.ts | 8 ----- src/qt_gui/translations/zh_TW.ts | 8 ----- 28 files changed, 24 insertions(+), 248 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index f5503e189..c130a374c 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -1126,7 +1126,7 @@ Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + : Speed Multiplier (def 1.0): @@ -1138,7 +1138,7 @@ This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + هذا الزر يقوم بنسخ تعيينات الأزرار من إعدادات المستخدم العامة لإعدادات المستخدم المحددة حالياً، ولا يمكن استعماله عندما تكون الإعدادات المستخدمة هي الإعدادات العامة. Copy values from Common Config @@ -1146,7 +1146,7 @@ Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + هل تريد استبدال التعيينات الحالية بالتعيينات العامة؟ Unable to Save @@ -1474,10 +1474,6 @@ Emulator المحاكي - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings علامة التبويب الافتراضية عند فتح الإعدادات @@ -1500,7 +1496,7 @@ Trophy Key - Trophy Key + زر الميداليات Trophy @@ -1508,7 +1504,7 @@ Open the custom trophy images/sounds folder - Open the custom trophy images/sounds folder + افتح مجلد تخصيص اصوات/صور الميداليات Logger @@ -1544,7 +1540,7 @@ s - s + س Controller @@ -1588,7 +1584,7 @@ Enable HDR - Enable HDR + تشغيل HDR Paths @@ -1628,23 +1624,23 @@ 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 @@ -1656,7 +1652,7 @@ Always Show Changelog - Always Show Changelog + اظهر سجل التغيرات دائماً Update Channel @@ -1672,23 +1668,23 @@ Title Music - Title Music + موسيقى الشاشة الرئيسية Disable Trophy Notification - Disable Trophy Notification + إغلاق إشعارات الميداليات Background Image - Background Image + صورة الخلفية Show Background Image - Show Background Image + إظهار صورة الخلفية Opacity - Opacity + درجة السواد Play title music @@ -1696,15 +1692,15 @@ Update Compatibility Database On Startup - Update Compatibility Database On Startup + تحديث قاعدة بيانات التوافق عند التشغيل Game Compatibility - Game Compatibility + توافق الألعاب Display Compatibility Data - Display Compatibility Data + إظهار معلومات التوافق Update Compatibility Database @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. لغة المحاكي:\nتحدد لغة واجهة المستخدم الخاصة بالمحاكي. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. إظهار شاشة البداية:\nيعرض شاشة البداية الخاصة باللعبة (صورة خاصة) أثناء بدء التشغيل. @@ -1760,7 +1752,7 @@ Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + مفتاح الميداليات:\nمفتاح يستخدم لفتح تشفير الميداليات. يجب أن يكون من جهاز مكسور الحماية.\nيجي أن يحتوي على أحرف نظام العد السداسي. Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. @@ -1776,7 +1768,7 @@ Background Image:\nControl the opacity of the game background image. - Background Image:\nControl the opacity of the game background image. + صورة الخلفية:\nيتحكم في درجة سواد صورة خلفية اللعبة. Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. @@ -1784,7 +1776,7 @@ 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). - 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 إغلاق إشعارات الميداليات داخل اللعبة. تقدم الميداليات يمكن تتبعه باستخدام عارض الميداليات (قم بالضغط على زر الفأرة الأيمن داخل النافذة الرئيسية). Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 658ac118f..131a989e1 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings Standardfaneblad ved åbning af indstillinger @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emulatorsprog:\nIndstiller sproget i emulatorens brugergrænseflade. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Vis startskærm:\nViser en startskærm (speciel grafik) under opstarten. diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index b6cd3105f..9642cd500 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Separaten Update-Ordner aktivieren - Default tab when opening settings Standardregisterkarte beim Öffnen der Einstellungen @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emulatorsprache:\nLegt die Sprache der Emulator-Benutzeroberfläche fest. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Separaten Update-Ordner aktivieren:\nErmöglicht die Installation von Spielaktualiserungen in einem separaten Ordner zur einfachen Verwaltung. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Startbildschirm anzeigen:\nZeigt beim Start einen speziellen Bildschirm (Splash) des Spiels an. diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index d1cf0d4a6..c91e0c731 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings Προεπιλεγμένη καρτέλα κατά την ανοίγμα των ρυθμίσεων @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Γλώσσα Εξομοιωτή:\nΡυθμίζει τη γλώσσα του γραφικού περιβάλλοντος του εξομοιωτή. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Εμφάνιση Splash Screen:\nΕμφανίζει ειδική γραφική οθόνη κατά την εκκίνηση. diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index bbd49f61d..035aac6a3 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -1474,10 +1474,6 @@ Emulator Emulador - - Enable Separate Update Folder - Habilitar Carpeta Independiente de Actualizaciones - Default tab when opening settings Pestaña predeterminada al abrir la configuración @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Idioma del Emulador:\nConfigura el idioma de la interfaz de usuario del emulador. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Habilitar Carpeta Independiente de Actualizaciónes:\nHabilita el instalar actualizaciones del juego en una carpeta separada para mas facilidad en la gestión.\nPuede crearse manualmente añadiendo la actualización extraída a la carpeta del juego con el nombre "CUSA00000-UPDATE" donde el CUSA ID coincide con el ID del juego. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Mostrar Pantalla de Inicio:\nMuestra la pantalla de inicio del juego (una imagen especial) mientras el juego se está iniciando. diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index f1f2c62ab..552a0ff23 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -1474,10 +1474,6 @@ Emulator شبیه ساز - - Enable Separate Update Folder - فعال‌سازی پوشه جداگانه برای به‌روزرسانی - Default tab when opening settings زبان پیش‌فرض هنگام باز کردن تنظیمات @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. زبان شبیه‌ساز:\nزبان رابط کاربری شبیه‌ساز را انتخاب می‌کند. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - فعال‌سازی پوشه جداگانه برای به‌روزرسانی:\nامکان نصب به‌روزرسانی‌های بازی در یک پوشه جداگانه برای مدیریت راحت‌تر را فراهم می‌کند. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. نمایش صفحه شروع:\nصفحه شروع بازی (تصویری ویژه) را هنگام بارگذاری بازی نمایش می‌دهد. diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index d07f82bd5..44c668560 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -1474,10 +1474,6 @@ Emulator Emulaattori - - Enable Separate Update Folder - Ota Käyttöön Erillinen Päivityshakemisto - Default tab when opening settings Oletusvälilehti avattaessa asetuksia @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emulaattorin Kieli:\nAsettaa emulaattorin käyttöliittymän kielen. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Ota Käyttöön Erillinen Päivityskansio:\nOttaa käyttöön päivitysten asennuksen erilliseen kansioon helpottamaan niiden hallintaa.\nTämä on tehtävissä manuaalisesti lisäämällä puretun päivityksen pelikansioon "CUSA00000-UPDATE" nimellä, missä CUSA ID vastaa pelin ID:tä. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Näytä Aloitusnäyttö:\nNäyttää pelin aloitusnäytön (erityinen kuva) pelin käynnistyessä. diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 89599e32c..13e1be9f5 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -1474,10 +1474,6 @@ Emulator Émulateur - - Enable Separate Update Folder - Dossier séparé pour les mises à jour - Default tab when opening settings Onglet par défaut lors de l'ouverture des paramètres @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Langue de l'émulateur:\nDéfinit la langue de l'interface utilisateur de l'émulateur. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Dossier séparé pour les mises à jour:\nInstalle les mises à jours des jeux dans un dossier séparé pour une gestion plus facile. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Afficher l'écran de démarrage:\nAffiche l'écran de démarrage du jeu (une image spéciale) lors du démarrage du jeu. diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 85b6f2c95..58857d0d7 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -1474,10 +1474,6 @@ Emulator Emulátor - - Enable Separate Update Folder - Külön Frissítési Mappa Engedélyezése - Default tab when opening settings Alapértelmezett fül a beállítások megnyitásakor @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emulátor nyelve:\nBeállítja az emulátor felhasználói felületének nyelvét. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Külön Frissítéi Mappa Engedélyezése:\nEngedélyezi a frissítések külön mappába helyezését, a könnyű kezelésük érdekében. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Indítóképernyő megjelenítése:\nMegjeleníti a játék indítóképernyőjét (különleges képet) a játék elindításakor. diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index 1858da2a7..de19824f7 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings Tab default saat membuka pengaturan @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Bahasa Emulator:\nMenetapkan bahasa antarmuka pengguna emulator. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Tampilkan Layar Pembuka:\nMenampilkan layar pembuka permainan (gambar khusus) saat permainan dimulai. diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 895908193..908013004 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1474,10 +1474,6 @@ Emulator Emulatore - - Enable Separate Update Folder - Abilita Cartella Aggiornamenti Separata - Default tab when opening settings Scheda predefinita all'apertura delle impostazioni @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Lingua dell'Emulatore:\nImposta la lingua dell'interfaccia utente dell'emulatore. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Abilita Cartella Aggiornamenti Separata:\nAbilita l'installazione degli aggiornamenti in una cartella separata per una più facile gestione. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Mostra Schermata di Avvio:\nMostra la schermata di avvio del gioco (un'immagine speciale) mentre il gioco si sta avviando. diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index d4b8fa5b7..146caa515 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -1474,10 +1474,6 @@ Emulator エミュレーター - - Enable Separate Update Folder - アップデートフォルダの分離を有効化 - Default tab when opening settings 設定を開くときのデフォルトタブ @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. エミュレーターの言語:\nエミュレーターのユーザーインターフェースの言語を設定します。 - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nゲームのアップデートを別のフォルダにインストールすることで、管理が容易になります。 - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. スプラッシュスクリーンを表示:\nゲーム起動中にゲームのスプラッシュスクリーン(特別な画像)を表示します。 diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 9d4b58c79..b79959d38 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings 설정 열기 시 기본 탭 @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emulator Language:\nSets the language of the emulator's user interface. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 55a9fac0c..03ff5a003 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings Numatytoji kortelė atidarius nustatymus @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emuliatoriaus kalba:\nNustato emuliatoriaus vartotojo sąsajos kalbą. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Rodyti paleidimo ekraną:\nPaleidimo metu rodo žaidimo paleidimo ekraną (ypatingą vaizdą). diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 4a0835c1c..e937287fd 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Bruk separat oppdateringsmappe - Default tab when opening settings Standardfanen når innstillingene åpnes @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emulatorspråket:\nAngir språket for emulatorens brukergrensesnitt. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Bruk separat oppdateringsmappe:\n Gjør det mulig å installere spilloppdateringer i en egen mappe for enkel administrasjon.\nDette kan gjøres manuelt ved å legge til den utpakkede oppdateringen, til spillmappa med navnet "CUSA00000-UPDATE" der CUSA-ID-en samsvarer med spillets-ID. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Vis velkomstbilde:\nViser spillets velkomstbilde (et spesialbilde) når spillet starter. diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 918da6a67..66872455e 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings Standaardtabblad bij het openen van instellingen @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emulator Taal:\nStelt de taal van de gebruikersinterface van de emulator in. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Opstartscherm weergeven:\nToont het opstartscherm van het spel (een speciale afbeelding) tijdens het starten van het spel. diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 39950d6ec..bd59a1894 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Włącz oddzielny folder aktualizacji - Default tab when opening settings Domyślna zakładka podczas otwierania ustawień @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Język emulatora:\nUstala język interfejsu użytkownika emulatora. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Włącz oddzielny folder aktualizacji:\nUmożliwia instalowanie aktualizacji gier w oddzielnym folderze w celu łatwego zarządzania. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Wyświetl ekran powitalny:\nPodczas uruchamiania gry wyświetla ekran powitalny (specjalny obraz). diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 5d1582b5a..584d6dc19 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -1474,10 +1474,6 @@ Emulator Emulador - - Enable Separate Update Folder - Ativar Pasta de Atualização Separada - Default tab when opening settings Aba padrão ao abrir as configurações @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Idioma do Emulador:\nDefine o idioma da interface do emulador. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Ativar Pasta de Atualização Separada:\nPermite instalar atualizações de jogos em uma pasta separada para fácil gerenciamento.\nIsso pode ser manualmente criado adicionando a atualização extraída à pasta do jogo com o nome "CUSA00000-UPDATE" onde o ID do CUSA corresponde ao ID do jogo. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Mostrar Splash Inicial:\nExibe a tela inicial do jogo (imagem especial) ao iniciar o jogo. diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index a155a6324..70a73afe7 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -1474,10 +1474,6 @@ Emulator Emulador - - Enable Separate Update Folder - Ativar Pasta de Atualizações Separada - Default tab when opening settings Aba padrão ao abrir as definições @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Idioma do Emulador:\nDefine o idioma da interface gráfica do emulador. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Ativar Pasta de Atualização Separada:\nPermite instalar as atualizações dos jogos numa pasta separada para uma fácil gestão.\nIsto pode ser manualmente criado adicionando a atualização extraída à pasta do jogo com o nome "CUSA00000-UPDATE" onde o ID do CUSA corresponde ao ID do jogo. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Mostrar Splash Inicial:\nExibe o ecrã inicial do jogo (uma imagem especial) enquanto o jogo inicia. diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 18121a204..78dd79c53 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings Tab-ul implicit la deschiderea setărilor @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Limba emulatorului:\nSetează limba interfeței utilizatorului a emulatorului. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Afișează ecranul de încărcare:\nAfișează ecranul de încărcare al jocului (o imagine specială) în timp ce jocul pornește. diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 3944af589..0f16efc2c 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1474,10 +1474,6 @@ Emulator Эмулятор - - Enable Separate Update Folder - Отдельная папка обновлений - Default tab when opening settings Вкладка по умолчанию при открытии настроек @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Язык эмулятора:\nУстанавливает язык пользовательского интерфейса эмулятора. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Отдельная папка обновлений:\nПозволяет устанавливать обновления игры в отдельную папку для удобства.\nМожно создать вручную, добавив извлеченное обновление в папку с игрой с именем "CUSA00000-UPDATE", где идентификатор CUSA совпадает с идентификатором игры. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Показывать заставку:\nОтображает заставку игры (специальное изображение) во время запуска. diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index d59fd8c3e..311633a99 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1474,10 +1474,6 @@ Emulator Emulatori - - Enable Separate Update Folder - Aktivizo dosjen e ndarë të përditësimit - Default tab when opening settings Skeda e paracaktuar kur hapen cilësimet @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Gjuha e emulatorit:\nPërcakton gjuhën e ndërfaqes së përdoruesit të emulatorit. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Aktivizo dosjen e ndarë të përditësimit:\nAktivizon instalimin e përditësimeve të lojërave në dosje të veçanta për menaxhim më të lehtë.\nKjo mund të krijohet manualisht duke shtuar përditësimin e shpaketuar në dosjen e lojës me emrin "CUSA00000-UPDATE" ku ID-ja CUSA përputhet me ID-në e lojës. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Shfaq ekranin e ngarkesës:\nShfaq ekranin e ngarkesës së lojës (një pamje e veçantë) gjatë fillimit të lojës. diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index b570c420e..ce0da785c 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Aktivera separat uppdateringsmapp - Default tab when opening settings Standardflik när inställningar öppnas @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emulatorspråk:\nStäller in språket för emulatorns användargränssnitt - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Aktivera separat uppdateringsmapp:\nAktiverar installation av speluppdateringar i en separat mapp för enkel hantering.\nDetta kan skapas manuellt genom att lägga till uppackad uppdatering till spelmappen med namnet "CUSA00000-UPDATE" där CUSA ID matchar spelets id - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Visa startskärm:\nVisar spelets startskärm (en speciell bild) när spelet startas diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index d2d018116..f5f7b65e5 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -1474,10 +1474,6 @@ Emulator Emülatör - - Enable Separate Update Folder - Ayrı Güncelleme Klasörünü Etkinleştir - Default tab when opening settings Ayarlar açıldığında varsayılan sekme @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Emülatör Dili:\nEmülatörün kullanıcı arayüzünün dilini ayarlar. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Açılış Ekranını Göster:\nOyun açılırken (özel bir görüntü) açılış ekranını gösterir. diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 5e2572449..3eb88bcab 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -1474,10 +1474,6 @@ Emulator Емулятор - - Enable Separate Update Folder - Увімкнути окрему папку оновлень - Default tab when opening settings Вкладка за замовчуванням при відкритті налаштувань @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Мова емулятора:\nВстановіть мову користувацького інтерфейсу емулятора. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Окрема папка для оновлень:\nДає змогу встановлювати оновлення гри в окрему папку для зручності. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Показувати заставку:\nВідображає заставку гри (спеціальне зображення) під час запуску гри. diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index b8c1759be..e0bc7e4fe 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -1474,10 +1474,6 @@ Emulator Trình giả lập - - Enable Separate Update Folder - Bật thư mục cập nhật riêng - Default tab when opening settings Tab mặc định khi mở cài đặt @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. Ngôn ngữ của trình giả lập:\nChọn ngôn ngữ của giao diện người dùng của trình giả lập. - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. Hiển thị màn hình khởi động:\nHiển thị màn hình khởi động của trò chơi (một hình ảnh đặc biệt) trong khi trò chơi khởi động. diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 7d414a493..120310810 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1474,10 +1474,6 @@ Emulator 模拟器 - - Enable Separate Update Folder - 启用单独的更新目录 - Default tab when opening settings 打开设置时的默认选项卡 @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. 模拟器语言:\n设置模拟器用户界面的语言。 - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - 启用单独的更新目录:\n启用安装游戏更新到一个单独的目录中以更便于管理。 - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. 显示启动画面:\n在游戏启动时显示游戏的启动画面(特殊图像)。 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 08aa812fb..077455558 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -1474,10 +1474,6 @@ Emulator Emulator - - Enable Separate Update Folder - Enable Separate Update Folder - Default tab when opening settings 打開設置時的默認選項卡 @@ -1742,10 +1738,6 @@ Emulator Language:\nSets the language of the emulator's user interface. 模擬器語言:\n設定模擬器的用戶介面的語言。 - - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management.\nThis can be manually created by adding the extracted update to the game folder with the name "CUSA00000-UPDATE" where the CUSA ID matches the game's ID. - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. 顯示啟動畫面:\n在遊戲啟動時顯示遊戲的啟動畫面(特殊圖片)。 From f85d8df71e770741768bb9c6e60123e2fe3019f7 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 30 Mar 2025 12:02:33 -0700 Subject: [PATCH 094/194] vulkan: Lower list primitive restart warning to debug log. (#2725) --- src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 901096259..c528258fb 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -77,8 +77,8 @@ GraphicsPipeline::GraphicsPipeline( auto prim_restart = key.enable_primitive_restart != 0; if (prim_restart && IsPrimitiveListTopology() && !instance.IsListRestartSupported()) { - LOG_WARNING(Render_Vulkan, - "Primitive restart is enabled for list topology but not supported by driver."); + LOG_DEBUG(Render_Vulkan, + "Primitive restart is enabled for list topology but not supported by driver."); prim_restart = false; } const vk::PipelineInputAssemblyStateCreateInfo input_assembly = { From 374b66ad8eae5c5d8a452f10973a321038aea451 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 30 Mar 2025 16:27:12 -0500 Subject: [PATCH 095/194] Emulate sceKernelInternalMemory mapping (#2726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Emulate sceKernelInternalMemory mapping This fixes the early crash in CUSA07820 (The Last of Us™ Part II). * Fix name --- src/core/linker.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 18ae62f4b..4ccb9d943 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -101,6 +101,17 @@ void Linker::Execute(const std::vector args) { memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2); + // Simulate sceKernelInternalMemory mapping, a mapping usually performed during libkernel init. + // Due to the large size of this mapping, failing to emulate it causes issues in some titles. + // This mapping belongs in the system reserved area, which starts at address 0x880000000. + static constexpr VAddr KernelAllocBase = 0x880000000ULL; + static constexpr s64 InternalMemorySize = 0x1000000; + void* addr_out{reinterpret_cast(KernelAllocBase)}; + + const s32 ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory( + &addr_out, InternalMemorySize, 3, 0, "SceKernelInternalMemory"); + ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping"); + main_thread.Run([this, module, args](std::stop_token) { Common::SetCurrentThreadName("GAME_MainThread"); LoadSharedLibraries(); @@ -372,7 +383,8 @@ void* Linker::AllocateTlsForThread(bool is_primary) { // If sceKernelMapNamedFlexibleMemory is being called from libkernel and addr = 0 // it automatically places mappings in system reserved area instead of managed. - static constexpr VAddr KernelAllocBase = 0x880000000ULL; + // Since the system reserved area already has a mapping in it, this address is slightly higher. + static constexpr VAddr KernelAllocBase = 0x881000000ULL; // The kernel module has a few different paths for TLS allocation. // For SDK < 1.7 it allocates both main and secondary thread blocks using libc mspace/malloc. From b0a12c02e16c02379385f675dd4c6318c0b7fd69 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Mon, 31 Mar 2025 04:55:14 -0500 Subject: [PATCH 096/194] Add S_SETPRIO to EmitFlowControl (#2727) By squidbus' suggestion, I've added a warning log for this case. --- src/shader_recompiler/frontend/translate/scalar_flow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/scalar_flow.cpp b/src/shader_recompiler/frontend/translate/scalar_flow.cpp index ef8bab789..0e02b77a2 100644 --- a/src/shader_recompiler/frontend/translate/scalar_flow.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_flow.cpp @@ -13,6 +13,9 @@ void Translator::EmitFlowControl(u32 pc, const GcnInst& inst) { case Opcode::S_TTRACEDATA: LOG_WARNING(Render_Vulkan, "S_TTRACEDATA instruction!"); return; + case Opcode::S_SETPRIO: + LOG_WARNING(Render_Vulkan, "S_SETPRIO instruction!"); + return; case Opcode::S_GETPC_B64: return S_GETPC_B64(pc, inst); case Opcode::S_WAITCNT: From 7533206d89ceb028221a9e094c6eb9a0cc019cab Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Mon, 31 Mar 2025 17:55:21 +0200 Subject: [PATCH 097/194] usbd: Implement libusb passthrough (#2271) * usbd: Implement libusb passthrough * clang-format * only do kernel activities on non-windows * use variable to represent "fake" windows kernel driver --------- Co-authored-by: georgemoralis --- .gitmodules | 3 + CMakeLists.txt | 3 +- externals/CMakeLists.txt | 5 + externals/libusb | 1 + src/core/libraries/usbd/usbd.cpp | 512 ++++++++++++++++++++----------- src/core/libraries/usbd/usbd.h | 198 ++++++++---- 6 files changed, 475 insertions(+), 247 deletions(-) create mode 160000 externals/libusb diff --git a/.gitmodules b/.gitmodules index 98fba2098..065a4570f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -107,3 +107,6 @@ path = externals/MoltenVK/cereal url = https://github.com/USCiLab/cereal shallow = true +[submodule "externals/libusb"] + path = externals/libusb + url = https://github.com/libusb/libusb-cmake.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 021167d58..74d555adc 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,6 +224,7 @@ find_package(xxHash 0.8.2 MODULE) find_package(ZLIB 1.3 MODULE) find_package(Zydis 5.0.0 CONFIG) find_package(pugixml 1.14 CONFIG) +find_package(usb-1.0 1.0.27 CONFIG) if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR NOT MSVC) find_package(cryptopp 8.9.0 MODULE) @@ -1061,7 +1062,7 @@ endif() create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) -target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers) +target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers usb-1.0) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index d6bdda023..ef75e8f3e 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -201,6 +201,11 @@ if (NOT TARGET pugixml::pugixml) add_subdirectory(pugixml) endif() +# libusb +if (NOT TARGET usb-1.0) + add_subdirectory(libusb) +endif() + # Discord RPC if (ENABLE_DISCORD_RPC) add_subdirectory(discord-rpc) diff --git a/externals/libusb b/externals/libusb new file mode 160000 index 000000000..8f0b4a38f --- /dev/null +++ b/externals/libusb @@ -0,0 +1 @@ +Subproject commit 8f0b4a38fc3eefa2b26a99dff89e1c12bf37afd4 diff --git a/src/core/libraries/usbd/usbd.cpp b/src/core/libraries/usbd/usbd.cpp index fdfa50b23..e5f6151eb 100644 --- a/src/core/libraries/usbd/usbd.cpp +++ b/src/core/libraries/usbd/usbd.cpp @@ -7,311 +7,455 @@ #include "core/libraries/libs.h" #include "usbd.h" +#include +#include + namespace Libraries::Usbd { -int PS4_SYSV_ABI sceUsbdAllocTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 libusb_to_orbis_error(int retVal) { + if (retVal == LIBUSB_ERROR_OTHER) + return 0x802400FF; + if (retVal < 0) { + LOG_ERROR(Lib_Usbd, "libusb returned: {}", libusb_error_name(retVal)); + return 0x80240000 - retVal; + } + + return retVal; } -int PS4_SYSV_ABI sceUsbdAttachKernelDriver() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +libusb_context* g_libusb_context; + +#if defined(_WIN32) +bool s_has_removed_driver = false; +#endif + +s32 PS4_SYSV_ABI sceUsbdInit() { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_init(&g_libusb_context)); } -int PS4_SYSV_ABI sceUsbdBulkTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdExit() { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_exit(g_libusb_context); } -int PS4_SYSV_ABI sceUsbdCancelTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s64 PS4_SYSV_ABI sceUsbdGetDeviceList(SceUsbdDevice*** list) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_get_device_list(g_libusb_context, list)); } -int PS4_SYSV_ABI sceUsbdCheckConnected() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdFreeDeviceList(SceUsbdDevice** list, s32 unref_devices) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_free_device_list(list, unref_devices); } -int PS4_SYSV_ABI sceUsbdClaimInterface() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +SceUsbdDevice* PS4_SYSV_ABI sceUsbdRefDevice(SceUsbdDevice* device) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_ref_device(device); } -int PS4_SYSV_ABI sceUsbdClearHalt() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdUnrefDevice(SceUsbdDevice* device) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_unref_device(device); } -int PS4_SYSV_ABI sceUsbdClose() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetConfiguration(SceUsbdDeviceHandle* dev_handle, s32* config) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_get_configuration(dev_handle, config)); } -int PS4_SYSV_ABI sceUsbdControlTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetDeviceDescriptor(SceUsbdDevice* device, SceUsbdDeviceDescriptor* desc) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_get_device_descriptor(device, desc)); } -int PS4_SYSV_ABI sceUsbdControlTransferGetData() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetActiveConfigDescriptor(SceUsbdDevice* device, + SceUsbdConfigDescriptor** config) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_get_active_config_descriptor(device, config)); } -int PS4_SYSV_ABI sceUsbdControlTransferGetSetup() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptor(SceUsbdDevice* device, u8 config_index, + SceUsbdConfigDescriptor** config) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_get_config_descriptor(device, config_index, config)); } -int PS4_SYSV_ABI sceUsbdDetachKernelDriver() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptorByValue(SceUsbdDevice* device, u8 bConfigurationValue, + SceUsbdConfigDescriptor** config) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error( + libusb_get_config_descriptor_by_value(device, bConfigurationValue, config)); } -int PS4_SYSV_ABI sceUsbdEventHandlerActive() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdFreeConfigDescriptor(SceUsbdConfigDescriptor* config) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_free_config_descriptor(config); } -int PS4_SYSV_ABI sceUsbdEventHandlingOk() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +u8 PS4_SYSV_ABI sceUsbdGetBusNumber(SceUsbdDevice* device) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_get_bus_number(device); } -int PS4_SYSV_ABI sceUsbdExit() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +u8 PS4_SYSV_ABI sceUsbdGetDeviceAddress(SceUsbdDevice* device) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_get_device_address(device); } -int PS4_SYSV_ABI sceUsbdFillBulkTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +SceUsbdSpeed PS4_SYSV_ABI sceUsbdGetDeviceSpeed(SceUsbdDevice* device) { + LOG_DEBUG(Lib_Usbd, "called"); + + return static_cast(libusb_get_device_speed(device)); } -int PS4_SYSV_ABI sceUsbdFillControlSetup() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetMaxPacketSize(SceUsbdDevice* device, u8 endpoint) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_get_max_packet_size(device, endpoint)); } -int PS4_SYSV_ABI sceUsbdFillControlTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetMaxIsoPacketSize(SceUsbdDevice* device, u8 endpoint) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_get_max_iso_packet_size(device, endpoint)); } -int PS4_SYSV_ABI sceUsbdFillInterruptTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdOpen(SceUsbdDevice* device, SceUsbdDeviceHandle** dev_handle) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_open(device, dev_handle)); } -int PS4_SYSV_ABI sceUsbdFillIsoTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdClose(SceUsbdDeviceHandle* dev_handle) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_close(dev_handle); } -int PS4_SYSV_ABI sceUsbdFreeConfigDescriptor() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +SceUsbdDevice* PS4_SYSV_ABI sceUsbdGetDevice(SceUsbdDeviceHandle* dev_handle) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_get_device(dev_handle); } -int PS4_SYSV_ABI sceUsbdFreeDeviceList() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdSetConfiguration(SceUsbdDeviceHandle* dev_handle, s32 config) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_set_configuration(dev_handle, config)); } -int PS4_SYSV_ABI sceUsbdFreeTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdClaimInterface(SceUsbdDeviceHandle* dev_handle, s32 interface_number) { + LOG_DEBUG(Lib_Usbd, "called"); + + if (sceUsbdKernelDriverActive(dev_handle, interface_number)) { + sceUsbdDetachKernelDriver(dev_handle, interface_number); + } + + return libusb_to_orbis_error(libusb_claim_interface(dev_handle, interface_number)); } -int PS4_SYSV_ABI sceUsbdGetActiveConfigDescriptor() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdReleaseInterface(SceUsbdDeviceHandle* dev_handle, s32 interface_number) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_release_interface(dev_handle, interface_number)); } -int PS4_SYSV_ABI sceUsbdGetBusNumber() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +SceUsbdDeviceHandle* PS4_SYSV_ABI sceUsbdOpenDeviceWithVidPid(u16 vendor_id, u16 product_id) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_open_device_with_vid_pid(g_libusb_context, vendor_id, product_id); } -int PS4_SYSV_ABI sceUsbdGetConfigDescriptor() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdSetInterfaceAltSetting(SceUsbdDeviceHandle* dev_handle, + int interface_number, int alternate_setting) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error( + libusb_set_interface_alt_setting(dev_handle, interface_number, alternate_setting)); } -int PS4_SYSV_ABI sceUsbdGetConfigDescriptorByValue() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdClearHalt(SceUsbdDeviceHandle* dev_handle, uint8_t endpoint) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_clear_halt(dev_handle, endpoint)); } -int PS4_SYSV_ABI sceUsbdGetConfiguration() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdResetDevice(SceUsbdDeviceHandle* dev_handle) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_reset_device(dev_handle)); } -int PS4_SYSV_ABI sceUsbdGetDescriptor() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdKernelDriverActive(SceUsbdDeviceHandle* dev_handle, int interface_number) { + LOG_DEBUG(Lib_Usbd, "called"); + +#if defined(_WIN32) + if (!s_has_removed_driver) + return 1; + else + return 0; +#endif + + return libusb_to_orbis_error(libusb_kernel_driver_active(dev_handle, interface_number)); } -int PS4_SYSV_ABI sceUsbdGetDevice() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdDetachKernelDriver(SceUsbdDeviceHandle* dev_handle, int interface_number) { + LOG_DEBUG(Lib_Usbd, "called"); + +#if defined(_WIN32) + s_has_removed_driver = true; + return 0; +#endif + + return libusb_to_orbis_error(libusb_detach_kernel_driver(dev_handle, interface_number)); } -int PS4_SYSV_ABI sceUsbdGetDeviceAddress() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdAttachKernelDriver(SceUsbdDeviceHandle* dev_handle, int interface_number) { + LOG_DEBUG(Lib_Usbd, "called"); + +#if defined(_WIN32) + s_has_removed_driver = false; + return 0; +#endif + + return libusb_to_orbis_error(libusb_attach_kernel_driver(dev_handle, interface_number)); } -int PS4_SYSV_ABI sceUsbdGetDeviceDescriptor() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +u8* PS4_SYSV_ABI sceUsbdControlTransferGetData(SceUsbdTransfer* transfer) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_control_transfer_get_data(transfer); } -int PS4_SYSV_ABI sceUsbdGetDeviceList() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +SceUsbdControlSetup* PS4_SYSV_ABI sceUsbdControlTransferGetSetup(SceUsbdTransfer* transfer) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_control_transfer_get_setup(transfer); } -int PS4_SYSV_ABI sceUsbdGetDeviceSpeed() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdFillControlSetup(u8* buf, u8 bmRequestType, u8 bRequest, u16 wValue, + u16 wIndex, u16 wLength) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_fill_control_setup(buf, bmRequestType, bRequest, wValue, wIndex, wLength); } -int PS4_SYSV_ABI sceUsbdGetIsoPacketBuffer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +SceUsbdTransfer* PS4_SYSV_ABI sceUsbdAllocTransfer(int iso_packets) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_alloc_transfer(iso_packets); } -int PS4_SYSV_ABI sceUsbdGetMaxIsoPacketSize() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdSubmitTransfer(SceUsbdTransfer* transfer) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_submit_transfer(transfer)); } -int PS4_SYSV_ABI sceUsbdGetMaxPacketSize() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdCancelTransfer(SceUsbdTransfer* transfer) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_cancel_transfer(transfer)); } -int PS4_SYSV_ABI sceUsbdGetStringDescriptor() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdFreeTransfer(SceUsbdTransfer* transfer) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_free_transfer(transfer); } -int PS4_SYSV_ABI sceUsbdGetStringDescriptorAscii() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdFillControlTransfer(SceUsbdTransfer* transfer, + SceUsbdDeviceHandle* dev_handle, u8* buffer, + SceUsbdTransferCallback callback, void* user_data, + u32 timeout) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_fill_control_transfer(transfer, dev_handle, buffer, callback, user_data, timeout); } -int PS4_SYSV_ABI sceUsbdHandleEvents() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdFillBulkTransfer(SceUsbdTransfer* transfer, + SceUsbdDeviceHandle* dev_handle, uint8_t endpoint, + u8* buffer, s32 length, SceUsbdTransferCallback callback, + void* user_data, u32 timeout) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length, callback, user_data, + timeout); } -int PS4_SYSV_ABI sceUsbdHandleEventsLocked() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdFillInterruptTransfer(SceUsbdTransfer* transfer, + SceUsbdDeviceHandle* dev_handle, uint8_t endpoint, + u8* buffer, s32 length, + SceUsbdTransferCallback callback, void* user_data, + u32 timeout) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_fill_interrupt_transfer(transfer, dev_handle, endpoint, buffer, length, callback, + user_data, timeout); } -int PS4_SYSV_ABI sceUsbdHandleEventsTimeout() { - LOG_DEBUG(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdFillIsoTransfer(SceUsbdTransfer* transfer, SceUsbdDeviceHandle* dev_handle, + uint8_t endpoint, u8* buffer, s32 length, + s32 num_iso_packets, SceUsbdTransferCallback callback, + void* user_data, u32 timeout) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_fill_iso_transfer(transfer, dev_handle, endpoint, buffer, length, num_iso_packets, + callback, user_data, timeout); } -int PS4_SYSV_ABI sceUsbdInit() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return 0x80240005; // Skip +void PS4_SYSV_ABI sceUsbdSetIsoPacketLengths(SceUsbdTransfer* transfer, u32 length) { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_set_iso_packet_lengths(transfer, length); } -int PS4_SYSV_ABI sceUsbdInterruptTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +u8* PS4_SYSV_ABI sceUsbdGetIsoPacketBuffer(SceUsbdTransfer* transfer, u32 packet) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_get_iso_packet_buffer(transfer, packet); } -int PS4_SYSV_ABI sceUsbdKernelDriverActive() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdControlTransfer(SceUsbdDeviceHandle* dev_handle, u8 request_type, + u8 bRequest, u16 wValue, u16 wIndex, u8* data, s32 wLength, + u32 timeout) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_control_transfer(dev_handle, request_type, bRequest, wValue, + wIndex, data, wLength, timeout)); } -int PS4_SYSV_ABI sceUsbdLockEvents() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdBulkTransfer(SceUsbdDeviceHandle* dev_handle, u8 endpoint, u8* data, + s32 length, s32* actual_length, u32 timeout) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error( + libusb_bulk_transfer(dev_handle, endpoint, data, length, actual_length, timeout)); } -int PS4_SYSV_ABI sceUsbdLockEventWaiters() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdInterruptTransfer(SceUsbdDeviceHandle* dev_handle, u8 endpoint, u8* data, + s32 length, s32* actual_length, u32 timeout) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error( + libusb_interrupt_transfer(dev_handle, endpoint, data, length, actual_length, timeout)); } -int PS4_SYSV_ABI sceUsbdOpen() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetDescriptor(SceUsbdDeviceHandle* dev_handle, u8 descType, u8 descIndex, + u8* data, s32 length) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error( + libusb_get_descriptor(dev_handle, descType, descIndex, data, length)); } -int PS4_SYSV_ABI sceUsbdOpenDeviceWithVidPid() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetStringDescriptor(SceUsbdDeviceHandle* dev_handle, u8 desc_index, + u16 langid, u8* data, s32 length) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error( + libusb_get_string_descriptor(dev_handle, desc_index, langid, data, length)); } -int PS4_SYSV_ABI sceUsbdRefDevice() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdGetStringDescriptorAscii(SceUsbdDeviceHandle* dev_handle, u8 desc_index, + u8* data, s32 length) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error( + libusb_get_string_descriptor_ascii(dev_handle, desc_index, data, length)); } -int PS4_SYSV_ABI sceUsbdReleaseInterface() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdTryLockEvents() { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_try_lock_events(g_libusb_context); } -int PS4_SYSV_ABI sceUsbdResetDevice() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdLockEvents() { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_lock_events(g_libusb_context); } -int PS4_SYSV_ABI sceUsbdSetConfiguration() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdUnlockEvents() { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_unlock_events(g_libusb_context); } -int PS4_SYSV_ABI sceUsbdSetInterfaceAltSetting() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdEventHandlingOk() { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_event_handling_ok(g_libusb_context); } -int PS4_SYSV_ABI sceUsbdSetIsoPacketLengths() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdEventHandlerActive() { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_event_handler_active(g_libusb_context); } -int PS4_SYSV_ABI sceUsbdSubmitTransfer() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdLockEventWaiters() { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_lock_event_waiters(g_libusb_context); } -int PS4_SYSV_ABI sceUsbdTryLockEvents() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceUsbdUnlockEventWaiters() { + LOG_DEBUG(Lib_Usbd, "called"); + + libusb_unlock_event_waiters(g_libusb_context); } -int PS4_SYSV_ABI sceUsbdUnlockEvents() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdWaitForEvent(timeval* tv) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_wait_for_event(g_libusb_context, tv)); } -int PS4_SYSV_ABI sceUsbdUnlockEventWaiters() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdHandleEventsTimeout(timeval* tv) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_handle_events_timeout(g_libusb_context, tv)); } -int PS4_SYSV_ABI sceUsbdUnrefDevice() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdHandleEvents() { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_handle_events(g_libusb_context)); } -int PS4_SYSV_ABI sceUsbdWaitForEvent() { - LOG_ERROR(Lib_Usbd, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceUsbdHandleEventsLocked(timeval* tv) { + LOG_DEBUG(Lib_Usbd, "called"); + + return libusb_to_orbis_error(libusb_handle_events_locked(g_libusb_context, tv)); +} + +s32 PS4_SYSV_ABI sceUsbdCheckConnected(SceUsbdDeviceHandle* dev_handle) { + LOG_DEBUG(Lib_Usbd, "called"); + + // There's no libusb version of this function. + // Simulate by querying data. + + int config; + int r = libusb_get_configuration(dev_handle, &config); + + return libusb_to_orbis_error(r); } int PS4_SYSV_ABI Func_65F6EF33E38FFF50() { @@ -406,4 +550,4 @@ void RegisterlibSceUsbd(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("1WtDBgcgseA", "libSceUsbd", 1, "libSceUsbd", 1, 1, Func_D56B43060720B1E0); }; -} // namespace Libraries::Usbd +} // namespace Libraries::Usbd \ No newline at end of file diff --git a/src/core/libraries/usbd/usbd.h b/src/core/libraries/usbd/usbd.h index 4ed153587..3bd8134a7 100644 --- a/src/core/libraries/usbd/usbd.h +++ b/src/core/libraries/usbd/usbd.h @@ -1,76 +1,150 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX - FileCopyrightText : Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "common/types.h" +extern "C" { +struct libusb_device; +struct libusb_device_handle; +struct libusb_device_descriptor; +struct libusb_config_descriptor; +struct libusb_transfer; +struct libusb_control_setup; +struct timeval; +} + namespace Core::Loader { class SymbolsResolver; } + namespace Libraries::Usbd { -int PS4_SYSV_ABI sceUsbdAllocTransfer(); -int PS4_SYSV_ABI sceUsbdAttachKernelDriver(); -int PS4_SYSV_ABI sceUsbdBulkTransfer(); -int PS4_SYSV_ABI sceUsbdCancelTransfer(); -int PS4_SYSV_ABI sceUsbdCheckConnected(); -int PS4_SYSV_ABI sceUsbdClaimInterface(); -int PS4_SYSV_ABI sceUsbdClearHalt(); -int PS4_SYSV_ABI sceUsbdClose(); -int PS4_SYSV_ABI sceUsbdControlTransfer(); -int PS4_SYSV_ABI sceUsbdControlTransferGetData(); -int PS4_SYSV_ABI sceUsbdControlTransferGetSetup(); -int PS4_SYSV_ABI sceUsbdDetachKernelDriver(); -int PS4_SYSV_ABI sceUsbdEventHandlerActive(); -int PS4_SYSV_ABI sceUsbdEventHandlingOk(); -int PS4_SYSV_ABI sceUsbdExit(); -int PS4_SYSV_ABI sceUsbdFillBulkTransfer(); -int PS4_SYSV_ABI sceUsbdFillControlSetup(); -int PS4_SYSV_ABI sceUsbdFillControlTransfer(); -int PS4_SYSV_ABI sceUsbdFillInterruptTransfer(); -int PS4_SYSV_ABI sceUsbdFillIsoTransfer(); -int PS4_SYSV_ABI sceUsbdFreeConfigDescriptor(); -int PS4_SYSV_ABI sceUsbdFreeDeviceList(); -int PS4_SYSV_ABI sceUsbdFreeTransfer(); -int PS4_SYSV_ABI sceUsbdGetActiveConfigDescriptor(); -int PS4_SYSV_ABI sceUsbdGetBusNumber(); -int PS4_SYSV_ABI sceUsbdGetConfigDescriptor(); -int PS4_SYSV_ABI sceUsbdGetConfigDescriptorByValue(); -int PS4_SYSV_ABI sceUsbdGetConfiguration(); -int PS4_SYSV_ABI sceUsbdGetDescriptor(); -int PS4_SYSV_ABI sceUsbdGetDevice(); -int PS4_SYSV_ABI sceUsbdGetDeviceAddress(); -int PS4_SYSV_ABI sceUsbdGetDeviceDescriptor(); -int PS4_SYSV_ABI sceUsbdGetDeviceList(); -int PS4_SYSV_ABI sceUsbdGetDeviceSpeed(); -int PS4_SYSV_ABI sceUsbdGetIsoPacketBuffer(); -int PS4_SYSV_ABI sceUsbdGetMaxIsoPacketSize(); -int PS4_SYSV_ABI sceUsbdGetMaxPacketSize(); -int PS4_SYSV_ABI sceUsbdGetStringDescriptor(); -int PS4_SYSV_ABI sceUsbdGetStringDescriptorAscii(); -int PS4_SYSV_ABI sceUsbdHandleEvents(); -int PS4_SYSV_ABI sceUsbdHandleEventsLocked(); -int PS4_SYSV_ABI sceUsbdHandleEventsTimeout(); -int PS4_SYSV_ABI sceUsbdInit(); -int PS4_SYSV_ABI sceUsbdInterruptTransfer(); -int PS4_SYSV_ABI sceUsbdKernelDriverActive(); -int PS4_SYSV_ABI sceUsbdLockEvents(); -int PS4_SYSV_ABI sceUsbdLockEventWaiters(); -int PS4_SYSV_ABI sceUsbdOpen(); -int PS4_SYSV_ABI sceUsbdOpenDeviceWithVidPid(); -int PS4_SYSV_ABI sceUsbdRefDevice(); -int PS4_SYSV_ABI sceUsbdReleaseInterface(); -int PS4_SYSV_ABI sceUsbdResetDevice(); -int PS4_SYSV_ABI sceUsbdSetConfiguration(); -int PS4_SYSV_ABI sceUsbdSetInterfaceAltSetting(); -int PS4_SYSV_ABI sceUsbdSetIsoPacketLengths(); -int PS4_SYSV_ABI sceUsbdSubmitTransfer(); -int PS4_SYSV_ABI sceUsbdTryLockEvents(); -int PS4_SYSV_ABI sceUsbdUnlockEvents(); -int PS4_SYSV_ABI sceUsbdUnlockEventWaiters(); -int PS4_SYSV_ABI sceUsbdUnrefDevice(); -int PS4_SYSV_ABI sceUsbdWaitForEvent(); +using SceUsbdDevice = libusb_device; +using SceUsbdDeviceHandle = libusb_device_handle; +using SceUsbdDeviceDescriptor = libusb_device_descriptor; +using SceUsbdConfigDescriptor = libusb_config_descriptor; +using SceUsbdTransfer = libusb_transfer; +using SceUsbdControlSetup = libusb_control_setup; +using SceUsbdTransferCallback = void (*)(SceUsbdTransfer* transfer); + +enum class SceUsbdSpeed : u32 { + UNKNOWN = 0, + LOW = 1, + FULL = 2, + HIGH = 3, + SUPER = 4, + SUPER_PLUS = 5 +}; + +s32 PS4_SYSV_ABI sceUsbdInit(); +void PS4_SYSV_ABI sceUsbdExit(); + +s64 PS4_SYSV_ABI sceUsbdGetDeviceList(SceUsbdDevice*** list); +void PS4_SYSV_ABI sceUsbdFreeDeviceList(SceUsbdDevice** list, s32 unref_devices); + +SceUsbdDevice* PS4_SYSV_ABI sceUsbdRefDevice(SceUsbdDevice* device); +void PS4_SYSV_ABI sceUsbdUnrefDevice(SceUsbdDevice* device); + +s32 PS4_SYSV_ABI sceUsbdGetConfiguration(SceUsbdDeviceHandle* dev_handle, s32* config); +s32 PS4_SYSV_ABI sceUsbdGetDeviceDescriptor(SceUsbdDevice* device, SceUsbdDeviceDescriptor* desc); +s32 PS4_SYSV_ABI sceUsbdGetActiveConfigDescriptor(SceUsbdDevice* device, + SceUsbdConfigDescriptor** config); +s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptor(SceUsbdDevice* device, u8 config_index, + SceUsbdConfigDescriptor** config); +s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptorByValue(SceUsbdDevice* device, u8 bConfigurationValue, + SceUsbdConfigDescriptor** config); +void PS4_SYSV_ABI sceUsbdFreeConfigDescriptor(SceUsbdConfigDescriptor* config); + +u8 PS4_SYSV_ABI sceUsbdGetBusNumber(SceUsbdDevice* device); +u8 PS4_SYSV_ABI sceUsbdGetDeviceAddress(SceUsbdDevice* device); + +SceUsbdSpeed PS4_SYSV_ABI sceUsbdGetDeviceSpeed(SceUsbdDevice* device); +s32 PS4_SYSV_ABI sceUsbdGetMaxPacketSize(SceUsbdDevice* device, u8 endpoint); +s32 PS4_SYSV_ABI sceUsbdGetMaxIsoPacketSize(SceUsbdDevice* device, u8 endpoint); + +s32 PS4_SYSV_ABI sceUsbdOpen(SceUsbdDevice* device, SceUsbdDeviceHandle** dev_handle); +void PS4_SYSV_ABI sceUsbdClose(SceUsbdDeviceHandle* dev_handle); +SceUsbdDevice* PS4_SYSV_ABI sceUsbdGetDevice(SceUsbdDeviceHandle* dev_handle); + +s32 PS4_SYSV_ABI sceUsbdSetConfiguration(SceUsbdDeviceHandle* dev_handle, s32 config); +s32 PS4_SYSV_ABI sceUsbdClaimInterface(SceUsbdDeviceHandle* dev_handle, s32 interface_number); +s32 PS4_SYSV_ABI sceUsbdReleaseInterface(SceUsbdDeviceHandle* dev_handle, s32 interface_number); + +SceUsbdDeviceHandle* PS4_SYSV_ABI sceUsbdOpenDeviceWithVidPid(u16 vendor_id, u16 product_id); + +s32 PS4_SYSV_ABI sceUsbdSetInterfaceAltSetting(SceUsbdDeviceHandle* dev_handle, + int interface_number, int alternate_setting); +s32 PS4_SYSV_ABI sceUsbdClearHalt(SceUsbdDeviceHandle* dev_handle, u8 endpoint); +s32 PS4_SYSV_ABI sceUsbdResetDevice(SceUsbdDeviceHandle* dev_handle); + +s32 PS4_SYSV_ABI sceUsbdKernelDriverActive(SceUsbdDeviceHandle* dev_handle, int interface_number); +s32 PS4_SYSV_ABI sceUsbdDetachKernelDriver(SceUsbdDeviceHandle* dev_handle, int interface_number); +s32 PS4_SYSV_ABI sceUsbdAttachKernelDriver(SceUsbdDeviceHandle* dev_handle, int interface_number); + +u8* PS4_SYSV_ABI sceUsbdControlTransferGetData(SceUsbdTransfer* transfer); +SceUsbdControlSetup* PS4_SYSV_ABI sceUsbdControlTransferGetSetup(SceUsbdTransfer* transfer); + +void PS4_SYSV_ABI sceUsbdFillControlSetup(u8* buf, u8 bmRequestType, u8 bRequest, u16 wValue, + u16 wIndex, u16 wLength); + +SceUsbdTransfer* PS4_SYSV_ABI sceUsbdAllocTransfer(int iso_packets); +s32 PS4_SYSV_ABI sceUsbdSubmitTransfer(SceUsbdTransfer* transfer); +s32 PS4_SYSV_ABI sceUsbdCancelTransfer(SceUsbdTransfer* transfer); +void PS4_SYSV_ABI sceUsbdFreeTransfer(SceUsbdTransfer* transfer); + +void PS4_SYSV_ABI sceUsbdFillControlTransfer(SceUsbdTransfer* transfer, + SceUsbdDeviceHandle* dev_handle, u8* buffer, + SceUsbdTransferCallback callback, void* user_data, + u32 timeout); +void PS4_SYSV_ABI sceUsbdFillBulkTransfer(SceUsbdTransfer* transfer, + SceUsbdDeviceHandle* dev_handle, u8 endpoint, u8* buffer, + s32 length, SceUsbdTransferCallback callback, + void* user_data, u32 timeout); +void PS4_SYSV_ABI sceUsbdFillInterruptTransfer(SceUsbdTransfer* transfer, + SceUsbdDeviceHandle* dev_handle, u8 endpoint, + u8* buffer, s32 length, + SceUsbdTransferCallback callback, void* user_data, + u32 timeout); +void PS4_SYSV_ABI sceUsbdFillIsoTransfer(SceUsbdTransfer* transfer, SceUsbdDeviceHandle* dev_handle, + u8 endpoint, u8* buffer, s32 length, s32 num_iso_packets, + SceUsbdTransferCallback callback, void* userData, + u32 timeout); + +void PS4_SYSV_ABI sceUsbdSetIsoPacketLengths(SceUsbdTransfer* transfer, u32 length); +u8* PS4_SYSV_ABI sceUsbdGetIsoPacketBuffer(SceUsbdTransfer* transfer, u32 packet); + +s32 PS4_SYSV_ABI sceUsbdControlTransfer(SceUsbdDeviceHandle* dev_handle, u8 request_type, + u8 bRequest, u16 wValue, u16 wIndex, u8* data, s32 wLength, + u32 timeout); +s32 PS4_SYSV_ABI sceUsbdBulkTransfer(SceUsbdDeviceHandle* dev_handle, u8 endpoint, u8* data, + s32 length, s32* actual_length, u32 timeout); +s32 PS4_SYSV_ABI sceUsbdInterruptTransfer(SceUsbdDeviceHandle* dev_handle, u8 endpoint, u8* data, + s32 length, s32* actual_length, u32 timeout); + +s32 PS4_SYSV_ABI sceUsbdGetDescriptor(SceUsbdDeviceHandle* dev_handle, u8 descType, u8 descIndex, + u8* data, s32 length); +s32 PS4_SYSV_ABI sceUsbdGetStringDescriptor(SceUsbdDeviceHandle* dev_handle, u8 desc_index, + u16 langid, u8* data, s32 length); +s32 PS4_SYSV_ABI sceUsbdGetStringDescriptorAscii(SceUsbdDeviceHandle* dev_handle, u8 desc_index, + u8* data, s32 length); + +s32 PS4_SYSV_ABI sceUsbdTryLockEvents(); +void PS4_SYSV_ABI sceUsbdLockEvents(); +void PS4_SYSV_ABI sceUsbdUnlockEvents(); +s32 PS4_SYSV_ABI sceUsbdEventHandlingOk(); +s32 PS4_SYSV_ABI sceUsbdEventHandlerActive(); +void PS4_SYSV_ABI sceUsbdLockEventWaiters(); +void PS4_SYSV_ABI sceUsbdUnlockEventWaiters(); +s32 PS4_SYSV_ABI sceUsbdWaitForEvent(timeval* tv); + +s32 PS4_SYSV_ABI sceUsbdHandleEventsTimeout(timeval* tv); +s32 PS4_SYSV_ABI sceUsbdHandleEvents(); +s32 PS4_SYSV_ABI sceUsbdHandleEventsLocked(timeval* tv); + +s32 PS4_SYSV_ABI sceUsbdCheckConnected(SceUsbdDeviceHandle* dev_handle); + int PS4_SYSV_ABI Func_65F6EF33E38FFF50(); int PS4_SYSV_ABI Func_97F056BAD90AADE7(); int PS4_SYSV_ABI Func_C55104A33B35B264(); From a5958bf7f0da207e02065a88355b8afae0b5e256 Mon Sep 17 00:00:00 2001 From: Alexandre Bouvier Date: Mon, 31 Mar 2025 19:29:24 +0000 Subject: [PATCH 098/194] cmake: remove leftover from #2707 (#2730) --- CMakeLists.txt | 4 ---- cmake/Findcryptopp.cmake | 15 --------------- 2 files changed, 19 deletions(-) delete mode 100644 cmake/Findcryptopp.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 74d555adc..20b0f70d7 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,10 +226,6 @@ find_package(Zydis 5.0.0 CONFIG) find_package(pugixml 1.14 CONFIG) find_package(usb-1.0 1.0.27 CONFIG) -if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR NOT MSVC) - find_package(cryptopp 8.9.0 MODULE) -endif() - if (APPLE) find_package(date 3.0.1 CONFIG) endif() diff --git a/cmake/Findcryptopp.cmake b/cmake/Findcryptopp.cmake deleted file mode 100644 index d57c0bc54..000000000 --- a/cmake/Findcryptopp.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -find_package(PkgConfig QUIET) -pkg_search_module(CRYPTOPP QUIET IMPORTED_TARGET libcryptopp) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(cryptopp - REQUIRED_VARS CRYPTOPP_LINK_LIBRARIES - VERSION_VAR CRYPTOPP_VERSION -) - -if (cryptopp_FOUND AND NOT TARGET cryptopp::cryptopp) - add_library(cryptopp::cryptopp ALIAS PkgConfig::CRYPTOPP) -endif() From a2a1ecde0a00bbcece1dc51c14db16ae44b00fc6 Mon Sep 17 00:00:00 2001 From: Alexandre Bouvier Date: Mon, 31 Mar 2025 19:29:52 +0000 Subject: [PATCH 099/194] cmake: fix system libusb discovery (#2731) --- CMakeLists.txt | 4 ++-- cmake/Findlibusb.cmake | 15 +++++++++++++++ externals/CMakeLists.txt | 3 ++- 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 cmake/Findlibusb.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 20b0f70d7..d50dc187d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,7 +224,7 @@ find_package(xxHash 0.8.2 MODULE) find_package(ZLIB 1.3 MODULE) find_package(Zydis 5.0.0 CONFIG) find_package(pugixml 1.14 CONFIG) -find_package(usb-1.0 1.0.27 CONFIG) +find_package(libusb 1.0.27 MODULE) if (APPLE) find_package(date 3.0.1 CONFIG) @@ -1058,7 +1058,7 @@ endif() create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) -target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers usb-1.0) +target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers libusb::usb) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/cmake/Findlibusb.cmake b/cmake/Findlibusb.cmake new file mode 100644 index 000000000..c8b35e979 --- /dev/null +++ b/cmake/Findlibusb.cmake @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +find_package(PkgConfig QUIET) +pkg_search_module(LIBUSB QUIET IMPORTED_TARGET libusb-1.0) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(libusb + REQUIRED_VARS LIBUSB_LINK_LIBRARIES + VERSION_VAR LIBUSB_VERSION +) + +if (libusb_FOUND AND NOT TARGET libusb::usb) + add_library(libusb::usb ALIAS PkgConfig::LIBUSB) +endif() diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index ef75e8f3e..b92e13795 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -202,8 +202,9 @@ if (NOT TARGET pugixml::pugixml) endif() # libusb -if (NOT TARGET usb-1.0) +if (NOT TARGET libusb::usb) add_subdirectory(libusb) + add_library(libusb::usb ALIAS usb-1.0) endif() # Discord RPC From 01243fb8e59710ce9a3dd1b58d663d9a28d5db66 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 1 Apr 2025 00:57:47 -0500 Subject: [PATCH 100/194] Fix libSceNgs2 HLE regressions (#2732) * Fix sceNgs2SystemCreateWithAllocator The cause of the exceptions without libraries. * Remove error handling for unimplemented functions Since nothing exists to create any VoiceHandle or RackHandle, we were consistently hitting those error returns. Also promotes logs for stubbed functions back to LOG_ERROR, with the exception of two functions that get spammed pretty frequently. * Use Core::ExecuteGuest to execute allocator alloc and free * Clang * Fix function definitions in ngs2.h These should be using the SYSV ABI * Fix function defs in ngs2_impl.h This will (hopefully) fix compilation * Clang * Clang again --- src/core/libraries/ngs2/ngs2.cpp | 152 ++++++++------------------ src/core/libraries/ngs2/ngs2.h | 18 +-- src/core/libraries/ngs2/ngs2_impl.h | 5 +- src/core/libraries/ngs2/ngs2_report.h | 3 +- 4 files changed, 60 insertions(+), 118 deletions(-) diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp index 0b42e2471..743be5fd6 100644 --- a/src/core/libraries/ngs2/ngs2.cpp +++ b/src/core/libraries/ngs2/ngs2.cpp @@ -18,36 +18,32 @@ namespace Libraries::Ngs2 { s32 PS4_SYSV_ABI sceNgs2CalcWaveformBlock(const OrbisNgs2WaveformFormat* format, u32 samplePos, u32 numSamples, OrbisNgs2WaveformBlock* outBlock) { - LOG_INFO(Lib_Ngs2, "samplePos = {}, numSamples = {}", samplePos, numSamples); + LOG_ERROR(Lib_Ngs2, "samplePos = {}, numSamples = {}", samplePos, numSamples); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2GetWaveformFrameInfo(const OrbisNgs2WaveformFormat* format, u32* outFrameSize, u32* outNumFrameSamples, u32* outUnitsPerFrame, u32* outNumDelaySamples) { - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2ParseWaveformData(const void* data, size_t dataSize, OrbisNgs2WaveformInfo* outInfo) { - LOG_INFO(Lib_Ngs2, "dataSize = {}", dataSize); + LOG_ERROR(Lib_Ngs2, "dataSize = {}", dataSize); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2ParseWaveformFile(const char* path, u64 offset, OrbisNgs2WaveformInfo* outInfo) { - LOG_INFO(Lib_Ngs2, "path = {}, offset = {}", path, offset); + LOG_ERROR(Lib_Ngs2, "path = {}, offset = {}", path, offset); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2ParseWaveformUser(OrbisNgs2ParseReadHandler handler, uintptr_t userData, OrbisNgs2WaveformInfo* outInfo) { - LOG_INFO(Lib_Ngs2, "userData = {}", userData); - if (!handler) { - LOG_ERROR(Lib_Ngs2, "handler is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_HANDLE; - } + LOG_ERROR(Lib_Ngs2, "userData = {}", userData); return ORBIS_OK; } @@ -55,7 +51,7 @@ s32 PS4_SYSV_ABI sceNgs2RackCreate(OrbisNgs2Handle systemHandle, u32 rackId, const OrbisNgs2RackOption* option, const OrbisNgs2ContextBufferInfo* bufferInfo, OrbisNgs2Handle* outHandle) { - LOG_INFO(Lib_Ngs2, "rackId = {}", rackId); + LOG_ERROR(Lib_Ngs2, "rackId = {}", rackId); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; @@ -67,7 +63,7 @@ s32 PS4_SYSV_ABI sceNgs2RackCreateWithAllocator(OrbisNgs2Handle systemHandle, u3 const OrbisNgs2RackOption* option, const OrbisNgs2BufferAllocator* allocator, OrbisNgs2Handle* outHandle) { - LOG_INFO(Lib_Ngs2, "rackId = {}", rackId); + LOG_ERROR(Lib_Ngs2, "rackId = {}", rackId); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; @@ -77,73 +73,45 @@ s32 PS4_SYSV_ABI sceNgs2RackCreateWithAllocator(OrbisNgs2Handle systemHandle, u3 s32 PS4_SYSV_ABI sceNgs2RackDestroy(OrbisNgs2Handle rackHandle, OrbisNgs2ContextBufferInfo* outBufferInfo) { - if (!rackHandle) { - LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; - } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2RackGetInfo(OrbisNgs2Handle rackHandle, OrbisNgs2RackInfo* outInfo, size_t infoSize) { - LOG_INFO(Lib_Ngs2, "infoSize = {}", infoSize); - if (!rackHandle) { - LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; - } + LOG_ERROR(Lib_Ngs2, "infoSize = {}", infoSize); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2RackGetUserData(OrbisNgs2Handle rackHandle, uintptr_t* outUserData) { - if (!rackHandle) { - LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; - } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2RackGetVoiceHandle(OrbisNgs2Handle rackHandle, u32 voiceIndex, OrbisNgs2Handle* outHandle) { - LOG_INFO(Lib_Ngs2, "voiceIndex = {}", voiceIndex); - if (!rackHandle) { - LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; - } + LOG_DEBUG(Lib_Ngs2, "(STUBBED) voiceIndex = {}", voiceIndex); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2RackLock(OrbisNgs2Handle rackHandle) { - if (!rackHandle) { - LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; - } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2RackQueryBufferSize(u32 rackId, const OrbisNgs2RackOption* option, OrbisNgs2ContextBufferInfo* outBufferInfo) { - LOG_INFO(Lib_Ngs2, "rackId = {}", rackId); + LOG_ERROR(Lib_Ngs2, "rackId = {}", rackId); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2RackSetUserData(OrbisNgs2Handle rackHandle, uintptr_t userData) { - LOG_INFO(Lib_Ngs2, "userData = {}", userData); - if (!rackHandle) { - LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; - } + LOG_ERROR(Lib_Ngs2, "userData = {}", userData); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2RackUnlock(OrbisNgs2Handle rackHandle) { - if (!rackHandle) { - LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; - } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } @@ -188,17 +156,17 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator(const OrbisNgs2SystemOption* o OrbisNgs2BufferAllocHandler hostAlloc = allocator->allocHandler; if (outHandle) { OrbisNgs2BufferFreeHandler hostFree = allocator->freeHandler; - OrbisNgs2ContextBufferInfo* bufferInfo = 0; - result = SystemSetup(option, bufferInfo, 0, 0); + OrbisNgs2ContextBufferInfo bufferInfo; + result = SystemSetup(option, &bufferInfo, 0, 0); if (result >= 0) { uintptr_t sysUserData = allocator->userData; - result = hostAlloc(bufferInfo); + result = Core::ExecuteGuest(hostAlloc, &bufferInfo); if (result >= 0) { OrbisNgs2Handle* handleCopy = outHandle; - result = SystemSetup(option, bufferInfo, hostFree, handleCopy); + result = SystemSetup(option, &bufferInfo, hostFree, handleCopy); if (result < 0) { if (hostFree) { - hostFree(bufferInfo); + Core::ExecuteGuest(hostFree, &bufferInfo); } } } @@ -226,13 +194,13 @@ s32 PS4_SYSV_ABI sceNgs2SystemDestroy(OrbisNgs2Handle systemHandle, } s32 PS4_SYSV_ABI sceNgs2SystemEnumHandles(OrbisNgs2Handle* aOutHandle, u32 maxHandles) { - LOG_INFO(Lib_Ngs2, "maxHandles = {}", maxHandles); + LOG_ERROR(Lib_Ngs2, "maxHandles = {}", maxHandles); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2SystemEnumRackHandles(OrbisNgs2Handle systemHandle, OrbisNgs2Handle* aOutHandle, u32 maxHandles) { - LOG_INFO(Lib_Ngs2, "maxHandles = {}", maxHandles); + LOG_ERROR(Lib_Ngs2, "maxHandles = {}", maxHandles); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; @@ -242,11 +210,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemEnumRackHandles(OrbisNgs2Handle systemHandle, s32 PS4_SYSV_ABI sceNgs2SystemGetInfo(OrbisNgs2Handle rackHandle, OrbisNgs2SystemInfo* outInfo, size_t infoSize) { - LOG_INFO(Lib_Ngs2, "infoSize = {}", infoSize); - if (!rackHandle) { - LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; - } + LOG_ERROR(Lib_Ngs2, "infoSize = {}", infoSize); return ORBIS_OK; } @@ -255,7 +219,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemGetUserData(OrbisNgs2Handle systemHandle, uintptr_ LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } @@ -264,7 +228,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemLock(OrbisNgs2Handle systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } @@ -285,7 +249,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemQueryBufferSize(const OrbisNgs2SystemOption* optio s32 PS4_SYSV_ABI sceNgs2SystemRender(OrbisNgs2Handle systemHandle, const OrbisNgs2RenderBufferInfo* aBufferInfo, u32 numBufferInfo) { - LOG_INFO(Lib_Ngs2, "numBufferInfo = {}", numBufferInfo); + LOG_DEBUG(Lib_Ngs2, "(STUBBED) numBufferInfo = {}", numBufferInfo); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; @@ -308,7 +272,7 @@ static s32 PS4_SYSV_ABI sceNgs2SystemResetOption(OrbisNgs2SystemOption* outOptio } s32 PS4_SYSV_ABI sceNgs2SystemSetGrainSamples(OrbisNgs2Handle systemHandle, u32 numSamples) { - LOG_INFO(Lib_Ngs2, "numSamples = {}", numSamples); + LOG_ERROR(Lib_Ngs2, "numSamples = {}", numSamples); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; @@ -317,7 +281,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemSetGrainSamples(OrbisNgs2Handle systemHandle, u32 } s32 PS4_SYSV_ABI sceNgs2SystemSetSampleRate(OrbisNgs2Handle systemHandle, u32 sampleRate) { - LOG_INFO(Lib_Ngs2, "sampleRate = {}", sampleRate); + LOG_ERROR(Lib_Ngs2, "sampleRate = {}", sampleRate); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; @@ -326,7 +290,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemSetSampleRate(OrbisNgs2Handle systemHandle, u32 sa } s32 PS4_SYSV_ABI sceNgs2SystemSetUserData(OrbisNgs2Handle systemHandle, uintptr_t userData) { - LOG_INFO(Lib_Ngs2, "userData = {}", userData); + LOG_ERROR(Lib_Ngs2, "userData = {}", userData); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; @@ -339,66 +303,42 @@ s32 PS4_SYSV_ABI sceNgs2SystemUnlock(OrbisNgs2Handle systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2VoiceControl(OrbisNgs2Handle voiceHandle, const OrbisNgs2VoiceParamHeader* paramList) { - if (!voiceHandle) { - LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; - } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2VoiceGetMatrixInfo(OrbisNgs2Handle voiceHandle, u32 matrixId, OrbisNgs2VoiceMatrixInfo* outInfo, size_t outInfoSize) { - LOG_INFO(Lib_Ngs2, "matrixId = {}, outInfoSize = {}", matrixId, outInfoSize); - if (!voiceHandle) { - LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; - } + LOG_ERROR(Lib_Ngs2, "matrixId = {}, outInfoSize = {}", matrixId, outInfoSize); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2VoiceGetOwner(OrbisNgs2Handle voiceHandle, OrbisNgs2Handle* outRackHandle, u32* outVoiceId) { - if (!voiceHandle) { - LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; - } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2VoiceGetPortInfo(OrbisNgs2Handle voiceHandle, u32 port, OrbisNgs2VoicePortInfo* outInfo, size_t outInfoSize) { - LOG_INFO(Lib_Ngs2, "port = {}, outInfoSize = {}", port, outInfoSize); - if (!voiceHandle) { - LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; - } + LOG_ERROR(Lib_Ngs2, "port = {}, outInfoSize = {}", port, outInfoSize); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2VoiceGetState(OrbisNgs2Handle voiceHandle, OrbisNgs2VoiceState* outState, size_t stateSize) { - LOG_INFO(Lib_Ngs2, "stateSize = {}", stateSize); - if (!voiceHandle) { - LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; - } + LOG_ERROR(Lib_Ngs2, "stateSize = {}", stateSize); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2VoiceGetStateFlags(OrbisNgs2Handle voiceHandle, u32* outStateFlags) { - if (!voiceHandle) { - LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; - } - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } @@ -407,36 +347,32 @@ s32 PS4_SYSV_ABI sceNgs2VoiceGetStateFlags(OrbisNgs2Handle voiceHandle, u32* out s32 PS4_SYSV_ABI sceNgs2CustomRackGetModuleInfo(OrbisNgs2Handle rackHandle, u32 moduleIndex, OrbisNgs2CustomModuleInfo* outInfo, size_t infoSize) { - LOG_INFO(Lib_Ngs2, "moduleIndex = {}, infoSize = {}", moduleIndex, infoSize); - if (!rackHandle) { - LOG_ERROR(Lib_Ngs2, "rackHandle is nullptr"); - return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; - } + LOG_ERROR(Lib_Ngs2, "moduleIndex = {}, infoSize = {}", moduleIndex, infoSize); return ORBIS_OK; } // Ngs2Geom s32 PS4_SYSV_ABI sceNgs2GeomResetListenerParam(OrbisNgs2GeomListenerParam* outListenerParam) { - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2GeomResetSourceParam(OrbisNgs2GeomSourceParam* outSourceParam) { - LOG_INFO(Lib_Ngs2, "called"); + LOG_ERROR(Lib_Ngs2, "called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2GeomCalcListener(const OrbisNgs2GeomListenerParam* param, OrbisNgs2GeomListenerWork* outWork, u32 flags) { - LOG_INFO(Lib_Ngs2, "flags = {}", flags); + LOG_ERROR(Lib_Ngs2, "flags = {}", flags); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2GeomApply(const OrbisNgs2GeomListenerWork* listener, const OrbisNgs2GeomSourceParam* source, OrbisNgs2GeomAttribute* outAttrib, u32 flags) { - LOG_INFO(Lib_Ngs2, "flags = {}", flags); + LOG_ERROR(Lib_Ngs2, "flags = {}", flags); return ORBIS_OK; } @@ -444,15 +380,15 @@ s32 PS4_SYSV_ABI sceNgs2GeomApply(const OrbisNgs2GeomListenerWork* listener, s32 PS4_SYSV_ABI sceNgs2PanInit(OrbisNgs2PanWork* work, const float* aSpeakerAngle, float unitAngle, u32 numSpeakers) { - LOG_INFO(Lib_Ngs2, "aSpeakerAngle = {}, unitAngle = {}, numSpeakers = {}", *aSpeakerAngle, - unitAngle, numSpeakers); + LOG_ERROR(Lib_Ngs2, "aSpeakerAngle = {}, unitAngle = {}, numSpeakers = {}", *aSpeakerAngle, + unitAngle, numSpeakers); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2PanGetVolumeMatrix(OrbisNgs2PanWork* work, const OrbisNgs2PanParam* aParam, u32 numParams, u32 matrixFormat, float* outVolumeMatrix) { - LOG_INFO(Lib_Ngs2, "numParams = {}, matrixFormat = {}", numParams, matrixFormat); + LOG_ERROR(Lib_Ngs2, "numParams = {}, matrixFormat = {}", numParams, matrixFormat); return ORBIS_OK; } diff --git a/src/core/libraries/ngs2/ngs2.h b/src/core/libraries/ngs2/ngs2.h index a34bf21d4..6c499d974 100644 --- a/src/core/libraries/ngs2/ngs2.h +++ b/src/core/libraries/ngs2/ngs2.h @@ -16,7 +16,8 @@ class SymbolsResolver; namespace Libraries::Ngs2 { -typedef s32 (*OrbisNgs2ParseReadHandler)(uintptr_t userData, u32 offset, void* data, size_t size); +using OrbisNgs2ParseReadHandler = s32 PS4_SYSV_ABI (*)(uintptr_t user_data, u32 offset, void* data, + size_t size); enum class OrbisNgs2HandleType : u32 { Invalid = 0, @@ -90,7 +91,7 @@ struct OrbisNgs2UserFxProcessContext { u32 sampleRate; }; -typedef s32 (*OrbisNgs2UserFxProcessHandler)(OrbisNgs2UserFxProcessContext* context); +using OrbisNgs2UserFxProcessHandler = s32 PS4_SYSV_ABI (*)(OrbisNgs2UserFxProcessContext* context); struct OrbisNgs2UserFx2SetupContext { void* common; @@ -102,7 +103,7 @@ struct OrbisNgs2UserFx2SetupContext { u64 reserved[4]; }; -typedef s32 (*OrbisNgs2UserFx2SetupHandler)(OrbisNgs2UserFx2SetupContext* context); +using OrbisNgs2UserFx2SetupHandler = s32 PS4_SYSV_ABI (*)(OrbisNgs2UserFx2SetupContext* context); struct OrbisNgs2UserFx2CleanupContext { void* common; @@ -114,7 +115,8 @@ struct OrbisNgs2UserFx2CleanupContext { u64 reserved[4]; }; -typedef s32 (*OrbisNgs2UserFx2CleanupHandler)(OrbisNgs2UserFx2CleanupContext* context); +using OrbisNgs2UserFx2CleanupHandler = + s32 PS4_SYSV_ABI (*)(OrbisNgs2UserFx2CleanupContext* context); struct OrbisNgs2UserFx2ControlContext { const void* data; @@ -125,7 +127,8 @@ struct OrbisNgs2UserFx2ControlContext { u64 reserved[4]; }; -typedef s32 (*OrbisNgs2UserFx2ControlHandler)(OrbisNgs2UserFx2ControlContext* context); +using OrbisNgs2UserFx2ControlHandler = + s32 PS4_SYSV_ABI (*)(OrbisNgs2UserFx2ControlContext* context); struct OrbisNgs2UserFx2ProcessContext { float** aChannelData; @@ -143,7 +146,8 @@ struct OrbisNgs2UserFx2ProcessContext { u64 reserved2[4]; }; -typedef s32 (*OrbisNgs2UserFx2ProcessHandler)(OrbisNgs2UserFx2ProcessContext* context); +using OrbisNgs2UserFx2ProcessHandler = + s32 PS4_SYSV_ABI (*)(OrbisNgs2UserFx2ProcessContext* context); struct OrbisNgs2BufferAllocator { OrbisNgs2BufferAllocHandler allocHandler; @@ -237,7 +241,7 @@ struct OrbisNgs2VoiceCallbackInfo { } param; }; -typedef void (*OrbisNgs2VoiceCallbackHandler)(const OrbisNgs2VoiceCallbackInfo* info); +using OrbisNgs2VoiceCallbackHandler = void PS4_SYSV_ABI (*)(const OrbisNgs2VoiceCallbackInfo* info); struct OrbisNgs2VoiceCallbackParam { OrbisNgs2VoiceParamHeader header; diff --git a/src/core/libraries/ngs2/ngs2_impl.h b/src/core/libraries/ngs2/ngs2_impl.h index 7be0f89cc..a433e84fd 100644 --- a/src/core/libraries/ngs2/ngs2_impl.h +++ b/src/core/libraries/ngs2/ngs2_impl.h @@ -30,8 +30,9 @@ struct OrbisNgs2SystemOption { u32 aReserved[6]; }; -typedef s32 (*OrbisNgs2BufferAllocHandler)(OrbisNgs2ContextBufferInfo* ioBufferInfo); -typedef s32 (*OrbisNgs2BufferFreeHandler)(OrbisNgs2ContextBufferInfo* ioBufferInfo); +using OrbisNgs2BufferAllocHandler = + s32 PS4_SYSV_ABI (*)(OrbisNgs2ContextBufferInfo* io_buffer_info); +using OrbisNgs2BufferFreeHandler = s32 PS4_SYSV_ABI (*)(OrbisNgs2ContextBufferInfo* io_buffer_info); struct OrbisNgs2SystemInfo { char name[ORBIS_NGS2_SYSTEM_NAME_LENGTH]; // 0 diff --git a/src/core/libraries/ngs2/ngs2_report.h b/src/core/libraries/ngs2/ngs2_report.h index 88f6d1df0..0f46f5081 100644 --- a/src/core/libraries/ngs2/ngs2_report.h +++ b/src/core/libraries/ngs2/ngs2_report.h @@ -18,7 +18,8 @@ struct OrbisNgs2ReportDataHeader { s32 result; }; -typedef void (*OrbisNgs2ReportHandler)(const OrbisNgs2ReportDataHeader* data, uintptr_t userData); +using OrbisNgs2ReportHandler = void PS4_SYSV_ABI (*)(const OrbisNgs2ReportDataHeader* data, + uintptr_t user_data); struct OrbisNgs2ReportMessageData { OrbisNgs2ReportDataHeader header; From 9ee5d066a228b37558a0eb6b0b1aa963a9fd9079 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 1 Apr 2025 13:22:32 +0300 Subject: [PATCH 101/194] hotfix: issue with cmake 4 --- externals/discord-rpc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/discord-rpc b/externals/discord-rpc index 51b09d426..d3b5af882 160000 --- a/externals/discord-rpc +++ b/externals/discord-rpc @@ -1 +1 @@ -Subproject commit 51b09d426a4a1bcfa6ee6d4894e57d669f4a2e65 +Subproject commit d3b5af8827031f3bccbf8c15d5dc1bfdc9467f17 From f6cc245e4019d1e2f3dfc435f27197ee1c2874a3 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 1 Apr 2025 15:36:31 -0500 Subject: [PATCH 102/194] Only log fd warning if there's a file getting closed (#2737) --- src/core/libraries/kernel/file_system.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index 3321559ed..bc34dff98 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -190,16 +190,16 @@ s32 PS4_SYSV_ABI sceKernelOpen(const char* path, s32 flags, /* SceKernelMode*/ u } s32 PS4_SYSV_ABI close(s32 fd) { - if (fd < 3) { - // This is technically possible, but it's usually caused by some stubbed function instead. - LOG_WARNING(Kernel_Fs, "called on an std handle, fd = {}", fd); - } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); if (file == nullptr) { *__Error() = POSIX_EBADF; return -1; } + if (fd < 3) { + // This is technically possible, but it's usually caused by some stubbed function instead. + LOG_WARNING(Kernel_Fs, "called on an std handle, fd = {}", fd); + } if (file->type == Core::FileSys::FileType::Regular) { file->f.Close(); } From eb300d0aa726e340df0feda1dfa688669642bcc4 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 1 Apr 2025 19:41:40 -0500 Subject: [PATCH 103/194] libraries: Proper ulobjmgr stubs (#2740) * Better ulobjmgr stubs Main goal is to stop all the Unknown stub spam in titles using libSceUlt. * Address review comments * Update ulobjmgr.cpp --- CMakeLists.txt | 2 ++ src/core/libraries/libs.cpp | 2 ++ src/core/libraries/ulobjmgr/ulobjmgr.cpp | 45 ++++++++++++++++++++++++ src/core/libraries/ulobjmgr/ulobjmgr.h | 14 ++++++++ 4 files changed, 63 insertions(+) create mode 100644 src/core/libraries/ulobjmgr/ulobjmgr.cpp create mode 100644 src/core/libraries/ulobjmgr/ulobjmgr.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d50dc187d..b3aba5d13 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -578,6 +578,8 @@ set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp src/core/libraries/screenshot/screenshot.h src/core/libraries/move/move.cpp src/core/libraries/move/move.h + src/core/libraries/ulobjmgr/ulobjmgr.cpp + src/core/libraries/ulobjmgr/ulobjmgr.h ) set(DEV_TOOLS src/core/devtools/layer.cpp diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index cd0fe650b..d0f82556e 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -50,6 +50,7 @@ #include "core/libraries/system/sysmodule.h" #include "core/libraries/system/systemservice.h" #include "core/libraries/system/userservice.h" +#include "core/libraries/ulobjmgr/ulobjmgr.h" #include "core/libraries/usbd/usbd.h" #include "core/libraries/videodec/videodec.h" #include "core/libraries/videodec/videodec2.h" @@ -116,6 +117,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Zlib::RegisterlibSceZlib(sym); Libraries::Hmd::RegisterlibSceHmd(sym); Libraries::DiscMap::RegisterlibSceDiscMap(sym); + Libraries::Ulobjmgr::RegisterlibSceUlobjmgr(sym); } } // namespace Libraries diff --git a/src/core/libraries/ulobjmgr/ulobjmgr.cpp b/src/core/libraries/ulobjmgr/ulobjmgr.cpp new file mode 100644 index 000000000..cad6feda9 --- /dev/null +++ b/src/core/libraries/ulobjmgr/ulobjmgr.cpp @@ -0,0 +1,45 @@ +// 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/kernel/posix_error.h" +#include "core/libraries/libs.h" +#include "core/libraries/ulobjmgr/ulobjmgr.h" + +namespace Libraries::Ulobjmgr { + +s32 PS4_SYSV_ABI Func_046DBA8411A2365C(u64 arg0, s32 arg1, u32* arg2) { + if (arg0 == 0 || arg1 == 0 || arg2 == nullptr) { + return POSIX_EINVAL; + } + *arg2 = 0; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_1D9F50D9CFB8054E() { + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_4A67FE7D435B94F7(u32 arg0) { + if (arg0 >= 0x4000) { + return POSIX_EINVAL; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_4B07893BBB77A649(u64 arg0) { + if (arg0 == 0) { + return POSIX_EINVAL; + } + return ORBIS_OK; +} + +void RegisterlibSceUlobjmgr(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("BG26hBGiNlw", "ulobjmgr", 1, "ulobjmgr", 1, 1, Func_046DBA8411A2365C); + LIB_FUNCTION("HZ9Q2c+4BU4", "ulobjmgr", 1, "ulobjmgr", 1, 1, Func_1D9F50D9CFB8054E); + LIB_FUNCTION("Smf+fUNblPc", "ulobjmgr", 1, "ulobjmgr", 1, 1, Func_4A67FE7D435B94F7); + LIB_FUNCTION("SweJO7t3pkk", "ulobjmgr", 1, "ulobjmgr", 1, 1, Func_4B07893BBB77A649); +}; + +} // namespace Libraries::Ulobjmgr diff --git a/src/core/libraries/ulobjmgr/ulobjmgr.h b/src/core/libraries/ulobjmgr/ulobjmgr.h new file mode 100644 index 000000000..9a1440e5c --- /dev/null +++ b/src/core/libraries/ulobjmgr/ulobjmgr.h @@ -0,0 +1,14 @@ +// 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::Ulobjmgr { +void RegisterlibSceUlobjmgr(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Ulobjmgr \ No newline at end of file From afd0251dd2b5db3f663efa0d3630cff0e1b120f3 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 2 Apr 2025 13:36:54 -0700 Subject: [PATCH 104/194] shader_recompiler: Use VK_AMD_shader_trinary_minmax when available. (#2739) * shader_recompiler: Use VK_AMD_shader_trinary_minmax when available. * shader_recompiler: Simplify signed/unsigned trinary instruction variants. --- externals/sirit | 2 +- .../spirv/emit_spirv_floating_point.cpp | 22 +++++++++ .../backend/spirv/emit_spirv_instructions.h | 9 ++++ .../backend/spirv/emit_spirv_integer.cpp | 44 +++++++++++++++++ .../frontend/translate/translate.h | 6 +-- .../frontend/translate/vector_alu.cpp | 41 +++++----------- src/shader_recompiler/ir/ir_emitter.cpp | 48 +++++++++++++++++++ src/shader_recompiler/ir/ir_emitter.h | 12 +++++ src/shader_recompiler/ir/opcodes.inc | 9 ++++ src/shader_recompiler/profile.h | 1 + .../renderer_vulkan/vk_instance.cpp | 1 + src/video_core/renderer_vulkan/vk_instance.h | 6 +++ .../renderer_vulkan/vk_pipeline_cache.cpp | 1 + 13 files changed, 168 insertions(+), 34 deletions(-) diff --git a/externals/sirit b/externals/sirit index 8b9b12c20..427a42c9e 160000 --- a/externals/sirit +++ b/externals/sirit @@ -1 +1 @@ -Subproject commit 8b9b12c2089505ac8b10fa56bf56b3ed49d9d7b0 +Subproject commit 427a42c9ed99b38204d9107bc3dc14e92458acf1 diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp index a63be87e2..8de903ce6 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp @@ -75,6 +75,28 @@ Id EmitFPMin64(EmitContext& ctx, Id a, Id b) { return ctx.OpFMin(ctx.F64[1], a, b); } +Id EmitFPMinTri32(EmitContext& ctx, Id a, Id b, Id c) { + if (ctx.profile.supports_trinary_minmax) { + return ctx.OpFMin3AMD(ctx.F32[1], a, b, c); + } + return ctx.OpFMin(ctx.F32[1], a, ctx.OpFMin(ctx.F32[1], b, c)); +} + +Id EmitFPMaxTri32(EmitContext& ctx, Id a, Id b, Id c) { + if (ctx.profile.supports_trinary_minmax) { + return ctx.OpFMax3AMD(ctx.F32[1], a, b, c); + } + return ctx.OpFMax(ctx.F32[1], a, ctx.OpFMax(ctx.F32[1], b, c)); +} + +Id EmitFPMedTri32(EmitContext& ctx, Id a, Id b, Id c) { + if (ctx.profile.supports_trinary_minmax) { + return ctx.OpFMid3AMD(ctx.F32[1], a, b, c); + } + const Id mmx{ctx.OpFMin(ctx.F32[1], ctx.OpFMax(ctx.F32[1], a, b), c)}; + return ctx.OpFMax(ctx.F32[1], ctx.OpFMin(ctx.F32[1], a, b), mmx); +} + Id EmitFPMul16(EmitContext& ctx, IR::Inst* inst, Id a, Id b) { return Decorate(ctx, inst, ctx.OpFMul(ctx.F16[1], a, b)); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index aaa2bb526..fb37799f5 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -247,6 +247,9 @@ Id EmitFPMax32(EmitContext& ctx, Id a, Id b, bool is_legacy = false); Id EmitFPMax64(EmitContext& ctx, Id a, Id b); Id EmitFPMin32(EmitContext& ctx, Id a, Id b, bool is_legacy = false); Id EmitFPMin64(EmitContext& ctx, Id a, Id b); +Id EmitFPMinTri32(EmitContext& ctx, Id a, Id b, Id c); +Id EmitFPMaxTri32(EmitContext& ctx, Id a, Id b, Id c); +Id EmitFPMedTri32(EmitContext& ctx, Id a, Id b, Id c); Id EmitFPMul16(EmitContext& ctx, IR::Inst* inst, Id a, Id b); Id EmitFPMul32(EmitContext& ctx, IR::Inst* inst, Id a, Id b); Id EmitFPMul64(EmitContext& ctx, IR::Inst* inst, Id a, Id b); @@ -372,6 +375,12 @@ Id EmitSMin32(EmitContext& ctx, Id a, Id b); Id EmitUMin32(EmitContext& ctx, Id a, Id b); Id EmitSMax32(EmitContext& ctx, Id a, Id b); Id EmitUMax32(EmitContext& ctx, Id a, Id b); +Id EmitSMinTri32(EmitContext& ctx, Id a, Id b, Id c); +Id EmitUMinTri32(EmitContext& ctx, Id a, Id b, Id c); +Id EmitSMaxTri32(EmitContext& ctx, Id a, Id b, Id c); +Id EmitUMaxTri32(EmitContext& ctx, Id a, Id b, Id c); +Id EmitSMedTri32(EmitContext& ctx, Id a, Id b, Id c); +Id EmitUMedTri32(EmitContext& ctx, Id a, Id b, Id c); Id EmitSClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max); Id EmitUClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max); Id EmitSLessThan32(EmitContext& ctx, Id lhs, Id rhs); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index 9f8784797..36726b6df 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -256,6 +256,50 @@ Id EmitUMax32(EmitContext& ctx, Id a, Id b) { return ctx.OpUMax(ctx.U32[1], a, b); } +Id EmitSMinTri32(EmitContext& ctx, Id a, Id b, Id c) { + if (ctx.profile.supports_trinary_minmax) { + return ctx.OpSMin3AMD(ctx.U32[1], a, b, c); + } + return ctx.OpSMin(ctx.U32[1], a, ctx.OpSMin(ctx.U32[1], b, c)); +} + +Id EmitUMinTri32(EmitContext& ctx, Id a, Id b, Id c) { + if (ctx.profile.supports_trinary_minmax) { + return ctx.OpUMin3AMD(ctx.U32[1], a, b, c); + } + return ctx.OpUMin(ctx.U32[1], a, ctx.OpUMin(ctx.U32[1], b, c)); +} + +Id EmitSMaxTri32(EmitContext& ctx, Id a, Id b, Id c) { + if (ctx.profile.supports_trinary_minmax) { + return ctx.OpSMax3AMD(ctx.U32[1], a, b, c); + } + return ctx.OpSMax(ctx.U32[1], a, ctx.OpSMax(ctx.U32[1], b, c)); +} + +Id EmitUMaxTri32(EmitContext& ctx, Id a, Id b, Id c) { + if (ctx.profile.supports_trinary_minmax) { + return ctx.OpUMax3AMD(ctx.U32[1], a, b, c); + } + return ctx.OpUMax(ctx.U32[1], a, ctx.OpUMax(ctx.U32[1], b, c)); +} + +Id EmitSMedTri32(EmitContext& ctx, Id a, Id b, Id c) { + if (ctx.profile.supports_trinary_minmax) { + return ctx.OpSMid3AMD(ctx.U32[1], a, b, c); + } + const Id mmx{ctx.OpSMin(ctx.U32[1], ctx.OpSMax(ctx.U32[1], a, b), c)}; + return ctx.OpSMax(ctx.U32[1], ctx.OpSMin(ctx.U32[1], a, b), mmx); +} + +Id EmitUMedTri32(EmitContext& ctx, Id a, Id b, Id c) { + if (ctx.profile.supports_trinary_minmax) { + return ctx.OpUMid3AMD(ctx.U32[1], a, b, c); + } + const Id mmx{ctx.OpUMin(ctx.U32[1], ctx.OpUMax(ctx.U32[1], a, b), c)}; + return ctx.OpUMax(ctx.U32[1], ctx.OpUMin(ctx.U32[1], a, b), mmx); +} + Id EmitSClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max) { Id result{}; if (ctx.profile.has_broken_spirv_clamp) { diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 2fd48a051..6803cda25 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -238,13 +238,11 @@ public: void V_FMA_F32(const GcnInst& inst); void V_FMA_F64(const GcnInst& inst); void V_MIN3_F32(const GcnInst& inst); - void V_MIN3_I32(const GcnInst& inst); - void V_MIN3_U32(const GcnInst& inst); + void V_MIN3_U32(bool is_signed, const GcnInst& inst); void V_MAX3_F32(const GcnInst& inst); void V_MAX3_U32(bool is_signed, const GcnInst& inst); void V_MED3_F32(const GcnInst& inst); - void V_MED3_I32(const GcnInst& inst); - void V_MED3_U32(const GcnInst& inst); + void V_MED3_U32(bool is_signed, const GcnInst& inst); void V_SAD(const GcnInst& inst); void V_SAD_U32(const GcnInst& inst); void V_CVT_PK_U16_U32(const GcnInst& inst); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 56e903052..22020d59f 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -359,9 +359,9 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { case Opcode::V_MIN3_F32: return V_MIN3_F32(inst); case Opcode::V_MIN3_I32: - return V_MIN3_I32(inst); + return V_MIN3_U32(true, inst); case Opcode::V_MIN3_U32: - return V_MIN3_U32(inst); + return V_MIN3_U32(false, inst); case Opcode::V_MAX3_F32: return V_MAX3_F32(inst); case Opcode::V_MAX3_I32: @@ -371,9 +371,9 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { case Opcode::V_MED3_F32: return V_MED3_F32(inst); case Opcode::V_MED3_I32: - return V_MED3_I32(inst); + return V_MED3_U32(true, inst); case Opcode::V_MED3_U32: - return V_MED3_U32(inst); + return V_MED3_U32(false, inst); case Opcode::V_SAD_U32: return V_SAD_U32(inst); case Opcode::V_CVT_PK_U16_U32: @@ -1166,59 +1166,42 @@ void Translator::V_MIN3_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::F32 src1{GetSrc(inst.src[1])}; const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPMin(src0, ir.FPMin(src1, src2))); + SetDst(inst.dst[0], ir.FPMinTri(src0, src1, src2)); } -void Translator::V_MIN3_I32(const GcnInst& inst) { +void Translator::V_MIN3_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.SMin(src0, ir.SMin(src1, src2))); -} - -void Translator::V_MIN3_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.UMin(src0, ir.UMin(src1, src2))); + SetDst(inst.dst[0], ir.IMinTri(src0, src1, src2, is_signed)); } void Translator::V_MAX3_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::F32 src1{GetSrc(inst.src[1])}; const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2))); + SetDst(inst.dst[0], ir.FPMaxTri(src0, src1, src2)); } void Translator::V_MAX3_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.IMax(src0, ir.IMax(src1, src2, is_signed), is_signed)); + SetDst(inst.dst[0], ir.IMaxTri(src0, src1, src2, is_signed)); } void Translator::V_MED3_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::F32 src1{GetSrc(inst.src[1])}; const IR::F32 src2{GetSrc(inst.src[2])}; - const IR::F32 mmx = ir.FPMin(ir.FPMax(src0, src1), src2); - SetDst(inst.dst[0], ir.FPMax(ir.FPMin(src0, src1), mmx)); + SetDst(inst.dst[0], ir.FPMedTri(src0, src1, src2)); } -void Translator::V_MED3_I32(const GcnInst& inst) { +void Translator::V_MED3_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src2{GetSrc(inst.src[2])}; - const IR::U32 mmx = ir.SMin(ir.SMax(src0, src1), src2); - SetDst(inst.dst[0], ir.SMax(ir.SMin(src0, src1), mmx)); -} - -void Translator::V_MED3_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - const IR::U32 mmx = ir.UMin(ir.UMax(src0, src1), src2); - SetDst(inst.dst[0], ir.UMax(ir.UMin(src0, src1), mmx)); + SetDst(inst.dst[0], ir.IMedTri(src0, src1, src2, is_signed)); } void Translator::V_SAD(const GcnInst& inst) { diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 3615e8cbb..a171d32a2 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1336,6 +1336,18 @@ F32F64 IREmitter::FPMin(const F32F64& lhs, const F32F64& rhs, bool is_legacy) { } } +F32F64 IREmitter::FPMinTri(const F32F64& a, const F32F64& b, const F32F64& c) { + return Inst(Opcode::FPMinTri32, a, b, c); +} + +F32F64 IREmitter::FPMaxTri(const F32F64& a, const F32F64& b, const F32F64& c) { + return Inst(Opcode::FPMaxTri32, a, b, c); +} + +F32F64 IREmitter::FPMedTri(const F32F64& a, const F32F64& b, const F32F64& c) { + return Inst(Opcode::FPMedTri32, a, b, c); +} + U32U64 IREmitter::IAdd(const U32U64& a, const U32U64& b) { if (a.Type() != b.Type()) { UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type()); @@ -1567,6 +1579,42 @@ U32 IREmitter::IMax(const U32& a, const U32& b, bool is_signed) { return is_signed ? SMax(a, b) : UMax(a, b); } +U32 IREmitter::SMinTri(const U32& a, const U32& b, const U32& c) { + return Inst(Opcode::SMinTri32, a, b, c); +} + +U32 IREmitter::UMinTri(const U32& a, const U32& b, const U32& c) { + return Inst(Opcode::UMinTri32, a, b, c); +} + +U32 IREmitter::IMinTri(const U32& a, const U32& b, const U32& c, bool is_signed) { + return is_signed ? SMinTri(a, b, c) : UMinTri(a, b, c); +} + +U32 IREmitter::SMaxTri(const U32& a, const U32& b, const U32& c) { + return Inst(Opcode::SMaxTri32, a, b, c); +} + +U32 IREmitter::UMaxTri(const U32& a, const U32& b, const U32& c) { + return Inst(Opcode::UMaxTri32, a, b, c); +} + +U32 IREmitter::IMaxTri(const U32& a, const U32& b, const U32& c, bool is_signed) { + return is_signed ? SMaxTri(a, b, c) : UMaxTri(a, b, c); +} + +U32 IREmitter::SMedTri(const U32& a, const U32& b, const U32& c) { + return Inst(Opcode::SMedTri32, a, b, c); +} + +U32 IREmitter::UMedTri(const U32& a, const U32& b, const U32& c) { + return Inst(Opcode::UMedTri32, a, b, c); +} + +U32 IREmitter::IMedTri(const U32& a, const U32& b, const U32& c, bool is_signed) { + return is_signed ? SMedTri(a, b, c) : UMedTri(a, b, c); +} + U32 IREmitter::SClamp(const U32& value, const U32& min, const U32& max) { return Inst(Opcode::SClamp32, value, min, max); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 7ac75bf70..48cc02725 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -233,6 +233,9 @@ public: [[nodiscard]] U1 FPUnordered(const F32F64& lhs, const F32F64& rhs); [[nodiscard]] F32F64 FPMax(const F32F64& lhs, const F32F64& rhs, bool is_legacy = false); [[nodiscard]] F32F64 FPMin(const F32F64& lhs, const F32F64& rhs, bool is_legacy = false); + [[nodiscard]] F32F64 FPMinTri(const F32F64& a, const F32F64& b, const F32F64& c); + [[nodiscard]] F32F64 FPMaxTri(const F32F64& a, const F32F64& b, const F32F64& c); + [[nodiscard]] F32F64 FPMedTri(const F32F64& a, const F32F64& b, const F32F64& c); [[nodiscard]] U32U64 IAdd(const U32U64& a, const U32U64& b); [[nodiscard]] Value IAddCary(const U32& a, const U32& b); @@ -266,6 +269,15 @@ public: [[nodiscard]] U32 SMax(const U32& a, const U32& b); [[nodiscard]] U32 UMax(const U32& a, const U32& b); [[nodiscard]] U32 IMax(const U32& a, const U32& b, bool is_signed); + [[nodiscard]] U32 SMinTri(const U32& a, const U32& b, const U32& c); + [[nodiscard]] U32 UMinTri(const U32& a, const U32& b, const U32& c); + [[nodiscard]] U32 IMinTri(const U32& a, const U32& b, const U32& c, bool is_signed); + [[nodiscard]] U32 SMaxTri(const U32& a, const U32& b, const U32& c); + [[nodiscard]] U32 UMaxTri(const U32& a, const U32& b, const U32& c); + [[nodiscard]] U32 IMaxTri(const U32& a, const U32& b, const U32& c, bool is_signed); + [[nodiscard]] U32 SMedTri(const U32& a, const U32& b, const U32& c); + [[nodiscard]] U32 UMedTri(const U32& a, const U32& b, const U32& c); + [[nodiscard]] U32 IMedTri(const U32& a, const U32& b, const U32& c, bool is_signed); [[nodiscard]] U32 SClamp(const U32& value, const U32& min, const U32& max); [[nodiscard]] U32 UClamp(const U32& value, const U32& min, const U32& max); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index d5e17631b..93d759b74 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -241,6 +241,9 @@ OPCODE(FPMax32, F32, F32, OPCODE(FPMax64, F64, F64, F64, ) OPCODE(FPMin32, F32, F32, F32, U1, ) OPCODE(FPMin64, F64, F64, F64, ) +OPCODE(FPMinTri32, F32, F32, F32, F32, ) +OPCODE(FPMaxTri32, F32, F32, F32, F32, ) +OPCODE(FPMedTri32, F32, F32, F32, F32, ) OPCODE(FPMul32, F32, F32, F32, ) OPCODE(FPMul64, F64, F64, F64, ) OPCODE(FPDiv32, F32, F32, F32, ) @@ -350,6 +353,12 @@ OPCODE(SMin32, U32, U32, OPCODE(UMin32, U32, U32, U32, ) OPCODE(SMax32, U32, U32, U32, ) OPCODE(UMax32, U32, U32, U32, ) +OPCODE(SMinTri32, U32, U32, U32, U32, ) +OPCODE(UMinTri32, U32, U32, U32, U32, ) +OPCODE(SMaxTri32, U32, U32, U32, U32, ) +OPCODE(UMaxTri32, U32, U32, U32, U32, ) +OPCODE(SMedTri32, U32, U32, U32, U32, ) +OPCODE(UMedTri32, U32, U32, U32, U32, ) OPCODE(SClamp32, U32, U32, U32, U32, ) OPCODE(UClamp32, U32, U32, U32, U32, ) OPCODE(SLessThan32, U1, U32, U32, ) diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 1ceaea664..3b2854d59 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -26,6 +26,7 @@ struct Profile { bool support_legacy_vertex_attributes{}; bool supports_image_load_store_lod{}; bool supports_native_cube_calc{}; + bool supports_trinary_minmax{}; bool supports_robust_buffer_access{}; bool has_broken_spirv_clamp{}; bool lower_left_origin_mode{}; diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index ab8f074cd..1f0125791 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -276,6 +276,7 @@ bool Instance::CreateDevice() { shader_stencil_export = add_extension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); image_load_store_lod = add_extension(VK_AMD_SHADER_IMAGE_LOAD_STORE_LOD_EXTENSION_NAME); amd_gcn_shader = add_extension(VK_AMD_GCN_SHADER_EXTENSION_NAME); + amd_shader_trinary_minmax = add_extension(VK_AMD_SHADER_TRINARY_MINMAX_EXTENSION_NAME); const bool calibrated_timestamps = TRACY_GPU_ENABLED ? add_extension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME) : false; diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 3ccf89276..013003b9b 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -145,6 +145,11 @@ public: return amd_gcn_shader; } + /// Returns true when VK_AMD_shader_trinary_minmax is supported. + bool IsAmdShaderTrinaryMinMaxSupported() const { + return amd_shader_trinary_minmax; + } + /// Returns true when geometry shaders are supported by the device bool IsGeometryStageSupported() const { return features.geometryShader; @@ -333,6 +338,7 @@ private: bool shader_stencil_export{}; bool image_load_store_lod{}; bool amd_gcn_shader{}; + bool amd_shader_trinary_minmax{}; bool portability_subset{}; }; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 4823b8ffe..d51c8fbd5 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -201,6 +201,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, .support_legacy_vertex_attributes = instance_.IsLegacyVertexAttributesSupported(), .supports_image_load_store_lod = instance_.IsImageLoadStoreLodSupported(), .supports_native_cube_calc = instance_.IsAmdGcnShaderSupported(), + .supports_trinary_minmax = instance_.IsAmdShaderTrinaryMinMaxSupported(), .supports_robust_buffer_access = instance_.IsRobustBufferAccess2Supported(), .needs_manual_interpolation = instance.IsFragmentShaderBarycentricSupported() && instance.GetDriverID() == vk::DriverId::eNvidiaProprietary, From 806b2ddc897fcc6f064ce03910de2a771c5f6400 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:39:08 -0700 Subject: [PATCH 105/194] cpu_patches: Remove CPU patches for macOS and bump minimum OS version to 15.4 (#2743) * cpu_patches: Remove CPU patches for macOS and bump minimum OS version to 15.4 * cpu_patches: Remove BMI1 patches These are now only good for very old Intel CPUs that: * Still do not currently function due to other CPU instruction issues. * Will probably be too slow to run shadPS4 well. --- CMakeLists.txt | 3 +- src/core/cpu_patches.cpp | 578 +-------------------------------------- src/core/cpu_patches.h | 6 - src/core/tls.cpp | 8 +- 4 files changed, 8 insertions(+), 587 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b3aba5d13..228a664eb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) if(APPLE) list(APPEND ADDITIONAL_LANGUAGES OBJC) - set(CMAKE_OSX_DEPLOYMENT_TARGET 14) + # Starting with 15.4, Rosetta 2 has support for all the necessary instruction sets. + set(CMAKE_OSX_DEPLOYMENT_TARGET 15.4) endif() if (NOT CMAKE_BUILD_TYPE) diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index a9f6c67a8..c8106b270 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -22,10 +22,6 @@ #include #else #include -#ifdef __APPLE__ -#include -#include -#endif #endif using namespace Xbyak::util; @@ -81,538 +77,6 @@ static Xbyak::Address ZydisToXbyakMemoryOperand(const ZydisDecodedOperand& opera return ptr[expression]; } -static u64 ZydisToXbyakImmediateOperand(const ZydisDecodedOperand& operand) { - ASSERT_MSG(operand.type == ZYDIS_OPERAND_TYPE_IMMEDIATE, - "Expected immediate operand, got type: {}", static_cast(operand.type)); - return operand.imm.value.u; -} - -static std::unique_ptr ZydisToXbyakOperand(const ZydisDecodedOperand& operand) { - switch (operand.type) { - case ZYDIS_OPERAND_TYPE_REGISTER: { - return std::make_unique(ZydisToXbyakRegisterOperand(operand)); - } - case ZYDIS_OPERAND_TYPE_MEMORY: { - return std::make_unique(ZydisToXbyakMemoryOperand(operand)); - } - default: - UNREACHABLE_MSG("Unsupported operand type: {}", static_cast(operand.type)); - } -} - -static bool OperandUsesRegister(const Xbyak::Operand* operand, int index) { - if (operand->isREG()) { - return operand->getIdx() == index; - } - if (operand->isMEM()) { - const Xbyak::RegExp& reg_exp = operand->getAddress().getRegExp(); - return reg_exp.getBase().getIdx() == index || reg_exp.getIndex().getIdx() == index; - } - UNREACHABLE_MSG("Unsupported operand kind: {}", static_cast(operand->getKind())); -} - -static bool IsRegisterAllocated( - const std::initializer_list& allocated_registers, const int index) { - return std::ranges::find_if(allocated_registers.begin(), allocated_registers.end(), - [index](const Xbyak::Operand* operand) { - return OperandUsesRegister(operand, index); - }) != allocated_registers.end(); -} - -static Xbyak::Reg AllocateScratchRegister( - const std::initializer_list allocated_registers, const u32 bits) { - for (int index = Xbyak::Operand::R8; index <= Xbyak::Operand::R15; index++) { - if (!IsRegisterAllocated(allocated_registers, index)) { - return Xbyak::Reg32e(index, static_cast(bits)); - } - } - UNREACHABLE_MSG("Out of scratch registers!"); -} - -#ifdef __APPLE__ - -static pthread_key_t stack_pointer_slot; -static pthread_key_t patch_stack_slot; -static std::once_flag patch_context_slots_init_flag; -static constexpr u32 patch_stack_size = 0x1000; - -static_assert(sizeof(void*) == sizeof(u64), - "Cannot fit a register inside a thread local storage slot."); - -static void FreePatchStack(void* patch_stack) { - // Subtract back to the bottom of the stack for free. - std::free(static_cast(patch_stack) - patch_stack_size); -} - -static void InitializePatchContextSlots() { - ASSERT_MSG(pthread_key_create(&stack_pointer_slot, nullptr) == 0, - "Unable to allocate thread-local register for stack pointer."); - ASSERT_MSG(pthread_key_create(&patch_stack_slot, FreePatchStack) == 0, - "Unable to allocate thread-local register for patch stack."); -} - -void InitializeThreadPatchStack() { - std::call_once(patch_context_slots_init_flag, InitializePatchContextSlots); - - pthread_setspecific(patch_stack_slot, - static_cast(std::malloc(patch_stack_size)) + patch_stack_size); -} - -/// Saves the stack pointer to thread local storage and loads the patch stack. -static void SaveStack(Xbyak::CodeGenerator& c) { - std::call_once(patch_context_slots_init_flag, InitializePatchContextSlots); - - // Save original stack pointer and load patch stack. - c.putSeg(gs); - c.mov(qword[reinterpret_cast(stack_pointer_slot * sizeof(void*))], rsp); - c.putSeg(gs); - c.mov(rsp, qword[reinterpret_cast(patch_stack_slot * sizeof(void*))]); -} - -/// Restores the stack pointer from thread local storage. -static void RestoreStack(Xbyak::CodeGenerator& c) { - std::call_once(patch_context_slots_init_flag, InitializePatchContextSlots); - - // Save patch stack pointer and load original stack. - c.putSeg(gs); - c.mov(qword[reinterpret_cast(patch_stack_slot * sizeof(void*))], rsp); - c.putSeg(gs); - c.mov(rsp, qword[reinterpret_cast(stack_pointer_slot * sizeof(void*))]); -} - -/// Validates that the dst register is supported given the SaveStack/RestoreStack implementation. -static void ValidateDst(const Xbyak::Reg& dst) { - // No restrictions. -} - -#else - -void InitializeThreadPatchStack() { - // No-op -} - -// NOTE: Since stack pointer here is subtracted through safe zone and not saved anywhere, -// it must not be modified during the instruction. Otherwise, we will not be able to find -// and load registers back from where they were saved. Thus, a limitation is placed on -// instructions, that they must not use the stack pointer register as a destination. - -/// Saves the stack pointer to thread local storage and loads the patch stack. -static void SaveStack(Xbyak::CodeGenerator& c) { - c.lea(rsp, ptr[rsp - 128]); // red zone -} - -/// Restores the stack pointer from thread local storage. -static void RestoreStack(Xbyak::CodeGenerator& c) { - c.lea(rsp, ptr[rsp + 128]); // red zone -} - -/// Validates that the dst register is supported given the SaveStack/RestoreStack implementation. -static void ValidateDst(const Xbyak::Reg& dst) { - // Stack pointer is not preserved, so it can't be used as a dst. - ASSERT_MSG(dst.getIdx() != rsp.getIdx(), "Stack pointer not supported as destination."); -} - -#endif - -/// Switches to the patch stack, saves registers, and restores the original stack. -static void SaveRegisters(Xbyak::CodeGenerator& c, const std::initializer_list regs) { - // Uses a more robust solution for saving registers on MacOS to avoid potential stack corruption - // if games decide to not follow the ABI and use the red zone. - SaveStack(c); - for (const auto& reg : regs) { - c.push(reg.cvt64()); - } - RestoreStack(c); -} - -/// Switches to the patch stack, restores registers, and restores the original stack. -static void RestoreRegisters(Xbyak::CodeGenerator& c, - const std::initializer_list regs) { - SaveStack(c); - for (const auto& reg : regs) { - c.pop(reg.cvt64()); - } - RestoreStack(c); -} - -/// Switches to the patch stack and stores all registers. -static void SaveContext(Xbyak::CodeGenerator& c, bool save_flags = false) { - SaveStack(c); - for (int reg = Xbyak::Operand::RAX; reg <= Xbyak::Operand::R15; reg++) { - c.push(Xbyak::Reg64(reg)); - } - c.lea(rsp, ptr[rsp - 32 * 16]); - for (int reg = 0; reg <= 15; reg++) { - c.vmovdqu(ptr[rsp + 32 * reg], Xbyak::Ymm(reg)); - } - if (save_flags) { - c.pushfq(); - } -} - -/// Restores all registers and restores the original stack. -/// If the destination is a register, it is not restored to preserve the output. -static void RestoreContext(Xbyak::CodeGenerator& c, const Xbyak::Operand& dst, - bool restore_flags = false) { - if (restore_flags) { - c.popfq(); - } - for (int reg = 15; reg >= 0; reg--) { - if ((!dst.isXMM() && !dst.isYMM()) || dst.getIdx() != reg) { - c.vmovdqu(Xbyak::Ymm(reg), ptr[rsp + 32 * reg]); - } - } - c.lea(rsp, ptr[rsp + 32 * 16]); - for (int reg = Xbyak::Operand::R15; reg >= Xbyak::Operand::RAX; reg--) { - if (!dst.isREG() || dst.getIdx() != reg) { - c.pop(Xbyak::Reg64(reg)); - } else { - c.lea(rsp, ptr[rsp + 8]); - } - } - RestoreStack(c); -} - -static void GenerateANDN(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { - const auto dst = ZydisToXbyakRegisterOperand(operands[0]); - const auto src1 = ZydisToXbyakRegisterOperand(operands[1]); - const auto src2 = ZydisToXbyakOperand(operands[2]); - ValidateDst(dst); - - // Check if src2 is a memory operand or a register different to dst. - // In those cases, we don't need to use a temporary register and are free to modify dst. - // In cases where dst and src2 are the same register, a temporary needs to be used to avoid - // modifying src2. - bool src2_uses_dst = false; - if (src2->isMEM()) { - const auto base = src2->getAddress().getRegExp().getBase().getIdx(); - const auto index = src2->getAddress().getRegExp().getIndex().getIdx(); - src2_uses_dst = base == dst.getIdx() || index == dst.getIdx(); - } else { - ASSERT(src2->isREG()); - src2_uses_dst = src2->getReg() == dst; - } - - if (!src2_uses_dst) { - if (dst != src1) - c.mov(dst, src1); - c.not_(dst); - c.and_(dst, *src2); - } else { - const auto scratch = AllocateScratchRegister({&dst, &src1, src2.get()}, dst.getBit()); - - SaveRegisters(c, {scratch}); - - c.mov(scratch, src1); - c.not_(scratch); - c.and_(scratch, *src2); - c.mov(dst, scratch); - - RestoreRegisters(c, {scratch}); - } -} - -static void GenerateBEXTR(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { - const auto dst = ZydisToXbyakRegisterOperand(operands[0]); - const auto src = ZydisToXbyakOperand(operands[1]); - const auto start_len = ZydisToXbyakRegisterOperand(operands[2]); - ValidateDst(dst); - - const Xbyak::Reg32e shift(Xbyak::Operand::RCX, static_cast(start_len.getBit())); - const auto scratch1 = - AllocateScratchRegister({&dst, src.get(), &start_len, &shift}, dst.getBit()); - const auto scratch2 = - AllocateScratchRegister({&dst, src.get(), &start_len, &shift, &scratch1}, dst.getBit()); - - if (dst.getIdx() == shift.getIdx()) { - SaveRegisters(c, {scratch1, scratch2}); - } else { - SaveRegisters(c, {scratch1, scratch2, shift}); - } - - c.mov(scratch1, *src); - if (shift.getIdx() != start_len.getIdx()) { - c.mov(shift, start_len); - } - - c.shr(scratch1, shift.cvt8()); - c.shr(shift, 8); - c.mov(scratch2, 1); - c.shl(scratch2, shift.cvt8()); - c.dec(scratch2); - - c.mov(dst, scratch1); - c.and_(dst, scratch2); - - if (dst.getIdx() == shift.getIdx()) { - RestoreRegisters(c, {scratch2, scratch1}); - } else { - RestoreRegisters(c, {shift, scratch2, scratch1}); - } -} - -static void GenerateBLSI(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { - const auto dst = ZydisToXbyakRegisterOperand(operands[0]); - const auto src = ZydisToXbyakOperand(operands[1]); - ValidateDst(dst); - - const auto scratch = AllocateScratchRegister({&dst, src.get()}, dst.getBit()); - - SaveRegisters(c, {scratch}); - - // BLSI sets CF to zero if source is zero, otherwise it sets CF to one. - Xbyak::Label clear_carry, end; - - c.mov(scratch, *src); - c.neg(scratch); // NEG, like BLSI, clears CF if the source is zero and sets it otherwise - c.jnc(clear_carry); - - c.and_(scratch, *src); - c.stc(); // setting/clearing carry needs to happen after the AND because that clears CF - c.jmp(end); - - c.L(clear_carry); - c.and_(scratch, *src); - // We don't need to clear carry here since AND does that for us - - c.L(end); - c.mov(dst, scratch); - - RestoreRegisters(c, {scratch}); -} - -static void GenerateBLSMSK(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { - const auto dst = ZydisToXbyakRegisterOperand(operands[0]); - const auto src = ZydisToXbyakOperand(operands[1]); - ValidateDst(dst); - - const auto scratch = AllocateScratchRegister({&dst, src.get()}, dst.getBit()); - - SaveRegisters(c, {scratch}); - - Xbyak::Label clear_carry, end; - - // BLSMSK sets CF to zero if source is NOT zero, otherwise it sets CF to one. - c.mov(scratch, *src); - c.test(scratch, scratch); - c.jnz(clear_carry); - - c.dec(scratch); - c.xor_(scratch, *src); - c.stc(); - c.jmp(end); - - c.L(clear_carry); - c.dec(scratch); - c.xor_(scratch, *src); - // We don't need to clear carry here since XOR does that for us - - c.L(end); - c.mov(dst, scratch); - - RestoreRegisters(c, {scratch}); -} - -static void GenerateTZCNT(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { - const auto dst = ZydisToXbyakRegisterOperand(operands[0]); - const auto src = ZydisToXbyakOperand(operands[1]); - ValidateDst(dst); - - Xbyak::Label src_zero, end; - - c.cmp(*src, 0); - c.je(src_zero); - - // If src is not zero, functions like a BSF, but also clears the CF - c.bsf(dst, *src); - c.clc(); - c.jmp(end); - - c.L(src_zero); - c.mov(dst, operands[0].size); - // Since dst is not zero, also set ZF to zero. Testing dst with itself when we know - // it isn't zero is a good way to do this. - // Use cvt32 to avoid REX/Operand size prefixes. - c.test(dst.cvt32(), dst.cvt32()); - // When source is zero, TZCNT also sets CF. - c.stc(); - - c.L(end); -} - -static void GenerateBLSR(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { - const auto dst = ZydisToXbyakRegisterOperand(operands[0]); - const auto src = ZydisToXbyakOperand(operands[1]); - ValidateDst(dst); - - const auto scratch = AllocateScratchRegister({&dst, src.get()}, dst.getBit()); - - SaveRegisters(c, {scratch}); - - Xbyak::Label clear_carry, end; - - // BLSR sets CF to zero if source is NOT zero, otherwise it sets CF to one. - c.mov(scratch, *src); - c.test(scratch, scratch); - c.jnz(clear_carry); - - c.dec(scratch); - c.and_(scratch, *src); - c.stc(); - c.jmp(end); - - c.L(clear_carry); - c.dec(scratch); - c.and_(scratch, *src); - // We don't need to clear carry here since AND does that for us - - c.L(end); - c.mov(dst, scratch); - - RestoreRegisters(c, {scratch}); -} - -#ifdef __APPLE__ - -static __attribute__((sysv_abi)) void PerformVCVTPH2PS(float* out, const half_float::half* in, - const u32 count) { - for (u32 i = 0; i < count; i++) { - out[i] = half_float::half_cast(in[i]); - } -} - -static void GenerateVCVTPH2PS(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { - const auto dst = ZydisToXbyakRegisterOperand(operands[0]); - const auto src = ZydisToXbyakOperand(operands[1]); - - const auto float_count = dst.getBit() / 32; - const auto byte_count = float_count * 4; - - SaveContext(c, true); - - // Allocate stack space for outputs and load into first parameter. - c.sub(rsp, byte_count); - c.mov(rdi, rsp); - - if (src->isXMM()) { - // Allocate stack space for inputs and load into second parameter. - c.sub(rsp, byte_count); - c.mov(rsi, rsp); - - // Move input to the allocated space. - c.movdqu(ptr[rsp], *reinterpret_cast(src.get())); - } else { - c.lea(rsi, src->getAddress()); - } - - // Load float count into third parameter. - c.mov(rdx, float_count); - - c.mov(rax, reinterpret_cast(PerformVCVTPH2PS)); - c.call(rax); - - if (src->isXMM()) { - // Clean up after inputs space. - c.add(rsp, byte_count); - } - - // Load outputs into destination register and clean up space. - if (dst.isYMM()) { - c.vmovdqu(*reinterpret_cast(&dst), ptr[rsp]); - } else { - c.movdqu(*reinterpret_cast(&dst), ptr[rsp]); - } - c.add(rsp, byte_count); - - RestoreContext(c, dst, true); -} - -using SingleToHalfFloatConverter = half_float::half (*)(float); -static const SingleToHalfFloatConverter SingleToHalfFloatConverters[4] = { - half_float::half_cast, - half_float::half_cast, - half_float::half_cast, - half_float::half_cast, -}; - -static __attribute__((sysv_abi)) void PerformVCVTPS2PH(half_float::half* out, const float* in, - const u32 count, const u8 rounding_mode) { - const auto conversion_func = SingleToHalfFloatConverters[rounding_mode]; - - for (u32 i = 0; i < count; i++) { - out[i] = conversion_func(in[i]); - } -} - -static void GenerateVCVTPS2PH(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { - const auto dst = ZydisToXbyakOperand(operands[0]); - const auto src = ZydisToXbyakRegisterOperand(operands[1]); - const auto ctrl = ZydisToXbyakImmediateOperand(operands[2]); - - const auto float_count = src.getBit() / 32; - const auto byte_count = float_count * 4; - - SaveContext(c, true); - - if (dst->isXMM()) { - // Allocate stack space for outputs and load into first parameter. - c.sub(rsp, byte_count); - c.mov(rdi, rsp); - } else { - c.lea(rdi, dst->getAddress()); - } - - // Allocate stack space for inputs and load into second parameter. - c.sub(rsp, byte_count); - c.mov(rsi, rsp); - - // Move input to the allocated space. - if (src.isYMM()) { - c.vmovdqu(ptr[rsp], *reinterpret_cast(&src)); - } else { - c.movdqu(ptr[rsp], *reinterpret_cast(&src)); - } - - // Load float count into third parameter. - c.mov(rdx, float_count); - - // Load rounding mode into fourth parameter. - if (ctrl & 4) { - // Load from MXCSR.RC. - c.stmxcsr(ptr[rsp - 4]); - c.mov(rcx, ptr[rsp - 4]); - c.shr(rcx, 13); - c.and_(rcx, 3); - } else { - c.mov(rcx, ctrl & 3); - } - - c.mov(rax, reinterpret_cast(PerformVCVTPS2PH)); - c.call(rax); - - // Clean up after inputs space. - c.add(rsp, byte_count); - - if (dst->isXMM()) { - // Load outputs into destination register and clean up space. - c.movdqu(*reinterpret_cast(dst.get()), ptr[rsp]); - c.add(rsp, byte_count); - } - - RestoreContext(c, *dst, true); -} - -static bool FilterRosetta2Only(const ZydisDecodedOperand*) { - int ret = 0; - size_t size = sizeof(ret); - if (sysctlbyname("sysctl.proc_translated", &ret, &size, nullptr, 0) != 0) { - return false; - } - return ret; -} - -#else // __APPLE__ - static bool FilterTcbAccess(const ZydisDecodedOperand* operands) { const auto& dst_op = operands[0]; const auto& src_op = operands[1]; @@ -657,18 +121,11 @@ static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGe #endif } -#endif // __APPLE__ - static bool FilterNoSSE4a(const ZydisDecodedOperand*) { Cpu cpu; return !cpu.has(Cpu::tSSE4a); } -static bool FilterNoBMI1(const ZydisDecodedOperand*) { - Cpu cpu; - return !cpu.has(Cpu::tBMI1); -} - static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE; @@ -940,30 +397,16 @@ struct PatchInfo { }; static const std::unordered_map Patches = { + // SSE4a + {ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}}, + {ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}}, + #if defined(_WIN32) // Windows needs a trampoline. {ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, true}}, #elif !defined(__APPLE__) {ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}}, #endif - - {ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}}, - {ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}}, - - // BMI1 - {ZYDIS_MNEMONIC_ANDN, {FilterNoBMI1, GenerateANDN, true}}, - {ZYDIS_MNEMONIC_BEXTR, {FilterNoBMI1, GenerateBEXTR, true}}, - {ZYDIS_MNEMONIC_BLSI, {FilterNoBMI1, GenerateBLSI, true}}, - {ZYDIS_MNEMONIC_BLSMSK, {FilterNoBMI1, GenerateBLSMSK, true}}, - {ZYDIS_MNEMONIC_BLSR, {FilterNoBMI1, GenerateBLSR, true}}, - {ZYDIS_MNEMONIC_TZCNT, {FilterNoBMI1, GenerateTZCNT, true}}, - -#ifdef __APPLE__ - // Patches for instruction sets not supported by Rosetta 2. - // F16C - {ZYDIS_MNEMONIC_VCVTPH2PS, {FilterRosetta2Only, GenerateVCVTPH2PS, true}}, - {ZYDIS_MNEMONIC_VCVTPS2PH, {FilterRosetta2Only, GenerateVCVTPS2PH, true}}, -#endif }; static std::once_flag init_flag; @@ -1280,18 +723,7 @@ void RegisterPatchModule(void* module_ptr, u64 module_size, void* trampoline_are } void PrePatchInstructions(u64 segment_addr, u64 segment_size) { -#if defined(__APPLE__) - // HACK: For some reason patching in the signal handler at the start of a page does not work - // under Rosetta 2. Patch any instructions at the start of a page ahead of time. - if (!Patches.empty()) { - auto* code_page = reinterpret_cast(Common::AlignUp(segment_addr, 0x1000)); - const auto* end_page = code_page + Common::AlignUp(segment_size, 0x1000); - while (code_page < end_page) { - TryPatchJit(code_page); - code_page += 0x1000; - } - } -#elif !defined(_WIN32) +#if !defined(_WIN32) && !defined(__APPLE__) // Linux and others have an FS segment pointing to valid memory, so continue to do full // ahead-of-time patching for now until a better solution is worked out. if (!Patches.empty()) { diff --git a/src/core/cpu_patches.h b/src/core/cpu_patches.h index 1ccac073a..7a0546046 100644 --- a/src/core/cpu_patches.h +++ b/src/core/cpu_patches.h @@ -7,12 +7,6 @@ namespace Core { -/// Initializes a stack for the current thread for use by patch implementations. -void InitializeThreadPatchStack(); - -/// Cleans up the patch stack for the current thread. -void CleanupThreadPatchStack(); - /// Registers a module for patching, providing an area to generate trampoline code. void RegisterPatchModule(void* module_ptr, u64 module_size, void* trampoline_area_ptr, u64 trampoline_area_size); diff --git a/src/core/tls.cpp b/src/core/tls.cpp index 9b3178171..e13c683e1 100644 --- a/src/core/tls.cpp +++ b/src/core/tls.cpp @@ -5,7 +5,6 @@ #include "common/arch.h" #include "common/assert.h" #include "common/types.h" -#include "core/cpu_patches.h" #include "core/libraries/kernel/threads/pthread.h" #include "core/tls.h" @@ -197,12 +196,7 @@ Tcb* GetTcbBase() { thread_local std::once_flag init_tls_flag; void EnsureThreadInitialized() { - std::call_once(init_tls_flag, [] { -#ifdef ARCH_X86_64 - InitializeThreadPatchStack(); -#endif - SetTcbBase(Libraries::Kernel::g_curthread->tcb); - }); + std::call_once(init_tls_flag, [] { SetTcbBase(Libraries::Kernel::g_curthread->tcb); }); } } // namespace Core From 7bb49d85d3bd4a522b5976205a631fada37dec4d Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 3 Apr 2025 22:43:04 +0300 Subject: [PATCH 106/194] New Crowdin updates (#2724) * New translations en_us.ts (Arabic) * New translations en_us.ts (Albanian) * New translations en_us.ts (German) --- src/qt_gui/translations/ar_SA.ts | 16 ++++++------- src/qt_gui/translations/de_DE.ts | 40 ++++++++++++++++++-------------- src/qt_gui/translations/sq_AL.ts | 20 ++++++++-------- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index c130a374c..63ae733e8 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -1126,7 +1126,7 @@ Deadzone Offset (def 0.50): - : + إزاحة المدى الغير فعال (الأصل ٠.٥٠). Speed Multiplier (def 1.0): @@ -1704,7 +1704,7 @@ Update Compatibility Database - Update Compatibility Database + تحديث قاعدة بيانات التوافق Volume @@ -1792,15 +1792,15 @@ Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + عرض بيانات التوافق:\nيقوم بإظهار معلومات توافق اللعبة في طريقة عرض الطاولة. تشغيل"تحديث التوافق عند التشغيل" للحصول على معلومات محدثة. Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + تحديث التوافق عند التشغيل:\nتحديث قاعدة بيانات التوافق تلقائياً عند تشغيل shadps4. Update Compatibility Database:\nImmediately update the compatibility database. - Update Compatibility Database:\nImmediately update the compatibility database. + تحديث قاعدة بيانات التوافق:\nقم بتحديث قاعدة بيانات التوافق حالاً. Never @@ -1852,7 +1852,7 @@ Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. - Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + تشغيل HDR:\n يقوم بتشغيل HDR في الألعاب المدعومة.\nيجب أن تدعم شاشتك أطياف ألوان BT2020 PQ و صيغة تنسيق المبادلة RGB10A2. Game Folders:\nThe list of folders to check for installed games. @@ -1884,11 +1884,11 @@ Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). - Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + تجميع برامج التظليل:\n يجب أن تقوم بتشغيل هذا لتعديل برامج التظليل باستخدام قائمة تصحيح الأخطاء (Ctrl + F10). 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. - 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مفيد لتصحيح أخطاء 'فصل الجهاز'. إذا قمت بتشغيل هذا من الأفضل أن تقوم بتشغيل "استضافة علامات تصحيح الأخطاء" و "ضيف علامات تصحيح الأخطاء".\nلا يعمل على وحدة معالجة رسوم إنتل.\nتحتاج لتشغيل التحقق من طبقات Vulkan و مجموعة تطوير البرامج الخاصة بـVulkan من أجل أن يعمل هذا. Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 9642cd500..c7a18dd99 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -1277,11 +1277,11 @@ Trophy Viewer - Trophy Viewer + Trophäenansicht No games found. Please add your games to your library first. - No games found. Please add your games to your library first. + Keine Spiele gefunden. Bitte fügen Sie zuerst Ihre Spiele zu Ihrer Bibliothek hinzu. Search... @@ -1409,43 +1409,43 @@ Play - Play + Spielen Pause - Pause + Pause Stop - Stop + Stopp Restart - Restart + Neustarten Full Screen - Full Screen + Vollbild Controllers - Controllers + Controller Keyboard - Keyboard + Tastatur Refresh List - Refresh List + Liste aktualisieren Resume - Resume + Fortsetzen Show Labels Under Icons - Show Labels Under Icons + Etiketten unter Symbolen anzeigen @@ -2048,7 +2048,11 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Öffnen Sie den Ordner für benutzerdefinierte Trophäenbilder/-sounds:\n +Sie können benutzerdefinierte Bilder zu den Trophäen hinzufügen und einen Ton hinzufügen.\n +Fügen Sie die Dateien dem Ordner custom_trophy mit folgenden Namen hinzu:\n +trophy.wav ODER trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\n +Hinweis: Der Sound funktioniert nur in Qt-Versionen. @@ -2059,23 +2063,23 @@ Select Game: - Select Game: + Spiel auswählen: Progress - Progress + Fortschritt Show Earned Trophies - Show Earned Trophies + Verdiente Trophäen anzeigen Show Not Earned Trophies - Show Not Earned Trophies + Nicht verdiente Trophäen anzeigen Show Hidden Trophies - Show Hidden Trophies + Verborgene Trophäen anzeigen diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 311633a99..50314a9b2 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1409,43 +1409,43 @@ Play - Play + Luaj Pause - Pause + Pezullo Stop - Stop + Ndalo Restart - Restart + Rinis Full Screen - Full Screen + Ekran i Plotë Controllers - Controllers + Dorezat Keyboard - Keyboard + Tastiera Refresh List - Refresh List + Rifresko Listën Resume - Resume + Rifillo Show Labels Under Icons - Show Labels Under Icons + Shfaq Etiketat Poshtë Ikonave From 1ee201690280d23454d7cd3548045c3f1a9f1669 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:44:12 -0700 Subject: [PATCH 107/194] doc: Update README macOS details --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9259ab875..6462c8bd2 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shad Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-macos.md). > [!IMPORTANT] -> macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 14 on Intel-based Mac devices. +> macOS users need at least macOS 15.4 to run shadPS4. Due to GPU issues there are currently heavy bugs on Intel Macs. # Debugging and reporting issues From 54b4d7fc788d570c14924d21fbb1f58de0254aad Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 4 Apr 2025 05:13:13 -0700 Subject: [PATCH 108/194] build: Target same CPU architecture level as PS4. (#2745) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 228a664eb..2af30ed46 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,8 +54,8 @@ else() endif() if (ARCHITECTURE STREQUAL "x86_64") - # Set x86_64 target level to Sandy Bridge to generally match what is supported for PS4 guest code with CPU patches. - add_compile_options(-march=sandybridge) + # Target the same x86_64 feature set as the PS4 CPU to match requirements. + add_compile_options(-march=btver2 -mno-sse4a) endif() if (APPLE AND ARCHITECTURE STREQUAL "x86_64" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64") From 0c6f2b470fde23011ba3c840b6ed40f79cbee7b7 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sat, 5 Apr 2025 13:14:27 -0700 Subject: [PATCH 109/194] renderer_vulkan: Use more depth-stencil dynamic state. (#2749) --- src/video_core/amdgpu/liverpool.h | 4 + .../renderer_vulkan/vk_graphics_pipeline.cpp | 30 ++-- .../renderer_vulkan/vk_graphics_pipeline.h | 16 -- .../renderer_vulkan/vk_instance.cpp | 4 + src/video_core/renderer_vulkan/vk_instance.h | 5 + .../renderer_vulkan/vk_pipeline_cache.cpp | 14 +- .../renderer_vulkan/vk_rasterizer.cpp | 157 ++++++++++-------- .../renderer_vulkan/vk_rasterizer.h | 3 +- 8 files changed, 116 insertions(+), 117 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 474c04ec2..8f9292f1c 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -1423,6 +1423,10 @@ struct Liverpool { return num_samples; } + bool IsClipDisabled() const { + return clipper_control.clip_disable || primitive_type == PrimitiveType::RectList; + } + void SetDefaults(); }; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index c528258fb..7cd4bd872 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -105,7 +105,6 @@ GraphicsPipeline::GraphicsPipeline( .frontFace = key.front_face == Liverpool::FrontFace::Clockwise ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise, - .depthBiasEnable = key.depth_bias_enable, .lineWidth = 1.0f, }; @@ -123,18 +122,20 @@ GraphicsPipeline::GraphicsPipeline( .pNext = instance.IsDepthClipControlSupported() ? &clip_control : nullptr, }; - boost::container::static_vector dynamic_states = { - vk::DynamicState::eViewportWithCountEXT, - vk::DynamicState::eScissorWithCountEXT, - vk::DynamicState::eBlendConstants, - vk::DynamicState::eDepthBounds, - vk::DynamicState::eDepthBias, - vk::DynamicState::eStencilReference, - vk::DynamicState::eStencilCompareMask, - vk::DynamicState::eStencilWriteMask, + boost::container::static_vector dynamic_states = { + vk::DynamicState::eViewportWithCountEXT, vk::DynamicState::eScissorWithCountEXT, + vk::DynamicState::eBlendConstants, vk::DynamicState::eDepthTestEnableEXT, + vk::DynamicState::eDepthWriteEnableEXT, vk::DynamicState::eDepthCompareOpEXT, + vk::DynamicState::eDepthBiasEnableEXT, vk::DynamicState::eDepthBias, + vk::DynamicState::eStencilTestEnableEXT, vk::DynamicState::eStencilReference, + vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask, vk::DynamicState::eStencilOpEXT, }; + if (instance.IsDepthBoundsSupported()) { + dynamic_states.push_back(vk::DynamicState::eDepthBoundsTestEnableEXT); + dynamic_states.push_back(vk::DynamicState::eDepthBounds); + } if (instance.IsDynamicColorWriteMaskSupported()) { dynamic_states.push_back(vk::DynamicState::eColorWriteMaskEXT); } @@ -149,14 +150,6 @@ GraphicsPipeline::GraphicsPipeline( .pDynamicStates = dynamic_states.data(), }; - const vk::PipelineDepthStencilStateCreateInfo depth_info = { - .depthTestEnable = key.depth_test_enable, - .depthWriteEnable = key.depth_write_enable, - .depthCompareOp = key.depth_compare_op, - .depthBoundsTestEnable = key.depth_bounds_test_enable, - .stencilTestEnable = key.stencil_test_enable, - }; - boost::container::static_vector shader_stages; auto stage = u32(Shader::LogicalStage::Vertex); @@ -292,7 +285,6 @@ GraphicsPipeline::GraphicsPipeline( .pViewportState = &viewport_info, .pRasterizationState = &raster_state, .pMultisampleState = &multisampling, - .pDepthStencilState = &depth_info, .pColorBlendState = &color_blending, .pDynamicState = &dynamic_info, .layout = *pipeline_layout, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index e6596db2f..7ffd14064 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -39,18 +39,6 @@ struct GraphicsPipelineKey { vk::Format depth_format; vk::Format stencil_format; - struct { - bool clip_disable : 1; - bool depth_test_enable : 1; - bool depth_write_enable : 1; - bool depth_bounds_test_enable : 1; - bool depth_bias_enable : 1; - bool stencil_test_enable : 1; - // Must be named to be zero-initialized. - u8 _unused : 2; - }; - vk::CompareOp depth_compare_op; - u32 num_samples; u32 mrt_mask; AmdGpu::PrimitiveType prim_type; @@ -94,10 +82,6 @@ public: return key.mrt_mask; } - auto IsClipDisabled() const { - return key.clip_disable; - } - [[nodiscard]] bool IsPrimitiveListTopology() const { return key.prim_type == AmdGpu::PrimitiveType::PointList || key.prim_type == AmdGpu::PrimitiveType::LineList || diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 1f0125791..d45889054 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -247,6 +247,7 @@ bool Instance::CreateDevice() { add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); + add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); const bool maintenance4 = add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); @@ -380,6 +381,9 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT{ .extendedDynamicState = true, }, + vk::PhysicalDeviceExtendedDynamicState2FeaturesEXT{ + .extendedDynamicState2 = true, + }, vk::PhysicalDeviceMaintenance4FeaturesKHR{ .maintenance4 = true, }, diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 013003b9b..04b68c1d0 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -84,6 +84,11 @@ public: return features.samplerAnisotropy; } + /// Returns true if depth bounds testing is supported + bool IsDepthBoundsSupported() const { + return features.depthBounds; + } + /// Returns true when VK_EXT_custom_border_color is supported bool IsCustomBorderColorSupported() const { return custom_border_color; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index d51c8fbd5..17a1fdec4 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -123,7 +123,7 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS info.vs_info.emulate_depth_negative_one_to_one = !instance.IsDepthClipControlSupported() && regs.clipper_control.clip_space == Liverpool::ClipSpace::MinusWToW; - info.vs_info.clip_disable = graphics_key.clip_disable; + info.vs_info.clip_disable = regs.IsClipDisabled(); if (l_stage == LogicalStage::TessellationEval) { info.vs_info.tess_type = regs.tess_config.type; info.vs_info.tess_topology = regs.tess_config.topology; @@ -267,16 +267,6 @@ bool PipelineCache::RefreshGraphicsKey() { auto& regs = liverpool->regs; auto& key = graphics_key; - key.clip_disable = - regs.clipper_control.clip_disable || regs.primitive_type == AmdGpu::PrimitiveType::RectList; - key.depth_test_enable = regs.depth_control.depth_enable; - key.depth_write_enable = - regs.depth_control.depth_write_enable && !regs.depth_render_control.depth_clear_enable; - key.depth_bounds_test_enable = regs.depth_control.depth_bounds_enable; - key.depth_bias_enable = regs.polygon_control.NeedsBias(); - key.depth_compare_op = LiverpoolToVK::CompareOp(regs.depth_control.depth_func); - key.stencil_test_enable = regs.depth_control.stencil_enable; - const auto depth_format = instance.GetSupportedFormat( LiverpoolToVK::DepthFormat(regs.depth_buffer.z_info.format, regs.depth_buffer.stencil_info.format), @@ -285,13 +275,11 @@ bool PipelineCache::RefreshGraphicsKey() { key.depth_format = depth_format; } else { key.depth_format = vk::Format::eUndefined; - key.depth_test_enable = false; } if (regs.depth_buffer.StencilValid()) { key.stencil_format = depth_format; } else { key.stencil_format = vk::Format::eUndefined; - key.stencil_test_enable = false; } key.prim_type = regs.primitive_type; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 87d07a967..ecb0c0a75 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -947,81 +947,18 @@ void Rasterizer::UnmapMemory(VAddr addr, u64 size) { } void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { - UpdateViewportScissorState(pipeline); + UpdateViewportScissorState(); + UpdateDepthStencilState(); - auto& regs = liverpool->regs; + const auto& regs = liverpool->regs; const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.setBlendConstants(®s.blend_constants.red); - if (instance.IsDynamicColorWriteMaskSupported()) { cmdbuf.setColorWriteMaskEXT(0, pipeline.GetWriteMasks()); } - if (regs.depth_control.depth_bounds_enable) { - cmdbuf.setDepthBounds(regs.depth_bounds_min, regs.depth_bounds_max); - } - if (regs.polygon_control.enable_polygon_offset_front) { - cmdbuf.setDepthBias(regs.poly_offset.front_offset, regs.poly_offset.depth_bias, - regs.poly_offset.front_scale / 16.f); - } else if (regs.polygon_control.enable_polygon_offset_back) { - cmdbuf.setDepthBias(regs.poly_offset.back_offset, regs.poly_offset.depth_bias, - regs.poly_offset.back_scale / 16.f); - } - - if (regs.depth_control.stencil_enable) { - const auto front_fail_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_front); - const auto front_pass_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_front); - const auto front_depth_fail_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_front); - const auto front_compare_op = LiverpoolToVK::CompareOp(regs.depth_control.stencil_ref_func); - if (regs.depth_control.backface_enable) { - const auto back_fail_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_back); - const auto back_pass_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_back); - const auto back_depth_fail_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_back); - const auto back_compare_op = - LiverpoolToVK::CompareOp(regs.depth_control.stencil_bf_func); - cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFront, front_fail_op, front_pass_op, - front_depth_fail_op, front_compare_op); - cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eBack, back_fail_op, back_pass_op, - back_depth_fail_op, back_compare_op); - } else { - cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFrontAndBack, front_fail_op, - front_pass_op, front_depth_fail_op, front_compare_op); - } - - const auto front = regs.stencil_ref_front; - const auto back = regs.stencil_ref_back; - if (front.stencil_test_val == back.stencil_test_val) { - cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, - front.stencil_test_val); - } else { - cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFront, front.stencil_test_val); - cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eBack, back.stencil_test_val); - } - - if (front.stencil_write_mask == back.stencil_write_mask) { - cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, - front.stencil_write_mask); - } else { - cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFront, front.stencil_write_mask); - cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eBack, back.stencil_write_mask); - } - - if (front.stencil_mask == back.stencil_mask) { - cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, - front.stencil_mask); - } else { - cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFront, front.stencil_mask); - cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eBack, back.stencil_mask); - } - } } -void Rasterizer::UpdateViewportScissorState(const GraphicsPipeline& pipeline) { +void Rasterizer::UpdateViewportScissorState() { const auto& regs = liverpool->regs; const auto combined_scissor_value_tl = [](s16 scr, s16 win, s16 gen, s16 win_offset) { @@ -1072,7 +1009,7 @@ void Rasterizer::UpdateViewportScissorState(const GraphicsPipeline& pipeline) { const auto zoffset = vp_ctl.zoffset_enable ? vp.zoffset : 0.f; const auto zscale = vp_ctl.zscale_enable ? vp.zscale : 1.f; - if (pipeline.IsClipDisabled()) { + if (regs.IsClipDisabled()) { // In case if clipping is disabled we patch the shader to convert vertex position // from screen space coordinates to NDC by defining a render space as full hardware // window range [0..16383, 0..16383] and setting the viewport to its size. @@ -1139,6 +1076,90 @@ void Rasterizer::UpdateViewportScissorState(const GraphicsPipeline& pipeline) { cmdbuf.setScissorWithCountEXT(scissors); } +void Rasterizer::UpdateDepthStencilState() { + auto& regs = liverpool->regs; + const auto cmdbuf = scheduler.CommandBuffer(); + + bool depth_test = regs.depth_control.depth_enable && regs.depth_buffer.DepthValid(); + cmdbuf.setDepthTestEnableEXT(depth_test); + cmdbuf.setDepthWriteEnableEXT(regs.depth_control.depth_write_enable && + !regs.depth_render_control.depth_clear_enable); + if (depth_test) { + cmdbuf.setDepthCompareOpEXT(LiverpoolToVK::CompareOp(regs.depth_control.depth_func)); + } + + if (instance.IsDepthBoundsSupported()) { + cmdbuf.setDepthBoundsTestEnableEXT(regs.depth_control.depth_bounds_enable); + if (regs.depth_control.depth_bounds_enable) { + cmdbuf.setDepthBounds(regs.depth_bounds_min, regs.depth_bounds_max); + } + } + + cmdbuf.setDepthBiasEnableEXT(regs.polygon_control.NeedsBias()); + if (regs.polygon_control.enable_polygon_offset_front) { + cmdbuf.setDepthBias(regs.poly_offset.front_offset, regs.poly_offset.depth_bias, + regs.poly_offset.front_scale / 16.f); + } else if (regs.polygon_control.enable_polygon_offset_back) { + cmdbuf.setDepthBias(regs.poly_offset.back_offset, regs.poly_offset.depth_bias, + regs.poly_offset.back_scale / 16.f); + } + + cmdbuf.setStencilTestEnableEXT(regs.depth_control.stencil_enable && + regs.depth_buffer.StencilValid()); + if (regs.depth_control.stencil_enable) { + const auto front_fail_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_front); + const auto front_pass_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_front); + const auto front_depth_fail_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_front); + const auto front_compare_op = LiverpoolToVK::CompareOp(regs.depth_control.stencil_ref_func); + if (regs.depth_control.backface_enable) { + const auto back_fail_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_back); + const auto back_pass_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_back); + const auto back_depth_fail_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_back); + const auto back_compare_op = + LiverpoolToVK::CompareOp(regs.depth_control.stencil_bf_func); + cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFront, front_fail_op, front_pass_op, + front_depth_fail_op, front_compare_op); + cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eBack, back_fail_op, back_pass_op, + back_depth_fail_op, back_compare_op); + } else { + cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFrontAndBack, front_fail_op, + front_pass_op, front_depth_fail_op, front_compare_op); + } + + const auto front = regs.stencil_ref_front; + const auto back = regs.stencil_ref_back; + if (front.stencil_test_val == back.stencil_test_val) { + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, + front.stencil_test_val); + } else { + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFront, front.stencil_test_val); + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eBack, back.stencil_test_val); + } + + if (front.stencil_write_mask == back.stencil_write_mask) { + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, + front.stencil_write_mask); + } else { + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFront, front.stencil_write_mask); + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eBack, back.stencil_write_mask); + } + + if (front.stencil_mask == back.stencil_mask) { + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, + front.stencil_mask); + } else { + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFront, front.stencil_mask); + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eBack, back.stencil_mask); + } + } +} + void Rasterizer::ScopeMarkerBegin(const std::string_view& str, bool from_guest) { if ((from_guest && !Config::getVkGuestMarkersEnabled()) || (!from_guest && !Config::getVkHostMarkersEnabled())) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 2fac8c8da..8e5d0065b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -76,7 +76,8 @@ private: void EliminateFastClear(); void UpdateDynamicState(const GraphicsPipeline& pipeline); - void UpdateViewportScissorState(const GraphicsPipeline& pipeline); + void UpdateViewportScissorState(); + void UpdateDepthStencilState(); bool FilterDraw(); From 9d2175180e1dafeecefef5d7870376c199107b6c Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 7 Apr 2025 02:22:51 -0700 Subject: [PATCH 110/194] build: Move versioning to CMake file. (#2752) --- CMakeLists.txt | 7 ++++--- src/common/config.cpp | 10 +++++----- src/common/scm_rev.cpp.in | 22 +++++++++------------- src/common/scm_rev.h | 3 +++ src/common/version.h | 14 -------------- src/emulator.cpp | 11 +++++------ src/input/input_handler.cpp | 1 - src/qt_gui/check_update.cpp | 5 ++--- src/qt_gui/gui_context_menus.h | 6 +++--- src/qt_gui/main_window.cpp | 9 ++++----- src/qt_gui/settings_dialog.cpp | 4 ++-- src/sdl_window.cpp | 1 - 12 files changed, 37 insertions(+), 56 deletions(-) delete mode 100644 src/common/version.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2af30ed46..844553340 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,6 +202,8 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) +set(APP_VERSION "0.7.1 WIP") +set(APP_IS_RELEASE false) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") @@ -671,7 +673,6 @@ set(COMMON src/common/logging/backend.cpp src/common/uint128.h src/common/unique_function.h src/common/va_ctx.h - src/common/version.h src/common/ntapi.h src/common/ntapi.cpp src/common/number_utils.h @@ -1193,8 +1194,8 @@ if (ENABLE_QT_GUI) MACOSX_BUNDLE ON MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/dist/MacOSBundleInfo.plist.in" MACOSX_BUNDLE_ICON_FILE "shadPS4.icns" - MACOSX_BUNDLE_SHORT_VERSION_STRING "0.4.1" - ) + MACOSX_BUNDLE_SHORT_VERSION_STRING "${APP_VERSION}" + ) set_source_files_properties(src/images/shadPS4.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) diff --git a/src/common/config.cpp b/src/common/config.cpp index 2657cd12a..111c0cfa9 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -7,10 +7,10 @@ #include // for wstring support #include +#include "common/config.h" +#include "common/logging/formatter.h" #include "common/path_util.h" -#include "config.h" -#include "logging/formatter.h" -#include "version.h" +#include "common/scm_rev.h" namespace toml { template @@ -763,7 +763,7 @@ void load(const std::filesystem::path& path) { logFilter = toml::find_or(general, "logFilter", ""); logType = toml::find_or(general, "logType", "sync"); userName = toml::find_or(general, "userName", "shadPS4"); - if (Common::isRelease) { + if (Common::g_is_release) { updateChannel = toml::find_or(general, "updateChannel", "Release"); } else { updateChannel = toml::find_or(general, "updateChannel", "Nightly"); @@ -1108,7 +1108,7 @@ void setDefaultValues() { logFilter = ""; logType = "sync"; userName = "shadPS4"; - if (Common::isRelease) { + if (Common::g_is_release) { updateChannel = "Release"; } else { updateChannel = "Nightly"; diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in index 2de04e0be..71c4c2d0a 100644 --- a/src/common/scm_rev.cpp.in +++ b/src/common/scm_rev.cpp.in @@ -3,21 +3,17 @@ #include "common/scm_rev.h" -#define GIT_REV "@GIT_REV@" -#define GIT_BRANCH "@GIT_BRANCH@" -#define GIT_DESC "@GIT_DESC@" -#define GIT_REMOTE_NAME "@GIT_REMOTE_NAME@" -#define GIT_REMOTE_URL "@GIT_REMOTE_URL@" -#define BUILD_DATE "@BUILD_DATE@" - namespace Common { -const char g_scm_rev[] = GIT_REV; -const char g_scm_branch[] = GIT_BRANCH; -const char g_scm_desc[] = GIT_DESC; -const char g_scm_remote_name[] = GIT_REMOTE_NAME; -const char g_scm_remote_url[] = GIT_REMOTE_URL; -const char g_scm_date[] = BUILD_DATE; +constexpr char g_version[] = "@APP_VERSION@"; +constexpr bool g_is_release = @APP_IS_RELEASE@; + +constexpr char g_scm_rev[] = "@GIT_REV@"; +constexpr char g_scm_branch[] = "@GIT_BRANCH@"; +constexpr char g_scm_desc[] = "@GIT_DESC@"; +constexpr char g_scm_remote_name[] = "@GIT_REMOTE_NAME@"; +constexpr char g_scm_remote_url[] = "@GIT_REMOTE_URL@"; +constexpr char g_scm_date[] = "@BUILD_DATE@"; } // namespace diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h index f38efff42..36b844e94 100644 --- a/src/common/scm_rev.h +++ b/src/common/scm_rev.h @@ -5,6 +5,9 @@ namespace Common { +extern const char g_version[]; +extern const bool g_is_release; + extern const char g_scm_rev[]; extern const char g_scm_branch[]; extern const char g_scm_desc[]; diff --git a/src/common/version.h b/src/common/version.h deleted file mode 100644 index 652f36e6d..000000000 --- a/src/common/version.h +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace Common { - -constexpr char VERSION[] = "0.7.1 WIP"; -constexpr bool isRelease = false; - -} // namespace Common diff --git a/src/emulator.cpp b/src/emulator.cpp index 5f94f008a..1a71b99cb 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -22,7 +22,6 @@ #include "common/polyfill_thread.h" #include "common/scm_rev.h" #include "common/singleton.h" -#include "common/version.h" #include "core/file_format/psf.h" #include "core/file_format/trp.h" #include "core/file_sys/fs.h" @@ -123,7 +122,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector", id, title, app_version); std::string window_title = ""; - if (Common::isRelease) { - window_title = fmt::format("shadPS4 v{} | {}", Common::VERSION, game_title); + if (Common::g_is_release) { + window_title = fmt::format("shadPS4 v{} | {}", Common::g_version, game_title); } else { std::string remote_url(Common::g_scm_remote_url); std::string remote_host; @@ -208,10 +207,10 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector #include #include -#include #include "check_update.h" using namespace Common::FS; @@ -52,7 +51,7 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) { url = QUrl("https://api.github.com/repos/shadps4-emu/shadPS4/releases/latest"); checkName = false; } else { - if (Common::isRelease) { + if (Common::g_is_release) { Config::setUpdateChannel("Release"); } else { Config::setUpdateChannel("Nightly"); @@ -162,7 +161,7 @@ tr("The Auto Updater allows up to 60 update checks per hour.\\nYou have reached QString currentRev = (updateChannel == "Nightly") ? QString::fromStdString(Common::g_scm_rev) - : "v." + QString::fromStdString(Common::VERSION); + : "v." + QString::fromStdString(Common::g_version); QString currentDate = Common::g_scm_date; QDateTime dateTime = QDateTime::fromString(latestDate, Qt::ISODate); diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index b5732d0ca..0aff80ccc 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -13,7 +13,7 @@ #include "cheats_patches.h" #include "common/config.h" #include "common/path_util.h" -#include "common/version.h" +#include "common/scm_rev.h" #include "compatibility_info.h" #include "game_info.h" #include "trophy_viewer.h" @@ -115,7 +115,7 @@ public: compatibilityMenu->addAction(updateCompatibility); compatibilityMenu->addAction(viewCompatibilityReport); - if (Common::isRelease) { + if (Common::g_is_release) { compatibilityMenu->addAction(submitCompatibilityReport); } @@ -571,7 +571,7 @@ public: query.addQueryItem("game-name", QString::fromStdString(m_games[itemID].name)); query.addQueryItem("game-serial", QString::fromStdString(m_games[itemID].serial)); query.addQueryItem("game-version", QString::fromStdString(m_games[itemID].version)); - query.addQueryItem("emulator-version", QString(Common::VERSION)); + query.addQueryItem("emulator-version", QString(Common::g_version)); url.setQuery(query); QDesktopServices::openUrl(url); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 072ad70e5..60ab58274 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -18,7 +18,6 @@ #include "common/path_util.h" #include "common/scm_rev.h" #include "common/string_util.h" -#include "common/version.h" #include "control_settings.h" #include "game_install_dialog.h" #include "kbm_gui.h" @@ -58,8 +57,8 @@ bool MainWindow::Init() { // show ui setMinimumSize(720, 405); std::string window_title = ""; - if (Common::isRelease) { - window_title = fmt::format("shadPS4 v{}", Common::VERSION); + if (Common::g_is_release) { + window_title = fmt::format("shadPS4 v{}", Common::g_version); } else { std::string remote_url(Common::g_scm_remote_url); std::string remote_host; @@ -69,10 +68,10 @@ bool MainWindow::Init() { remote_host = "unknown"; } if (remote_host == "shadps4-emu" || remote_url.length() == 0) { - window_title = fmt::format("shadPS4 v{} {} {}", Common::VERSION, Common::g_scm_branch, + window_title = fmt::format("shadPS4 v{} {} {}", Common::g_version, Common::g_scm_branch, Common::g_scm_desc); } else { - window_title = fmt::format("shadPS4 v{} {}/{} {}", Common::VERSION, remote_host, + window_title = fmt::format("shadPS4 v{} {}/{} {}", Common::g_version, remote_host, Common::g_scm_branch, Common::g_scm_desc); } } diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 25c27fef3..5ee802b0c 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -9,7 +9,7 @@ #include #include "common/config.h" -#include "common/version.h" +#include "common/scm_rev.h" #include "qt_gui/compatibility_info.h" #ifdef ENABLE_DISCORD_RPC #include "common/discord_rpc_handler.h" @@ -491,7 +491,7 @@ void SettingsDialog::LoadValuesFromConfig() { QString updateChannel = QString::fromStdString(Config::getUpdateChannel()); ui->updateComboBox->setCurrentText( channelMap.key(updateChannel != "Release" && updateChannel != "Nightly" - ? (Common::isRelease ? "Release" : "Nightly") + ? (Common::g_is_release ? "Release" : "Nightly") : updateChannel)); #endif diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index fcdde7240..e369240c6 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -10,7 +10,6 @@ #include "common/assert.h" #include "common/config.h" #include "common/elf_info.h" -#include "common/version.h" #include "core/debug_state.h" #include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" From b8f6ef1c0b197291091af15713d48ac240fa61a9 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 7 Apr 2025 02:23:04 -0700 Subject: [PATCH 111/194] externals: Update MoltenVK (#2754) --- CMakeLists.txt | 2 +- externals/MoltenVK/MoltenVK | 2 +- externals/MoltenVK/SPIRV-Cross | 2 +- externals/vulkan-headers | 2 +- src/video_core/renderer_vulkan/vk_platform.cpp | 10 ---------- 5 files changed, 4 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 844553340..c5d9f7c9c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,7 +220,7 @@ find_package(SDL3 3.1.2 CONFIG) find_package(stb MODULE) find_package(toml11 4.2.0 CONFIG) find_package(tsl-robin-map 1.3.0 CONFIG) -find_package(VulkanHeaders 1.4.305 CONFIG) +find_package(VulkanHeaders 1.4.309 CONFIG) find_package(VulkanMemoryAllocator 3.1.0 CONFIG) find_package(xbyak 7.07 CONFIG) find_package(xxHash 0.8.2 MODULE) diff --git a/externals/MoltenVK/MoltenVK b/externals/MoltenVK/MoltenVK index 2048427e5..83510e0f3 160000 --- a/externals/MoltenVK/MoltenVK +++ b/externals/MoltenVK/MoltenVK @@ -1 +1 @@ -Subproject commit 2048427e50f9eb20f2b8f98d316ecaee398c9b91 +Subproject commit 83510e0f3835c3c43651dda087305abc42572e17 diff --git a/externals/MoltenVK/SPIRV-Cross b/externals/MoltenVK/SPIRV-Cross index 2c32b6bf8..cb71abe30 160000 --- a/externals/MoltenVK/SPIRV-Cross +++ b/externals/MoltenVK/SPIRV-Cross @@ -1 +1 @@ -Subproject commit 2c32b6bf86f3c4a5539aa1f0bacbd59fe61759cf +Subproject commit cb71abe3063094bf383379b15473d39cb1144120 diff --git a/externals/vulkan-headers b/externals/vulkan-headers index a03d2f6d5..952f776f6 160000 --- a/externals/vulkan-headers +++ b/externals/vulkan-headers @@ -1 +1 @@ -Subproject commit a03d2f6d5753b365d704d58161825890baad0755 +Subproject commit 952f776f6573aafbb62ea717d871cd1d6816c387 diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index 716473377..e656369b2 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -278,7 +278,6 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e vk::Bool32 enable_force_barriers = vk::True; #ifdef __APPLE__ const vk::Bool32 mvk_debug_mode = enable_crash_diagnostic ? vk::True : vk::False; - constexpr vk::Bool32 mvk_use_mtlheap = vk::True; #endif const std::array layer_setings = { @@ -355,15 +354,6 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e .valueCount = 1, .pValues = &mvk_debug_mode, }, - // Use MTLHeap to back device memory, which among other things allows us to - // use VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT via memory aliasing. - vk::LayerSettingEXT{ - .pLayerName = "MoltenVK", - .pSettingName = "MVK_CONFIG_USE_MTLHEAP", - .type = vk::LayerSettingTypeEXT::eBool32, - .valueCount = 1, - .pValues = &mvk_use_mtlheap, - }, #endif }; From 473b66649f6fc4645812526eb21b520f8f88f302 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Mon, 7 Apr 2025 11:25:49 +0200 Subject: [PATCH 112/194] Fix compatibility related issues in the GUI (#2755) Co-authored-by: georgemoralis --- src/qt_gui/gui_context_menus.h | 43 ++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 0aff80ccc..2fd4588d2 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -122,7 +122,8 @@ public: menu.addMenu(compatibilityMenu); compatibilityMenu->setEnabled(Config::getCompatibilityEnabled()); - viewCompatibilityReport->setEnabled(!m_games[itemID].compatibility.url.isEmpty()); + viewCompatibilityReport->setEnabled(m_games[itemID].compatibility.status != + CompatibilityStatus::Unknown); // Show menu. auto selected = menu.exec(global_pos); @@ -557,24 +558,36 @@ public: } if (selected == viewCompatibilityReport) { - if (!m_games[itemID].compatibility.url.isEmpty()) - QDesktopServices::openUrl(QUrl(m_games[itemID].compatibility.url)); + if (m_games[itemID].compatibility.issue_number != "") { + auto url_issues = + "https://github.com/shadps4-emu/shadps4-game-compatibility/issues/"; + QDesktopServices::openUrl( + QUrl(url_issues + m_games[itemID].compatibility.issue_number)); + } } if (selected == submitCompatibilityReport) { - QUrl url = QUrl("https://github.com/shadps4-emu/shadps4-game-compatibility/issues/new"); - QUrlQuery query; - query.addQueryItem("template", QString("game_compatibility.yml")); - query.addQueryItem( - "title", QString("%1 - %2").arg(QString::fromStdString(m_games[itemID].serial), - QString::fromStdString(m_games[itemID].name))); - query.addQueryItem("game-name", QString::fromStdString(m_games[itemID].name)); - query.addQueryItem("game-serial", QString::fromStdString(m_games[itemID].serial)); - query.addQueryItem("game-version", QString::fromStdString(m_games[itemID].version)); - query.addQueryItem("emulator-version", QString(Common::g_version)); - url.setQuery(query); + if (m_games[itemID].compatibility.issue_number == "") { + QUrl url = + QUrl("https://github.com/shadps4-emu/shadps4-game-compatibility/issues/new"); + QUrlQuery query; + query.addQueryItem("template", QString("game_compatibility.yml")); + query.addQueryItem( + "title", QString("%1 - %2").arg(QString::fromStdString(m_games[itemID].serial), + QString::fromStdString(m_games[itemID].name))); + query.addQueryItem("game-name", QString::fromStdString(m_games[itemID].name)); + query.addQueryItem("game-serial", QString::fromStdString(m_games[itemID].serial)); + query.addQueryItem("game-version", QString::fromStdString(m_games[itemID].version)); + query.addQueryItem("emulator-version", QString(Common::g_version)); + url.setQuery(query); - QDesktopServices::openUrl(url); + QDesktopServices::openUrl(url); + } else { + auto url_issues = + "https://github.com/shadps4-emu/shadps4-game-compatibility/issues/"; + QDesktopServices::openUrl( + QUrl(url_issues + m_games[itemID].compatibility.issue_number)); + } } } From 040fd79ef7fa3b47683a98d6cbc301c368ca6807 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 7 Apr 2025 10:57:39 -0700 Subject: [PATCH 113/194] Revert "build: Target same CPU architecture level as PS4. (#2745)" (#2757) * Revert "build: Target same CPU architecture level as PS4. (#2745)" This reverts commit 54b4d7fc788d570c14924d21fbb1f58de0254aad. Causing issues on M1 CPUs for some reason. * build: Update architecture comments and set mtune=generic. --- CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5d9f7c9c..7f3d4468f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,8 +54,9 @@ else() endif() if (ARCHITECTURE STREQUAL "x86_64") - # Target the same x86_64 feature set as the PS4 CPU to match requirements. - add_compile_options(-march=btver2 -mno-sse4a) + # Target Sandy Bridge as a reasonable subset of instructions supported by PS4 and host CPUs. + # Note that the native PS4 architecture 'btver2' has been attempted but causes issues with M1 CPUs. + add_compile_options(-march=sandybridge -mtune=generic) endif() if (APPLE AND ARCHITECTURE STREQUAL "x86_64" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64") From 7fee289b66bcc650e7fd7f116a36e0976ea9cdca Mon Sep 17 00:00:00 2001 From: Missake212 Date: Mon, 7 Apr 2025 19:29:51 +0100 Subject: [PATCH 114/194] Add information about firmware files (sys modules) in the README (#2758) * Update README.md * Update README.md * Update README.md --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 6462c8bd2..0e2248970 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,27 @@ R3 | M | Keyboard and mouse inputs can be customized in the settings menu by clicking the Controller button, and further details and help on controls are also found there. Custom bindings are saved per-game. Inputs support up to three keys per binding, mouse buttons, mouse movement mapped to joystick input, and more. +# Firmware files + +shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console.\ +The following firmware modules are supported and must be placed in shadPS4's `user/sys_modules` folder. + +
+ +| Modules | Modules | Modules | Modules | +|-------------------------|-------------------------|-------------------------|-------------------------| +| libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx | +| libSceJson.sprx | libSceJson2.sprx | libSceLibcInternal.sprx | libSceNgs2.sprx | +| libSceRtc.sprx | libSceUlt.sprx | | | + +
+ +> [!Caution] +> The above modules are required to run the games properly and must be extracted from your PlayStation 4.\ +> **We do not provide any information or support on how to do this**. + + + # Main team - [**georgemoralis**](https://github.com/georgemoralis) From 08731303d857ba0d8684f77e193f2958e7691ccb Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Mon, 7 Apr 2025 22:58:00 +0200 Subject: [PATCH 115/194] Fix incorrect calculation setting the center of the joysticks one value off causing stick drift in games that assume already corrected input values (#2760) --- src/input/input_handler.cpp | 6 +++--- src/input/input_mouse.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index f7c2ee296..3e2d66a6b 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -550,18 +550,18 @@ void ControllerOutput::FinalizeUpdate() { break; case Axis::TriggerLeft: ApplyDeadzone(new_param, lefttrigger_deadzone); - controller->Axis(0, c_axis, GetAxis(0x0, 0x80, *new_param)); + controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); controller->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20); return; case Axis::TriggerRight: ApplyDeadzone(new_param, righttrigger_deadzone); - controller->Axis(0, c_axis, GetAxis(0x0, 0x80, *new_param)); + controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20); return; default: break; } - controller->Axis(0, c_axis, GetAxis(-0x80, 0x80, *new_param * multiplier)); + controller->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier)); } } diff --git a/src/input/input_mouse.cpp b/src/input/input_mouse.cpp index 11feaeebb..c84d14b3f 100644 --- a/src/input/input_mouse.cpp +++ b/src/input/input_mouse.cpp @@ -61,11 +61,11 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { float a_x = cos(angle) * output_speed, a_y = sin(angle) * output_speed; if (d_x != 0 && d_y != 0) { - controller->Axis(0, axis_x, GetAxis(-0x80, 0x80, a_x)); - controller->Axis(0, axis_y, GetAxis(-0x80, 0x80, a_y)); + controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, a_x)); + controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, a_y)); } else { - controller->Axis(0, axis_x, GetAxis(-0x80, 0x80, 0)); - controller->Axis(0, axis_y, GetAxis(-0x80, 0x80, 0)); + controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, 0)); + controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, 0)); } return interval; From 03b1fef3318ce5f972e9d1c3590556e556343345 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 8 Apr 2025 07:22:16 -0700 Subject: [PATCH 116/194] renderer_vulkan: Only update dynamic state when changed. (#2751) --- .../renderer_vulkan/vk_rasterizer.cpp | 139 +++++------ .../renderer_vulkan/vk_rasterizer.h | 6 +- .../renderer_vulkan/vk_scheduler.cpp | 136 +++++++++++ src/video_core/renderer_vulkan/vk_scheduler.h | 219 ++++++++++++++++++ 4 files changed, 414 insertions(+), 86 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index ecb0c0a75..600c205e3 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -946,19 +946,19 @@ void Rasterizer::UnmapMemory(VAddr addr, u64 size) { mapped_ranges -= boost::icl::interval::right_open(addr, addr + size); } -void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { +void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) const { UpdateViewportScissorState(); UpdateDepthStencilState(); - const auto& regs = liverpool->regs; - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.setBlendConstants(®s.blend_constants.red); - if (instance.IsDynamicColorWriteMaskSupported()) { - cmdbuf.setColorWriteMaskEXT(0, pipeline.GetWriteMasks()); - } + auto& dynamic_state = scheduler.GetDynamicState(); + dynamic_state.SetBlendConstants(&liverpool->regs.blend_constants.red); + dynamic_state.SetColorWriteMasks(pipeline.GetWriteMasks()); + + // Commit new dynamic state to the command buffer. + dynamic_state.Commit(instance, scheduler.CommandBuffer()); } -void Rasterizer::UpdateViewportScissorState() { +void Rasterizer::UpdateViewportScissorState() const { const auto& regs = liverpool->regs; const auto combined_scissor_value_tl = [](s16 scr, s16 win, s16 gen, s16 win_offset) { @@ -1071,92 +1071,65 @@ void Rasterizer::UpdateViewportScissorState() { scissors.push_back(empty_scissor); } - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.setViewportWithCountEXT(viewports); - cmdbuf.setScissorWithCountEXT(scissors); + auto& dynamic_state = scheduler.GetDynamicState(); + dynamic_state.SetViewports(viewports); + dynamic_state.SetScissors(scissors); } -void Rasterizer::UpdateDepthStencilState() { - auto& regs = liverpool->regs; - const auto cmdbuf = scheduler.CommandBuffer(); +void Rasterizer::UpdateDepthStencilState() const { + const auto& regs = liverpool->regs; + auto& dynamic_state = scheduler.GetDynamicState(); - bool depth_test = regs.depth_control.depth_enable && regs.depth_buffer.DepthValid(); - cmdbuf.setDepthTestEnableEXT(depth_test); - cmdbuf.setDepthWriteEnableEXT(regs.depth_control.depth_write_enable && - !regs.depth_render_control.depth_clear_enable); - if (depth_test) { - cmdbuf.setDepthCompareOpEXT(LiverpoolToVK::CompareOp(regs.depth_control.depth_func)); + const auto depth_test_enabled = + regs.depth_control.depth_enable && regs.depth_buffer.DepthValid(); + dynamic_state.SetDepthTestEnabled(depth_test_enabled); + if (depth_test_enabled) { + dynamic_state.SetDepthWriteEnabled(regs.depth_control.depth_write_enable && + !regs.depth_render_control.depth_clear_enable); + dynamic_state.SetDepthCompareOp(LiverpoolToVK::CompareOp(regs.depth_control.depth_func)); } - if (instance.IsDepthBoundsSupported()) { - cmdbuf.setDepthBoundsTestEnableEXT(regs.depth_control.depth_bounds_enable); - if (regs.depth_control.depth_bounds_enable) { - cmdbuf.setDepthBounds(regs.depth_bounds_min, regs.depth_bounds_max); - } + const auto depth_bounds_test_enabled = regs.depth_control.depth_bounds_enable; + dynamic_state.SetDepthBoundsTestEnabled(depth_bounds_test_enabled); + if (depth_bounds_test_enabled) { + dynamic_state.SetDepthBounds(regs.depth_bounds_min, regs.depth_bounds_max); } - cmdbuf.setDepthBiasEnableEXT(regs.polygon_control.NeedsBias()); - if (regs.polygon_control.enable_polygon_offset_front) { - cmdbuf.setDepthBias(regs.poly_offset.front_offset, regs.poly_offset.depth_bias, - regs.poly_offset.front_scale / 16.f); - } else if (regs.polygon_control.enable_polygon_offset_back) { - cmdbuf.setDepthBias(regs.poly_offset.back_offset, regs.poly_offset.depth_bias, - regs.poly_offset.back_scale / 16.f); + const auto depth_bias_enabled = regs.polygon_control.NeedsBias(); + if (depth_bias_enabled) { + dynamic_state.SetDepthBias( + regs.polygon_control.enable_polygon_offset_front ? regs.poly_offset.front_offset + : regs.poly_offset.back_offset, + regs.poly_offset.depth_bias, + (regs.polygon_control.enable_polygon_offset_front ? regs.poly_offset.front_scale + : regs.poly_offset.back_scale) / + 16.f); } - cmdbuf.setStencilTestEnableEXT(regs.depth_control.stencil_enable && - regs.depth_buffer.StencilValid()); - if (regs.depth_control.stencil_enable) { - const auto front_fail_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_front); - const auto front_pass_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_front); - const auto front_depth_fail_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_front); - const auto front_compare_op = LiverpoolToVK::CompareOp(regs.depth_control.stencil_ref_func); - if (regs.depth_control.backface_enable) { - const auto back_fail_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_back); - const auto back_pass_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_back); - const auto back_depth_fail_op = - LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_back); - const auto back_compare_op = - LiverpoolToVK::CompareOp(regs.depth_control.stencil_bf_func); - cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFront, front_fail_op, front_pass_op, - front_depth_fail_op, front_compare_op); - cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eBack, back_fail_op, back_pass_op, - back_depth_fail_op, back_compare_op); - } else { - cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFrontAndBack, front_fail_op, - front_pass_op, front_depth_fail_op, front_compare_op); - } + const auto stencil_test_enabled = + regs.depth_control.stencil_enable && regs.depth_buffer.StencilValid(); + dynamic_state.SetStencilTestEnabled(stencil_test_enabled); + if (stencil_test_enabled) { + const StencilOps front_ops{ + .fail_op = LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_front), + .pass_op = LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_front), + .depth_fail_op = LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_front), + .compare_op = LiverpoolToVK::CompareOp(regs.depth_control.stencil_ref_func), + }; + const StencilOps back_ops = regs.depth_control.backface_enable ? StencilOps{ + .fail_op = LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_back), + .pass_op = LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_back), + .depth_fail_op = LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_back), + .compare_op = LiverpoolToVK::CompareOp(regs.depth_control.stencil_bf_func), + } : front_ops; + dynamic_state.SetStencilOps(front_ops, back_ops); const auto front = regs.stencil_ref_front; - const auto back = regs.stencil_ref_back; - if (front.stencil_test_val == back.stencil_test_val) { - cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, - front.stencil_test_val); - } else { - cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFront, front.stencil_test_val); - cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eBack, back.stencil_test_val); - } - - if (front.stencil_write_mask == back.stencil_write_mask) { - cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, - front.stencil_write_mask); - } else { - cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFront, front.stencil_write_mask); - cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eBack, back.stencil_write_mask); - } - - if (front.stencil_mask == back.stencil_mask) { - cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, - front.stencil_mask); - } else { - cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFront, front.stencil_mask); - cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eBack, back.stencil_mask); - } + const auto back = + regs.depth_control.backface_enable ? regs.stencil_ref_back : regs.stencil_ref_front; + dynamic_state.SetStencilReferences(front.stencil_test_val, back.stencil_test_val); + dynamic_state.SetStencilWriteMasks(front.stencil_write_mask, back.stencil_write_mask); + dynamic_state.SetStencilCompareMasks(front.stencil_mask, back.stencil_mask); } } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 8e5d0065b..02c24c7ec 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -75,9 +75,9 @@ private: void DepthStencilCopy(bool is_depth, bool is_stencil); void EliminateFastClear(); - void UpdateDynamicState(const GraphicsPipeline& pipeline); - void UpdateViewportScissorState(); - void UpdateDepthStencilState(); + void UpdateDynamicState(const GraphicsPipeline& pipeline) const; + void UpdateViewportScissorState() const; + void UpdateDepthStencilState() const; bool FilterDraw(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index fd84c54ed..6b872bdaa 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -97,6 +97,9 @@ void Scheduler::AllocateWorkerCommandBuffers() { ASSERT_MSG(begin_result == vk::Result::eSuccess, "Failed to begin command buffer: {}", vk::to_string(begin_result)); + // Invalidate dynamic state so it gets applied to the new command buffer. + dynamic_state.Invalidate(); + #if TRACY_GPU_ENABLED auto* profiler_ctx = instance.GetProfilerContext(); if (profiler_ctx) { @@ -164,4 +167,137 @@ void Scheduler::SubmitExecution(SubmitInfo& info) { } } +void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmdbuf) { + if (dirty_state.viewports) { + dirty_state.viewports = false; + cmdbuf.setViewportWithCountEXT(viewports); + } + if (dirty_state.scissors) { + dirty_state.scissors = false; + cmdbuf.setScissorWithCountEXT(scissors); + } + if (dirty_state.depth_test_enabled) { + dirty_state.depth_test_enabled = false; + cmdbuf.setDepthTestEnableEXT(depth_test_enabled); + } + if (dirty_state.depth_write_enabled) { + dirty_state.depth_write_enabled = false; + // Note that this must be set in a command buffer even if depth test is disabled. + cmdbuf.setDepthWriteEnableEXT(depth_write_enabled); + } + if (depth_test_enabled && dirty_state.depth_compare_op) { + dirty_state.depth_compare_op = false; + cmdbuf.setDepthCompareOpEXT(depth_compare_op); + } + if (dirty_state.depth_bounds_test_enabled) { + dirty_state.depth_bounds_test_enabled = false; + if (instance.IsDepthBoundsSupported()) { + cmdbuf.setDepthBoundsTestEnableEXT(depth_bounds_test_enabled); + } + } + if (depth_bounds_test_enabled && dirty_state.depth_bounds) { + dirty_state.depth_bounds = false; + if (instance.IsDepthBoundsSupported()) { + cmdbuf.setDepthBounds(depth_bounds_min, depth_bounds_max); + } + } + if (dirty_state.depth_bias_enabled) { + dirty_state.depth_bias_enabled = false; + cmdbuf.setDepthBiasEnableEXT(depth_bias_enabled); + } + if (depth_bias_enabled && dirty_state.depth_bias) { + dirty_state.depth_bias = false; + cmdbuf.setDepthBias(depth_bias_constant, depth_bias_clamp, depth_bias_slope); + } + if (dirty_state.stencil_test_enabled) { + dirty_state.stencil_test_enabled = false; + cmdbuf.setStencilTestEnableEXT(stencil_test_enabled); + } + if (stencil_test_enabled) { + if (dirty_state.stencil_front_ops && dirty_state.stencil_back_ops && + stencil_front_ops == stencil_back_ops) { + dirty_state.stencil_front_ops = false; + dirty_state.stencil_back_ops = false; + cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFrontAndBack, + stencil_front_ops.fail_op, stencil_front_ops.pass_op, + stencil_front_ops.depth_fail_op, stencil_front_ops.compare_op); + } else { + if (dirty_state.stencil_front_ops) { + dirty_state.stencil_front_ops = false; + cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFront, stencil_front_ops.fail_op, + stencil_front_ops.pass_op, stencil_front_ops.depth_fail_op, + stencil_front_ops.compare_op); + } + if (dirty_state.stencil_back_ops) { + dirty_state.stencil_back_ops = false; + cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eBack, stencil_back_ops.fail_op, + stencil_back_ops.pass_op, stencil_back_ops.depth_fail_op, + stencil_back_ops.compare_op); + } + } + if (dirty_state.stencil_front_reference && dirty_state.stencil_back_reference && + stencil_front_reference == stencil_back_reference) { + dirty_state.stencil_front_reference = false; + dirty_state.stencil_back_reference = false; + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, + stencil_front_reference); + } else { + if (dirty_state.stencil_front_reference) { + dirty_state.stencil_front_reference = false; + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFront, + stencil_front_reference); + } + if (dirty_state.stencil_back_reference) { + dirty_state.stencil_back_reference = false; + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eBack, stencil_back_reference); + } + } + if (dirty_state.stencil_front_write_mask && dirty_state.stencil_back_write_mask && + stencil_front_write_mask == stencil_back_write_mask) { + dirty_state.stencil_front_write_mask = false; + dirty_state.stencil_back_write_mask = false; + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, + stencil_front_write_mask); + } else { + if (dirty_state.stencil_front_write_mask) { + dirty_state.stencil_front_write_mask = false; + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFront, + stencil_front_write_mask); + } + if (dirty_state.stencil_back_write_mask) { + dirty_state.stencil_back_write_mask = false; + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eBack, stencil_back_write_mask); + } + } + if (dirty_state.stencil_front_compare_mask && dirty_state.stencil_back_compare_mask && + stencil_front_compare_mask == stencil_back_compare_mask) { + dirty_state.stencil_front_compare_mask = false; + dirty_state.stencil_back_compare_mask = false; + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, + stencil_front_compare_mask); + } else { + if (dirty_state.stencil_front_compare_mask) { + dirty_state.stencil_front_compare_mask = false; + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFront, + stencil_front_compare_mask); + } + if (dirty_state.stencil_back_compare_mask) { + dirty_state.stencil_back_compare_mask = false; + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eBack, + stencil_back_compare_mask); + } + } + } + if (dirty_state.blend_constants) { + dirty_state.blend_constants = false; + cmdbuf.setBlendConstants(blend_constants); + } + if (dirty_state.color_write_masks) { + dirty_state.color_write_masks = false; + if (instance.IsDynamicColorWriteMaskSupported()) { + cmdbuf.setColorWriteMaskEXT(0, color_write_masks); + } + } +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index fd5e68373..880bd4b04 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -7,6 +7,7 @@ #include #include "common/types.h" #include "common/unique_function.h" +#include "video_core/amdgpu/liverpool.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/renderer_vulkan/vk_resource_pool.h" @@ -55,6 +56,219 @@ struct SubmitInfo { } }; +using Viewports = boost::container::static_vector; +using Scissors = boost::container::static_vector; +using ColorWriteMasks = std::array; +struct StencilOps { + vk::StencilOp fail_op{}; + vk::StencilOp pass_op{}; + vk::StencilOp depth_fail_op{}; + vk::CompareOp compare_op{}; + + bool operator==(const StencilOps& other) const { + return fail_op == other.fail_op && pass_op == other.pass_op && + depth_fail_op == other.depth_fail_op && compare_op == other.compare_op; + } +}; +struct DynamicState { + struct { + bool viewports : 1; + bool scissors : 1; + + bool depth_test_enabled : 1; + bool depth_write_enabled : 1; + bool depth_compare_op : 1; + + bool depth_bounds_test_enabled : 1; + bool depth_bounds : 1; + + bool depth_bias_enabled : 1; + bool depth_bias : 1; + + bool stencil_test_enabled : 1; + bool stencil_front_ops : 1; + bool stencil_front_reference : 1; + bool stencil_front_write_mask : 1; + bool stencil_front_compare_mask : 1; + bool stencil_back_ops : 1; + bool stencil_back_reference : 1; + bool stencil_back_write_mask : 1; + bool stencil_back_compare_mask : 1; + + bool blend_constants : 1; + bool color_write_masks : 1; + } dirty_state{}; + + Viewports viewports{}; + Scissors scissors{}; + + bool depth_test_enabled{}; + bool depth_write_enabled{}; + vk::CompareOp depth_compare_op{}; + + bool depth_bounds_test_enabled{}; + float depth_bounds_min{}; + float depth_bounds_max{}; + + bool depth_bias_enabled{}; + float depth_bias_constant{}; + float depth_bias_clamp{}; + float depth_bias_slope{}; + + bool stencil_test_enabled{}; + StencilOps stencil_front_ops{}; + u32 stencil_front_reference{}; + u32 stencil_front_write_mask{}; + u32 stencil_front_compare_mask{}; + StencilOps stencil_back_ops{}; + u32 stencil_back_reference{}; + u32 stencil_back_write_mask{}; + u32 stencil_back_compare_mask{}; + + float blend_constants[4]{}; + ColorWriteMasks color_write_masks{}; + + /// Commits the dynamic state to the provided command buffer. + void Commit(const Instance& instance, const vk::CommandBuffer& cmdbuf); + + /// Invalidates all dynamic state to be flushed into the next command buffer. + void Invalidate() { + std::memset(&dirty_state, 0xFF, sizeof(dirty_state)); + } + + void SetViewports(const Viewports& viewports_) { + if (!std::ranges::equal(viewports, viewports_)) { + viewports = viewports_; + dirty_state.viewports = true; + } + } + + void SetScissors(const Scissors& scissors_) { + if (!std::ranges::equal(scissors, scissors_)) { + scissors = scissors_; + dirty_state.scissors = true; + } + } + + void SetDepthTestEnabled(const bool enabled) { + if (depth_test_enabled != enabled) { + depth_test_enabled = enabled; + dirty_state.depth_test_enabled = true; + } + } + + void SetDepthWriteEnabled(const bool enabled) { + if (depth_write_enabled != enabled) { + depth_write_enabled = enabled; + dirty_state.depth_write_enabled = true; + } + } + + void SetDepthCompareOp(const vk::CompareOp compare_op) { + if (depth_compare_op != compare_op) { + depth_compare_op = compare_op; + dirty_state.depth_compare_op = true; + } + } + + void SetDepthBoundsTestEnabled(const bool enabled) { + if (depth_bounds_test_enabled != enabled) { + depth_bounds_test_enabled = enabled; + dirty_state.depth_bounds_test_enabled = true; + } + } + + void SetDepthBounds(const float min, const float max) { + if (depth_bounds_min != min || depth_bounds_max != max) { + depth_bounds_min = min; + depth_bounds_max = max; + dirty_state.depth_bounds = true; + } + } + + void SetDepthBiasEnabled(const bool enabled) { + if (depth_bias_enabled != enabled) { + depth_bias_enabled = enabled; + dirty_state.depth_bias_enabled = true; + } + } + + void SetDepthBias(const float constant, const float clamp, const float slope) { + if (depth_bias_constant != constant || depth_bias_clamp != clamp || + depth_bias_slope != slope) { + depth_bias_constant = constant; + depth_bias_clamp = clamp; + depth_bias_slope = slope; + dirty_state.depth_bias = true; + } + } + + void SetStencilTestEnabled(const bool enabled) { + if (stencil_test_enabled != enabled) { + stencil_test_enabled = enabled; + dirty_state.stencil_test_enabled = true; + } + } + + void SetStencilOps(const StencilOps& front_ops, const StencilOps& back_ops) { + if (stencil_front_ops != front_ops) { + stencil_front_ops = front_ops; + dirty_state.stencil_front_ops = true; + } + if (stencil_back_ops != back_ops) { + stencil_back_ops = back_ops; + dirty_state.stencil_back_ops = true; + } + } + + void SetStencilReferences(const u32 front_reference, const u32 back_reference) { + if (stencil_front_reference != front_reference) { + stencil_front_reference = front_reference; + dirty_state.stencil_front_reference = true; + } + if (stencil_back_reference != back_reference) { + stencil_back_reference = back_reference; + dirty_state.stencil_back_reference = true; + } + } + + void SetStencilWriteMasks(const u32 front_write_mask, const u32 back_write_mask) { + if (stencil_front_write_mask != front_write_mask) { + stencil_front_write_mask = front_write_mask; + dirty_state.stencil_front_write_mask = true; + } + if (stencil_back_write_mask != back_write_mask) { + stencil_back_write_mask = back_write_mask; + dirty_state.stencil_back_write_mask = true; + } + } + + void SetStencilCompareMasks(const u32 front_compare_mask, const u32 back_compare_mask) { + if (stencil_front_compare_mask != front_compare_mask) { + stencil_front_compare_mask = front_compare_mask; + dirty_state.stencil_front_compare_mask = true; + } + if (stencil_back_compare_mask != back_compare_mask) { + stencil_back_compare_mask = back_compare_mask; + dirty_state.stencil_back_compare_mask = true; + } + } + + void SetBlendConstants(const float blend_constants_[4]) { + if (!std::equal(blend_constants, std::end(blend_constants), blend_constants_)) { + std::memcpy(blend_constants, blend_constants_, sizeof(blend_constants)); + dirty_state.blend_constants = true; + } + } + + void SetColorWriteMasks(const ColorWriteMasks& color_write_masks_) { + if (!std::ranges::equal(color_write_masks, color_write_masks_)) { + color_write_masks = color_write_masks_; + dirty_state.color_write_masks = true; + } + } +}; + class Scheduler { public: explicit Scheduler(const Instance& instance); @@ -81,6 +295,10 @@ public: return render_state; } + DynamicState& GetDynamicState() { + return dynamic_state; + } + /// Returns the current command buffer. vk::CommandBuffer CommandBuffer() const { return current_cmdbuf; @@ -125,6 +343,7 @@ private: }; std::queue pending_ops; RenderState render_state; + DynamicState dynamic_state; bool is_rendering = false; tracy::VkCtxScope* profiler_scope{}; }; From 29656563259be924e04dca02e5f2a7c63f27beee Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:54:39 -0700 Subject: [PATCH 117/194] build: Target same CPU architecture level as PS4. (#2763) --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f3d4468f..37492eeb3 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,9 +54,9 @@ else() endif() if (ARCHITECTURE STREQUAL "x86_64") - # Target Sandy Bridge as a reasonable subset of instructions supported by PS4 and host CPUs. - # Note that the native PS4 architecture 'btver2' has been attempted but causes issues with M1 CPUs. - add_compile_options(-march=sandybridge -mtune=generic) + # Target the same CPU architecture as the PS4, to maintain the same level of compatibility. + # Exclude SSE4a as it is only available on AMD CPUs. + add_compile_options(-march=btver2 -mtune=generic -mno-sse4a) endif() if (APPLE AND ARCHITECTURE STREQUAL "x86_64" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64") From e3b1c041d0de657795e8753cea34da8481c61c4c Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:59:33 -0700 Subject: [PATCH 118/194] documents: Update macOS version in quickstart guide. --- documents/Quickstart/Quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documents/Quickstart/Quickstart.md b/documents/Quickstart/Quickstart.md index 9c6bc5a6f..55825ac7d 100644 --- a/documents/Quickstart/Quickstart.md +++ b/documents/Quickstart/Quickstart.md @@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-2.0-or-later - A CPU supporting the following instruction sets: MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, AVX, F16C, CLMUL, AES, BMI1, MOVBE, XSAVE, ABM - **Intel**: Haswell generation or newer - **AMD**: Jaguar generation or newer - - **Apple**: Rosetta 2 on macOS 15 or newer + - **Apple**: Rosetta 2 on macOS 15.4 or newer ### GPU From 5abec2a2917a52cf01f2ef9f5c9e3e2656988383 Mon Sep 17 00:00:00 2001 From: Dmugetsu <168934208+diegolix29@users.noreply.github.com> Date: Wed, 9 Apr 2025 18:06:54 -0600 Subject: [PATCH 119/194] Enabling Depth Bias Explicity (#2766) --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 600c205e3..5aae43cc8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1096,14 +1096,13 @@ void Rasterizer::UpdateDepthStencilState() const { } const auto depth_bias_enabled = regs.polygon_control.NeedsBias(); + dynamic_state.SetDepthBiasEnabled(depth_bias_enabled); if (depth_bias_enabled) { + const bool front = regs.polygon_control.enable_polygon_offset_front; dynamic_state.SetDepthBias( - regs.polygon_control.enable_polygon_offset_front ? regs.poly_offset.front_offset - : regs.poly_offset.back_offset, + front ? regs.poly_offset.front_offset : regs.poly_offset.back_offset, regs.poly_offset.depth_bias, - (regs.polygon_control.enable_polygon_offset_front ? regs.poly_offset.front_scale - : regs.poly_offset.back_scale) / - 16.f); + (front ? regs.poly_offset.front_scale : regs.poly_offset.back_scale) / 16.f); } const auto stencil_test_enabled = From 37d4cd091c0ff3d534380f686b986a5699da8f8b Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 10 Apr 2025 16:20:01 -0700 Subject: [PATCH 120/194] renderer_vulkan: Make some primitive state dynamic. (#2764) * renderer_vulkan: Make some primitive state dynamic. * renderer_vulkan: Silence MoltenVK primitive restart warning spam. --- .../renderer_vulkan/liverpool_to_vk.cpp | 12 ++++++ .../renderer_vulkan/liverpool_to_vk.h | 2 + .../renderer_vulkan/vk_graphics_pipeline.cpp | 41 ++++++++++--------- .../renderer_vulkan/vk_graphics_pipeline.h | 14 ------- src/video_core/renderer_vulkan/vk_instance.h | 5 +++ .../renderer_vulkan/vk_pipeline_cache.cpp | 4 -- .../renderer_vulkan/vk_rasterizer.cpp | 20 +++++++++ .../renderer_vulkan/vk_rasterizer.h | 1 + .../renderer_vulkan/vk_scheduler.cpp | 14 +++++++ src/video_core/renderer_vulkan/vk_scheduler.h | 29 +++++++++++++ 10 files changed, 105 insertions(+), 37 deletions(-) diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 843bedb20..a6ae0c304 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -156,6 +156,18 @@ vk::CullModeFlags CullMode(Liverpool::CullMode mode) { } } +vk::FrontFace FrontFace(Liverpool::FrontFace face) { + switch (face) { + case Liverpool::FrontFace::Clockwise: + return vk::FrontFace::eClockwise; + case Liverpool::FrontFace::CounterClockwise: + return vk::FrontFace::eCounterClockwise; + default: + UNREACHABLE(); + return vk::FrontFace::eClockwise; + } +} + vk::BlendFactor BlendFactor(Liverpool::BlendControl::BlendFactor factor) { using BlendFactor = Liverpool::BlendControl::BlendFactor; switch (factor) { diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.h b/src/video_core/renderer_vulkan/liverpool_to_vk.h index 42da7aa06..fca0a8378 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.h +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.h @@ -26,6 +26,8 @@ vk::PolygonMode PolygonMode(Liverpool::PolygonMode mode); vk::CullModeFlags CullMode(Liverpool::CullMode mode); +vk::FrontFace FrontFace(Liverpool::FrontFace mode); + vk::BlendFactor BlendFactor(Liverpool::BlendControl::BlendFactor factor); vk::BlendOp BlendOp(Liverpool::BlendControl::BlendFunc func); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 7cd4bd872..354e22331 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -28,6 +28,15 @@ static constexpr std::array LogicalStageToStageBit = { vk::ShaderStageFlagBits::eCompute, }; +static bool IsPrimitiveTopologyList(const vk::PrimitiveTopology topology) { + return topology == vk::PrimitiveTopology::ePointList || + topology == vk::PrimitiveTopology::eLineList || + topology == vk::PrimitiveTopology::eTriangleList || + topology == vk::PrimitiveTopology::eLineListWithAdjacency || + topology == vk::PrimitiveTopology::eTriangleListWithAdjacency || + topology == vk::PrimitiveTopology::ePatchList; +} + GraphicsPipeline::GraphicsPipeline( const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap, const Shader::Profile& profile, const GraphicsPipelineKey& key_, @@ -75,19 +84,15 @@ GraphicsPipeline::GraphicsPipeline( .pVertexAttributeDescriptions = vertex_attributes.data(), }; - auto prim_restart = key.enable_primitive_restart != 0; - if (prim_restart && IsPrimitiveListTopology() && !instance.IsListRestartSupported()) { - LOG_DEBUG(Render_Vulkan, - "Primitive restart is enabled for list topology but not supported by driver."); - prim_restart = false; - } + const auto topology = LiverpoolToVK::PrimitiveType(key.prim_type); const vk::PipelineInputAssemblyStateCreateInfo input_assembly = { - .topology = LiverpoolToVK::PrimitiveType(key.prim_type), - .primitiveRestartEnable = prim_restart, + .topology = topology, + // Avoid warning spam on all pipelines about unsupported restart disable, if not supported. + // However, must be false for list topologies to avoid validation errors. + .primitiveRestartEnable = + !instance.IsPrimitiveRestartDisableSupported() && !IsPrimitiveTopologyList(topology), }; - ASSERT_MSG(!prim_restart || key.primitive_restart_index == 0xFFFF || - key.primitive_restart_index == 0xFFFFFFFF, - "Primitive restart index other than -1 is not supported yet"); + const bool is_rect_list = key.prim_type == AmdGpu::PrimitiveType::RectList; const bool is_quad_list = key.prim_type == AmdGpu::PrimitiveType::QuadList; const auto& fs_info = runtime_infos[u32(Shader::LogicalStage::Fragment)].fs_info; @@ -99,12 +104,6 @@ GraphicsPipeline::GraphicsPipeline( .depthClampEnable = false, .rasterizerDiscardEnable = false, .polygonMode = LiverpoolToVK::PolygonMode(key.polygon_mode), - .cullMode = LiverpoolToVK::IsPrimitiveCulled(key.prim_type) - ? LiverpoolToVK::CullMode(key.cull_mode) - : vk::CullModeFlagBits::eNone, - .frontFace = key.front_face == Liverpool::FrontFace::Clockwise - ? vk::FrontFace::eClockwise - : vk::FrontFace::eCounterClockwise, .lineWidth = 1.0f, }; @@ -122,16 +121,20 @@ GraphicsPipeline::GraphicsPipeline( .pNext = instance.IsDepthClipControlSupported() ? &clip_control : nullptr, }; - boost::container::static_vector dynamic_states = { + boost::container::static_vector dynamic_states = { vk::DynamicState::eViewportWithCountEXT, vk::DynamicState::eScissorWithCountEXT, vk::DynamicState::eBlendConstants, vk::DynamicState::eDepthTestEnableEXT, vk::DynamicState::eDepthWriteEnableEXT, vk::DynamicState::eDepthCompareOpEXT, vk::DynamicState::eDepthBiasEnableEXT, vk::DynamicState::eDepthBias, vk::DynamicState::eStencilTestEnableEXT, vk::DynamicState::eStencilReference, vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask, - vk::DynamicState::eStencilOpEXT, + vk::DynamicState::eStencilOpEXT, vk::DynamicState::eCullModeEXT, + vk::DynamicState::eFrontFaceEXT, }; + if (instance.IsPrimitiveRestartDisableSupported()) { + dynamic_states.push_back(vk::DynamicState::ePrimitiveRestartEnableEXT); + } if (instance.IsDepthBoundsSupported()) { dynamic_states.push_back(vk::DynamicState::eDepthBoundsTestEnableEXT); dynamic_states.push_back(vk::DynamicState::eDepthBounds); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 7ffd14064..59230ae46 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -42,11 +42,7 @@ struct GraphicsPipelineKey { u32 num_samples; u32 mrt_mask; AmdGpu::PrimitiveType prim_type; - u32 enable_primitive_restart; - u32 primitive_restart_index; Liverpool::PolygonMode polygon_mode; - Liverpool::CullMode cull_mode; - Liverpool::FrontFace front_face; Liverpool::ClipSpace clip_space; Liverpool::ColorBufferMask cb_shader_mask; std::array blend_controls; @@ -82,16 +78,6 @@ public: return key.mrt_mask; } - [[nodiscard]] bool IsPrimitiveListTopology() const { - return key.prim_type == AmdGpu::PrimitiveType::PointList || - key.prim_type == AmdGpu::PrimitiveType::LineList || - key.prim_type == AmdGpu::PrimitiveType::TriangleList || - key.prim_type == AmdGpu::PrimitiveType::AdjLineList || - key.prim_type == AmdGpu::PrimitiveType::AdjTriangleList || - key.prim_type == AmdGpu::PrimitiveType::RectList || - key.prim_type == AmdGpu::PrimitiveType::QuadList; - } - /// Gets the attributes and bindings for vertex inputs. template void GetVertexInputs(VertexInputs& attributes, VertexInputs& bindings, diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 04b68c1d0..6de419041 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -292,6 +292,11 @@ public: properties.limits.framebufferStencilSampleCounts; } + /// Returns whether disabling primitive restart is supported. + bool IsPrimitiveRestartDisableSupported() const { + return driver_id != vk::DriverId::eMoltenvk; + } + private: /// Creates the logical device opportunistically enabling extensions bool CreateDevice(); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 17a1fdec4..bad2a549c 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -283,12 +283,8 @@ bool PipelineCache::RefreshGraphicsKey() { } key.prim_type = regs.primitive_type; - key.enable_primitive_restart = regs.enable_primitive_restart & 1; - key.primitive_restart_index = regs.primitive_restart_index; key.polygon_mode = regs.polygon_control.PolyMode(); - key.cull_mode = regs.polygon_control.CullingMode(); key.clip_space = regs.clipper_control.clip_space; - key.front_face = regs.polygon_control.front_face; key.num_samples = regs.NumSamples(); const bool skip_cb_binding = diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 5aae43cc8..30102960a 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -949,6 +949,7 @@ void Rasterizer::UnmapMemory(VAddr addr, u64 size) { void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) const { UpdateViewportScissorState(); UpdateDepthStencilState(); + UpdatePrimitiveState(); auto& dynamic_state = scheduler.GetDynamicState(); dynamic_state.SetBlendConstants(&liverpool->regs.blend_constants.red); @@ -1132,6 +1133,25 @@ void Rasterizer::UpdateDepthStencilState() const { } } +void Rasterizer::UpdatePrimitiveState() const { + const auto& regs = liverpool->regs; + auto& dynamic_state = scheduler.GetDynamicState(); + + const auto prim_restart = (regs.enable_primitive_restart & 1) != 0; + ASSERT_MSG(!prim_restart || regs.primitive_restart_index == 0xFFFF || + regs.primitive_restart_index == 0xFFFFFFFF, + "Primitive restart index other than -1 is not supported yet"); + + const auto cull_mode = LiverpoolToVK::IsPrimitiveCulled(regs.primitive_type) + ? LiverpoolToVK::CullMode(regs.polygon_control.CullingMode()) + : vk::CullModeFlagBits::eNone; + const auto front_face = LiverpoolToVK::FrontFace(regs.polygon_control.front_face); + + dynamic_state.SetPrimitiveRestartEnabled(prim_restart); + dynamic_state.SetCullMode(cull_mode); + dynamic_state.SetFrontFace(front_face); +} + void Rasterizer::ScopeMarkerBegin(const std::string_view& str, bool from_guest) { if ((from_guest && !Config::getVkGuestMarkersEnabled()) || (!from_guest && !Config::getVkHostMarkersEnabled())) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 02c24c7ec..54bf3d253 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -78,6 +78,7 @@ private: void UpdateDynamicState(const GraphicsPipeline& pipeline) const; void UpdateViewportScissorState() const; void UpdateDepthStencilState() const; + void UpdatePrimitiveState() const; bool FilterDraw(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 6b872bdaa..a48d93dee 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -288,6 +288,20 @@ void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmd } } } + if (dirty_state.primitive_restart_enable) { + dirty_state.primitive_restart_enable = false; + if (instance.IsPrimitiveRestartDisableSupported()) { + cmdbuf.setPrimitiveRestartEnableEXT(primitive_restart_enable); + } + } + if (dirty_state.cull_mode) { + dirty_state.cull_mode = false; + cmdbuf.setCullModeEXT(cull_mode); + } + if (dirty_state.front_face) { + dirty_state.front_face = false; + cmdbuf.setFrontFaceEXT(front_face); + } if (dirty_state.blend_constants) { dirty_state.blend_constants = false; cmdbuf.setBlendConstants(blend_constants); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 880bd4b04..7709e1d41 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -95,6 +95,10 @@ struct DynamicState { bool stencil_back_write_mask : 1; bool stencil_back_compare_mask : 1; + bool primitive_restart_enable : 1; + bool cull_mode : 1; + bool front_face : 1; + bool blend_constants : 1; bool color_write_masks : 1; } dirty_state{}; @@ -125,6 +129,10 @@ struct DynamicState { u32 stencil_back_write_mask{}; u32 stencil_back_compare_mask{}; + bool primitive_restart_enable{}; + vk::CullModeFlags cull_mode{}; + vk::FrontFace front_face{}; + float blend_constants[4]{}; ColorWriteMasks color_write_masks{}; @@ -254,6 +262,27 @@ struct DynamicState { } } + void SetPrimitiveRestartEnabled(const bool enabled) { + if (primitive_restart_enable != enabled) { + primitive_restart_enable = enabled; + dirty_state.primitive_restart_enable = true; + } + } + + void SetCullMode(const vk::CullModeFlags cull_mode_) { + if (cull_mode != cull_mode_) { + cull_mode = cull_mode_; + dirty_state.cull_mode = true; + } + } + + void SetFrontFace(const vk::FrontFace front_face_) { + if (front_face != front_face_) { + front_face = front_face_; + dirty_state.front_face = true; + } + } + void SetBlendConstants(const float blend_constants_[4]) { if (!std::equal(blend_constants, std::end(blend_constants), blend_constants_)) { std::memcpy(blend_constants, blend_constants_, sizeof(blend_constants)); From 6ebed7ce69a54264454c122a1f6f0d84f2fb800a Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 12 Apr 2025 18:01:39 +0300 Subject: [PATCH 121/194] update submodules (#2773) --- externals/MoltenVK/SPIRV-Cross | 2 +- externals/MoltenVK/cereal | 2 +- externals/date | 2 +- externals/ffmpeg-core | 2 +- externals/fmt | 2 +- externals/glslang | 2 +- externals/libpng | 2 +- externals/libusb | 2 +- externals/magic_enum | 2 +- externals/pugixml | 2 +- externals/robin-map | 2 +- externals/sdl3 | 2 +- externals/toml11 | 2 +- externals/vma | 2 +- externals/vulkan-headers | 2 +- externals/winpthreads | 2 +- externals/xbyak | 2 +- externals/xxhash | 2 +- externals/zlib-ng | 2 +- externals/zydis | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/externals/MoltenVK/SPIRV-Cross b/externals/MoltenVK/SPIRV-Cross index cb71abe30..68300dc07 160000 --- a/externals/MoltenVK/SPIRV-Cross +++ b/externals/MoltenVK/SPIRV-Cross @@ -1 +1 @@ -Subproject commit cb71abe3063094bf383379b15473d39cb1144120 +Subproject commit 68300dc07ac3dc592dbbdb87e02d5180f984ad12 diff --git a/externals/MoltenVK/cereal b/externals/MoltenVK/cereal index d1fcec807..a56bad8bb 160000 --- a/externals/MoltenVK/cereal +++ b/externals/MoltenVK/cereal @@ -1 +1 @@ -Subproject commit d1fcec807b372f04e4c1041b3058e11c12853e6e +Subproject commit a56bad8bbb770ee266e930c95d37fff2a5be7fea diff --git a/externals/date b/externals/date index 28b7b2325..a45ea7c17 160000 --- a/externals/date +++ b/externals/date @@ -1 +1 @@ -Subproject commit 28b7b232521ace2c8ef3f2ad4126daec3569c14f +Subproject commit a45ea7c17b4a7f320e199b71436074bd624c9e15 diff --git a/externals/ffmpeg-core b/externals/ffmpeg-core index 27de97c82..42557a704 160000 --- a/externals/ffmpeg-core +++ b/externals/ffmpeg-core @@ -1 +1 @@ -Subproject commit 27de97c826b6b40c255891c37ac046a25836a575 +Subproject commit 42557a704720d1b7d85c03bff0c2d369a61848da diff --git a/externals/fmt b/externals/fmt index 8ee89546f..64db979e3 160000 --- a/externals/fmt +++ b/externals/fmt @@ -1 +1 @@ -Subproject commit 8ee89546ffcf046309d1f0d38c0393f02fde56c8 +Subproject commit 64db979e38ec644b1798e41610b28c8d2c8a2739 diff --git a/externals/glslang b/externals/glslang index a0995c49e..ba1640446 160000 --- a/externals/glslang +++ b/externals/glslang @@ -1 +1 @@ -Subproject commit a0995c49ebcaca2c6d3b03efbabf74f3843decdb +Subproject commit ba1640446f3826a518721d1f083f3a8cca1120c3 diff --git a/externals/libpng b/externals/libpng index c1cc0f3f4..34005e3d3 160000 --- a/externals/libpng +++ b/externals/libpng @@ -1 +1 @@ -Subproject commit c1cc0f3f4c3d4abd11ca68c59446a29ff6f95003 +Subproject commit 34005e3d3d373c0c36898cc55eae48a79c8238a1 diff --git a/externals/libusb b/externals/libusb index 8f0b4a38f..a63a7e43e 160000 --- a/externals/libusb +++ b/externals/libusb @@ -1 +1 @@ -Subproject commit 8f0b4a38fc3eefa2b26a99dff89e1c12bf37afd4 +Subproject commit a63a7e43e0950a595cf4b98a0eaf4051749ace5f diff --git a/externals/magic_enum b/externals/magic_enum index 1a1824df7..a413fcc9c 160000 --- a/externals/magic_enum +++ b/externals/magic_enum @@ -1 +1 @@ -Subproject commit 1a1824df7ac798177a521eed952720681b0bf482 +Subproject commit a413fcc9c46a020a746907136a384c227f3cd095 diff --git a/externals/pugixml b/externals/pugixml index 4bc14418d..caade5a28 160000 --- a/externals/pugixml +++ b/externals/pugixml @@ -1 +1 @@ -Subproject commit 4bc14418d12d289dd9978fdce9490a45deeb653e +Subproject commit caade5a28aad86b92a4b5337a9dc70c4ba73c5eb diff --git a/externals/robin-map b/externals/robin-map index fe845fd78..4ec1bf19c 160000 --- a/externals/robin-map +++ b/externals/robin-map @@ -1 +1 @@ -Subproject commit fe845fd7852ef541c5479ae23b3d36b57f8608ee +Subproject commit 4ec1bf19c6a96125ea22062f38c2cf5b958e448e diff --git a/externals/sdl3 b/externals/sdl3 index a336b62d8..4093e4a19 160000 --- a/externals/sdl3 +++ b/externals/sdl3 @@ -1 +1 @@ -Subproject commit a336b62d8b0b97b09214e053203e442e2b6e2be5 +Subproject commit 4093e4a193971ef1d4928158e0a1832be42e4599 diff --git a/externals/toml11 b/externals/toml11 index 7f6c574ff..a01fe3b4c 160000 --- a/externals/toml11 +++ b/externals/toml11 @@ -1 +1 @@ -Subproject commit 7f6c574ff5aa1053534e7e19c0a4f22bf4c6aaca +Subproject commit a01fe3b4c14c6d7b99ee3f07c9e80058c6403097 diff --git a/externals/vma b/externals/vma index 5a53a1989..f378e7b3f 160000 --- a/externals/vma +++ b/externals/vma @@ -1 +1 @@ -Subproject commit 5a53a198945ba8260fbc58fadb788745ce6aa263 +Subproject commit f378e7b3f18f6e2b06b957f6ba7b1c7207d2a536 diff --git a/externals/vulkan-headers b/externals/vulkan-headers index 952f776f6..5ceb9ed48 160000 --- a/externals/vulkan-headers +++ b/externals/vulkan-headers @@ -1 +1 @@ -Subproject commit 952f776f6573aafbb62ea717d871cd1d6816c387 +Subproject commit 5ceb9ed481e58e705d0d9b5326537daedd06b97d diff --git a/externals/winpthreads b/externals/winpthreads index f00c973a6..f35b0948d 160000 --- a/externals/winpthreads +++ b/externals/winpthreads @@ -1 +1 @@ -Subproject commit f00c973a6ab2a23573708568b8ef4acc20a9d36b +Subproject commit f35b0948d36a736e6a2d052ae295a3ffde09703f diff --git a/externals/xbyak b/externals/xbyak index 4e44f4614..44a72f369 160000 --- a/externals/xbyak +++ b/externals/xbyak @@ -1 +1 @@ -Subproject commit 4e44f4614ddbf038f2a6296f5b906d5c72691e0f +Subproject commit 44a72f369268f7d552650891b296693e91db86bb diff --git a/externals/xxhash b/externals/xxhash index 2bf8313b9..953a09abc 160000 --- a/externals/xxhash +++ b/externals/xxhash @@ -1 +1 @@ -Subproject commit 2bf8313b934633b2a5b7e8fd239645b85e10c852 +Subproject commit 953a09abc39096da9e216b6eb0002c681cdc1199 diff --git a/externals/zlib-ng b/externals/zlib-ng index d54e3769b..fd0d263ce 160000 --- a/externals/zlib-ng +++ b/externals/zlib-ng @@ -1 +1 @@ -Subproject commit d54e3769be0c522015b784eca2af258b1c026107 +Subproject commit fd0d263cedab1a136f40d65199987e3eaeecfcbd diff --git a/externals/zydis b/externals/zydis index bffbb610c..120e0e705 160000 --- a/externals/zydis +++ b/externals/zydis @@ -1 +1 @@ -Subproject commit bffbb610cfea643b98e87658b9058382f7522807 +Subproject commit 120e0e705f8e3b507dc49377ac2879979f0d545c From b0fe1532f706e3cbf8f2f431836e71a902684d85 Mon Sep 17 00:00:00 2001 From: JohnLogostini <55333109+johnlogostini@users.noreply.github.com> Date: Sat, 12 Apr 2025 13:09:21 -0300 Subject: [PATCH 122/194] Icon Updates (#2768) * Updated the trophy icons to match those on the PS4 * Updated the Windows icon to have less harsh aliasing at smaller sizes. * Created a correctly sized SVG client icon. * Remade all of the link icons. Remade all of the icons for links to a standard 1K size and fixed all of the crazy aliasing problems. * Revert "Updated the trophy icons to match those on the PS4" This reverts commit 223c70de132276857393ad372ea2d02329d06bab. * Created SVG versions of all of the link icons. * Update REUSE.toml --------- Co-authored-by: georgemoralis --- REUSE.toml | 6 +++ src/images/discord.png | Bin 68549 -> 43672 bytes src/images/discord.svg | 24 ++++++++++ src/images/github.png | Bin 116428 -> 43288 bytes src/images/github.svg | 22 +++++++++ src/images/ko-fi.png | Bin 39500 -> 40174 bytes src/images/ko-fi.svg | 22 +++++++++ src/images/shadps4.ico | Bin 74814 -> 410598 bytes src/images/shadps4.svg | 105 +++++++++++++++++++++++++++++++++++++++++ src/images/website.png | Bin 90853 -> 73633 bytes src/images/website.svg | 22 +++++++++ src/images/youtube.png | Bin 39404 -> 33075 bytes src/images/youtube.svg | 28 +++++++++++ 13 files changed, 229 insertions(+) create mode 100644 src/images/discord.svg create mode 100644 src/images/github.svg create mode 100644 src/images/ko-fi.svg create mode 100644 src/images/shadps4.svg create mode 100644 src/images/website.svg create mode 100644 src/images/youtube.svg diff --git a/REUSE.toml b/REUSE.toml index ad2bc3678..d17594e4d 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -62,6 +62,12 @@ path = [ "src/images/update_icon.png", "src/images/youtube.png", "src/images/website.png", + "src/images/discord.svg", + "src/images/github.svg", + "src/images/ko-fi.svg", + "src/images/shadps4.svg", + "src/images/website.svg", + "src/images/youtube.svg", "src/shadps4.qrc", "src/shadps4.rc", "src/qt_gui/translations/update_translation.sh", diff --git a/src/images/discord.png b/src/images/discord.png index 2fa455fd10a5cd8ae4df56f2f35e642b4c0d8b38..9858f217ee8f81b93ffce550798b0447620a8ea1 100644 GIT binary patch literal 43672 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
WuRsi#XBbUGnn|v&DT9 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 N002ovPDHLkV1j2Ep;rI^ literal 68549 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 + + + diff --git a/src/images/github.png b/src/images/github.png index 22b1017982f887fa2001d1cdf9c9e5e85385e0df..7b61598cc9032c85442adc48ebd36e1eb1e7248d 100644 GIT binary patch literal 43288 zcmX_o2RzjO|M>fkvsbdo$Ppow%y3pYGO{bPuu4kC ziI9>JlKFqW@ALirKaWR`PrY96*X#A%n?!5NL!4|PYzTsI5>1V55QGu_%80Nq!T+Lm zD8wQNCeFvm$eL(mB4(U{X8q|E+i^;VJx00>d)it`D{@YMW$3}ttUEzBVgmC1wmjEwB~$lwpZ>i3 z_tCj(OJr-Ge{t~oGg*~2;>E&3lN55(Ioh`ug&miu-??}qdu5Ey{}zjNnK_y!?H=TEo?o$!(j z@d-L5d5CCX?HJ7^h#-;((b&K)bl}%eSh)RC_SU>3FBb2Q!@Uw9#K!!(@=NX;6PrEb z>lhCc9_M2jO%5|tb~<*)fBKfMlt0&gR=D>H`>_WsSJ`%6y^?0ZBNt#-sq=h?0E&fyP4nYeqfVV)H|@f!XvMwcQ@k&$73(a**6^w zsxBa36&~!g!VoFn6yCGoiP|J)1}(x3(ta+7LbIPFS}M)6nUrdEBA%*FQkXND3otKU z3aip%|1?3{I!M7EMNBDnGj}jb(E?4U#VfpTI&k$U%wMf9$YWV~+PjK(l3@N>NXs>+ z;Mox(g*>J7_Z<0CQ=7w02j8mGqPB7~7Y@EB#AaL9k>C^A-iLjnPa1Nj2+?UV>Y2OQ}39a z+28Y$4NrWGq~hfh1{ijbr4=458*6;L@$H1@oL*H~?=j)^eME{Pf+r4eU&dsUI|*%O z3U5!PQ`w^VZ+_ZK($ce(q#sXwLFhLtH?I;APNuXA(CSIw9gmqDl&0^=6fzM)XeBhy zYSOIiXWnr zWI{i!ZT%p6v#EPTS!hK@=9Wfuyn*nX_FR6NDIKv(AGm~gDmDqs(rJb#PgpZ5vV1x% z{olm{m^N%1<4=F-G)gBkb)8i2EhUN4yy#=Mh>(-dGYww(?{(aD$9(>^lj)F92#i8@ zK1(XR#E~k-Oh+Gnp+x%nt9%WAtbcgb-elumz1YbH)T*r4!n{~>EpuZ4#E_l(C*b|Gh<+T)Eyca||KFMD^Z z@zM!xZ?VUpO$G0?O*$<-kBuxCv&2oh0chgL_#c;p855Xdw-Q^Wh2iqW2MN#skcX>Rjk`MRp&!SGK8eOnHk@Y+k>ljzph~j&GGh36TEI zXv?Q*`Ydb=?xo!(9c7|F67On7()8Ev?$gBo(D}~rgnWNq6+^#@=LcifHjd5uBqPf5 z+v5h?=J=`xHSrx-0)^K?fn=<*BH<@Su~Ty8x=P*^o&4V;cDB
lIKMXX&)t2O3tTvib2`%nAF+h(?-0s{Nj!%}_TRF0@X2Y1*)Dh&E z%^QtJ@;IAlq}iTj9H;oW$LvAO71hY1OzR-BgyJ}a4+y4My5*p~;eqwwuiUq23IqZVu3-|l^z z1t3bb5FXf^^-*Ek`di8LS@<4C`{=F&O)NAqpP1LboZ;g>0`Wd^AVLBTU~$2-#R|)1HH^F&G0N?H_4Lk;BNYsMz2tzLQ2BG(5_~@f zN`+1FNTN8Q>Pdx?0pw~<5EMtH$MA(I*E3CG=`t%q9^MU7M`_kuHO{G`g1Os#~{0ZW*?QxnUdWQ0EK5Ys;MOhhwm+Y0ny_ z?HLL1uap;hI^ZXP8=iVSoPA5 z?gF+;(Ii3>i%u2;Zg2H~0(->#qW|kLC*Ixa-RVZokyMHt$)=21gwyQqZX)^S|vFktD7;rRy6V|Tyovb#n0DLtefL)3f6c_OZ9lp**_&h=B_(C zaeN-iny_f;4aQdMI(#hbpmZpnNGMIbl1o>_fKvoL2*&;cdlidtu7!uA;c^ z;)3V*fHhEn&Pe#IJO!TkxjmRPFo{{ECVVp%wH9yD)BZnF?C3EdA(Ox!YTy76foD9>1(8ONSF#;^zG$4J=H&FmN1fQY39*Adf(o&OtR?!USar*V= zl$r5XBQ8}7#56+oZZAq__;d!2Rm*3W!FiQrhnpf$c}&sjYH!QHP2$+ron)yrX}>n- zCl>7%t{$ef;=A)6{Wf%95GS#=sg`9>yA?h?BVo<$J5zL+C1$%syTaloj#5se?SP%D#BHP`S>%p(pj_Y)(@C zz}J1seZM9VN_aoAh8&4M3fag8EmZA#jJHVsme4Hh$8bYqHL)>nC4(d?fw(FV_W}J@ zJr7iIb4=So*E$Wx3+YIH2v2bDegtkFvpy&~D{~`WcDaVwc9+n-K)MWV7d6{@!_b%S zu@{}Xe%+W2&|w)2JGI_vVh*8AF-3r?>BIUB8_$;^S~PN1Kzq($-|=G)EBlUsiv-ew zF+Xxj1c=%-*ir(B9NEUV-jRea_=&1M9j`PSl&A3XW2nuA{rGWPA zWK-q4wQtZ-q8Ug1IN$A3(kD#*qzYBzO-v?LwPo1c4cSP9O6s&AO2-6L8WaTm+#46u zdyt-Q2VbY8Y(*3N)XIMyx;kjfr)Zlhm`Y|<{^XY>FenIR#TKwF>Zn)6ME9-AGxPnK z8Tx4@KdgiPwPO9{5(dTRZwM!8#kqB;w!@~?l>Z)loek=WbNZf+iAyQ@BD@Zmt{c~a z_xePN=$IlsxL=#lh@y7?j%Qu5jm>@ZpLB8{tUnuUvaq!%62ot z*z&>4_6Wz_Ch3rFd>(*Cq&((Ylm-6xL+mTxwgMUqwK2JwS$dX(zEgv*BU0gXt$(g} zbkOQ^;85jWDFbCGUYac(7#Ybi)PZgkjbiYEVL(eupv!07^ca)AXR+s8v_Ad*T+5gr z+N>WOlQCN%5+C=Hu~`@>0*3?#qw|?ltN6of@!?N3NM)^Xg}fegZ&ppAZA{x+WMah6 z8}5-r`vUZ0L-gXS_)$j{>oKAa)JrhL`xNxr5bb9d_Xmd5IesHowV^KwEMrc$O`va< zDQ?uK3Y_WqGu@Sf)sqHdNBLeyxm#lweqvA@d?P#=raiZN$~=@^bAF2pnyY;X!&+Ex z&v~HS$6z%FJzYAnW%(FY&F(tq@dn&!9Vc1>F}A$x#xjgg2->o$vl^S2w88+B3td8cAlIYJ@_(qzgvK(4nRfwg(U*$07yar($S}8*is`8=B zKcZV+9CUu+TvCGszYPCF;r{)HYhGUB+>mdBQZz$k{O)8qO0v_oF66RJ%NrdvjbYv@>Gn(rsc-R+^PlkjLjMI|$S zqU<&cr_tPqn;}IeTFLaJt~X|i`>kktT8nkd9o(FmW$;HK^o}y#x-5nNn+Xa&&C?fWC&1grN9*PI+SZu_ThK* zaVKeefM2Se5PiAmyKyG@3IE^;3!!P_fl5X9W>40Xj$#3mLfq$+$ta|iG)5=NVsx^MPo$>f6y9Zs$Bd4VN2EtE-tXw0Bz ziLU!80D&T+BmBv*-}X%vXr}F)r%_r0W;7?__A)+^u8nJ89oG0gu(cJ9?uY8Jpddzu zPy4gLX0w@1t9g zOBUn%Vk#WGE_%H~TC5!P)jwisDIS;!BAUwxLWysD`ut1w(5OqBwG|LXiChK`uKm62 zK9N~ZX1b!4vJg~#{l&Y{Vs^8~Tx)hqzh&=3Bc&V=6L~NasOy&;jP`xL0w{i9){$XW z1j2JmA|~Q4{`Oo`e;n|#ec3U zCJp^jH`Lkb5qEo6yR^3}c7_y~0w`{;$Nc)y=$;UY!lVv&FL(82JCXPqs?<{bvHAI| z_;vUCgMX4g44$p6t%?QyYOC9qJ3IP!nW$5{e*Gh-*&*~J5Ma(G_XTX^4tY^z8)t9x zAXz%pI3HDWpsF9NArV43SQpvD?)o@YL&EC}4XP&G{~%vBQ>1k{M9DfxkZ48CYGpe3 zIub+Z^CZT*M%CMiL2+|Z6=WfA{P(&Ra=KF^e3n%)9K#4dhx?@KC}Os7`!s{tj%QQG z$*Q66PcF|3Bj5e?ib8QEE&k;*Vw}?6Xz+G1v5wt0|KeKEN88vOnp2?{NShyYHGvhh)AG_+w!y9Gk>JbwPQ$DB8N8%b1{lTm zJaZ<-v&S+v$iTo1Fy*-ywUD24i(X`Ta5++iCryUuY+3*hWQTr}s?-6EX0|Udq(t~f zh4~*c|8h*4vbI@AGTsRnw&6rXm zP^v?|%RD@Go*3J$6$_)02M$qW34qxx{)&nyKA*h!O#+8Xt;5(cRUrJJS_&A8x=J2lWy>27 zUimVNtAcU}aCC0pz8PuG4%i9dUQOQ5u@cGuU3pY}BcUCUjIVD7BT9sGHG-QKF*ImT zSLYQtsM7p!g)ipF9`C3)Oe53GX#K$z`x(()FnR6r@`v|UGX3*oHeZcC%i19RG|XJ< zY5n7uYhstb+rU)ex62Y`*@VAn)JMp9K6IaunP1=zPDm($H4_(b)!e^8A05+lSlgH zl){f+dDhmbNdZ=;2(xQ)6qM@EdggK*?soJT4wHr~!Ed-}E|X78lOZawKV>+N9`iHh`hRQsu*%fF`x9|xDb`nyys z+s7C7g39-95A(gxLlR2-EY1O`{A#`^Lh^<8^M99Vc^-nI3<7l=)BdRP+5W1a=JWda zJ&|G95B1**FbXYarUP&EaG2zq@vs!sk;mP7osED7ZD1OftGrH8+383W^J1&Rlw!#$ z5rYHy{JddMB^2zskt+Bmd#Z<_L7LBoYjet2^4Cd!HwQ?8ga0 zu8+Z{wiHLaKH=w1Fr|Zjk#hn`gJgji|0q~lhZZ_~>({^duB6+4Jz08Q6;&&bOH6jz zLvb@CINH@2#$Qr%LEH$aacw-L113joR&J6Q=~yn39_pd)6>U0dm=SIuu-@cF4*mk2 zToE#)Iz4MbGQe(L+VDzJVTW{+Zp4xTp8v6C+y~Fw;~sG5##8U*OU27T!|`}4N~p)X zPJ>pm^_SP18pCXj%z(mJXt_kxl4n{#ELT5CS7q4-FJ~mNDi)vJKIzebK2A}HvA4bk zJ1S%u9u$X_Z=fm#?D{SAYSUtY07V5ySfXwoqDNmYkFxT~tU;3X>E2I19dsd)HAacG z+YCnY(?mZ+e?t^RVC50L(yD^)m!Z8$*ZmR0q(Sf#L)`r5iYrud`)#MM+ba(=u~jc@ zZbd;P%F4`u&G=-QmR85(yLAB~&SW*6BJXnqwuNJL;vc|OLH2%Nf=+-ll&yL<&~0-- zAa4X-oL-l+$7fvD?R=B=XRf%Kw}{wvisw{=I0(3;ZIjZ=`H7hG<)bs~y@?)cxJ7Xq ziZ_}&Q?N}zRysVopu50Kk#|hdfJ6D1IZ>4ln%!CD-Z6IYsj2m96M{$CX z+V!$|S3R!bj7ZhoKDv3&B7*Q#KL17cbuY-F;fUsJLdYgjTP^;afYVk3a-%TB);KZzDtEdyS&ccQhC%-Z0@Xox)j1 zn+;!T2^vVx`fCOdgu(~ZbiwO=Tsk_dv&Itm4H}Vm_p2V0s4H8w@1&y+1|hFI1-=<_ zHscsKt&sWwjK_K1{{|HYTApxuI8DuS+_`S>yB1g;-HCYph_p8wHoGXj2WNlfdziEWh&e6GKQ_6GXkk+k8lP z31Wh`(wuLhjKkKY`Q7X$u#|n0Dh=jX!)Sjl7hp_4zF@2amK^u?PR3SQbv2Bi-}A^0 z7~Egbj2nQkJ%v%%jyD;!19I^{dk1X_Jm_zCzjE)^;;xlt4PQxvh8`~yeK;bLbC2A70u3yp z(Gr{i-3+O3@%(R6KYD2w?qQ*`6SJy&ow8+trLy@7>$B@A^~R|?7nkED+3RZ+YeK@d zjlIcVrzh;qdh}8>7_%F+{{~$L5MdQaJg=1o6n}?mC3KRtT!~a}y&!IUXY~JkVP!Rb z%+tJUc87^9wKb*xH&mY!hgP^&!sy4{sI-7O@DPCAdv)jbOZc@WjIfZtuc9%6qvaZPRL=Umx2$ZF7qhRPF#(I}iwo zS58t){nTABU41|?V7Ajedj}wM#GDqFqdpPHl}2z(sC>9l=vtx({I^K0pUiUCu%M04 zxxhi@?pz;D&5aE8pCu|g_v}8}u3*K`B(Pd-swb94L)$(<@vhMU@tk}C+(GkYoaSDA)=pKS{uOvt)m?D`CZ8c2THRR5XgDiD_ndP@P6@uGeHvB7eMev=Q zXxI{&eoU8gwxkVeNfZ!(O;3(BDu8i2U)9^DpRz9r*jTdPWh?*D1S#Re^i@nNvsqm% zBiMlhL{`P@>^!MZFPGpPw*~Yf!whSc-~7B)f$`*4u?PgV^Wnwzg5w z1r=_q<@R|qMqw`~Xua67s`BF7NAi_!A+ZJZkbu`)*Z(l3OV7+`eA@;AUKsS#Y0Fqc zL8Sp;WgQQ|D)Cm*QebuK-+uSc!G|p{$;Ja%bD;KL6SAKtyrG9aq;{Fb6zjPYBFbA* z#SMTHX+1X$0CO7!-w3cylQt&ktAEq_MGP z%THlwuQFIu?l!-(L7+NHexy(@L;Zyt_j^{9`}qaZJaV|d$RQ#kC{@2|m&nq4Vf`+t z79Hg#0bhuf zCz4$242CJuft}wA|GX-*YufdDUjj93AT0fqE?5}v<>v-u6V%7QZv{lDbDz~pN`*jY zxbUU^fyZk{8f#IsIWIVV9y^`;Q)*+J?rv7B)Gr-5$8P~l?alFAQpl?-ZY|h{HSQgO5cC)SN(*x}x>?^z3SRkhjlqfMA=epuJ4 z@{bIsPG|B!6^cfKSXQ5huN~J(=v4eUqn-YQSpUTI3b$ZE`7SUWC{)K?&ui@=55qAe zV0&~{3QW1U^)<< z+2XSLC^Tq6AW|q2_()0DKQCFp*F61lHgaN^`yE#TuT3Th0ywQz#Fy+My3WA)C&Itp z_(^4e>z&>6ka@?&$P#kRlcv-F_3Kw z@jTgQf_bhUqZcy6V%G~B&SW;U9J2EfOS1a^1ri1A`hVX-2$2xMpv4V0P3Eru{viGO zC#4q4VPHJ4IT@YBBGo4V5IrF<515*a&g$xp@!$jVc;I()IDDwycYAJmHzJ?8Bo2}J zW4yE$^F?*1AcPP?g$^Ba`Pj|Vx%10cQ&v`(tZv^XTGS^cHv64RI!pjg`8n`q61Xqm zbd3!m{`vQ%JoJ|Zy!pnKnA@>GFa~E#|2Re#o4~K1LpyLxbr?bjkrX3F_2UztyqFtr znoPUAq-BQnfV+WjB&$O|(rE$!Fa>xPNa}X_HLJJt(4QCZuYWAz=#hrjUufurzi};| z;nhL$J0`{TeYqK~c9Lb^==Ao@t)DWX5< zq;edW6|ZgPu}2s4@5h&M{Mh-EIJp&N*f&p4Qgr8L%v*)Ac%AAngb*U`I(BNul~;}C zL)T8A`jU&{U81+O76UVh=Cny0={Ny6<VsA z0`Mc9CjbBiz-*$MEtJ~0VK?(%SjCdXYkBR}jT|}JfPK>x+`c6cR5Bw4K-*b|^}q|t z`WHe7zd9GRXYBZaOr18EOD-Kw#lX0ij)}#qz?HnC7B-;|WTgb>27oE(cml|88% z(u;B9D;PU|AQc14(6r>-Gh3kn_$2VS>OlO;lnDSt_XhkE_y9>dDbN;<JTVt4A5u1aqhutw3uXBg^t`@i+)=&S9mI9R!g%IKt%Q7hI-i2P}-RRk? zE9K=y^sgvo@Q`x)^es+Q1wOw#;7Q;n((GCPxn$A=;FL<>2|#`7y;gD@m&1qZIefUD zy+<0@UEf6Qi6$B~IA*&v0b$dizA=Pj=(xHDXxP^I-y|K5HPLVLo}~OY2q6SO=g#c` zumUD|c|lCmprD`w1%)TSPX2p}4)W)+9=I0Rs5+2%%ESr4DOUi01-dAXLkJ;6dO8B! zP6jOZwm07G#sLI@#Z;{a~~KLnmu981cP3BZMY zjOdQg!N_Dl2q99P3v2@JBf7CK<9GANN+tlUXA-SI_%UEOa*99*AyP#nunKq-c#tgG z$?u0u0Ag1O+)1 z1VD|!z!+dC(1&Qvf&!oe(Y*yZMC&vdyqB9mauf(5(uH^0|2jlB;x!X}R$osv0Z$#U i4Ok5|w_0000 - :/images/shadps4.svg + :/images/shadps4.png true diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp index a823eedab..550fdddb5 100644 --- a/src/qt_gui/check_update.cpp +++ b/src/qt_gui/check_update.cpp @@ -188,7 +188,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate, QHBoxLayout* titleLayout = new QHBoxLayout(); QLabel* imageLabel = new QLabel(this); - QPixmap pixmap(":/images/shadps4.svg"); + QPixmap pixmap(":/images/shadps4.png"); imageLabel->setPixmap(pixmap); imageLabel->setScaledContents(true); imageLabel->setFixedSize(50, 50); diff --git a/src/shadps4.qrc b/src/shadps4.qrc index 81a36af34..2aee394c8 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -1,7 +1,7 @@ images/shadps4.ico - images/shadps4.svg + images/shadps4.png images/about_icon.png images/dump_icon.png images/play_icon.png From d188d14e197ccc05414106ad2d089701d039267d Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 13 Apr 2025 12:18:28 -0700 Subject: [PATCH 127/194] hotfix: Update discord-rpc to remove invasive macOS minimum linker flag. --- externals/discord-rpc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/discord-rpc b/externals/discord-rpc index d3b5af882..19f66e6dc 160000 --- a/externals/discord-rpc +++ b/externals/discord-rpc @@ -1 +1 @@ -Subproject commit d3b5af8827031f3bccbf8c15d5dc1bfdc9467f17 +Subproject commit 19f66e6dcabb2268965f453db9e5774ede43238f From ec515ad113d344fb6d9c498d7f547bc18e97348c Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 13 Apr 2025 19:08:27 -0500 Subject: [PATCH 128/194] Implement sceVideoOutGetEventCount and sceVideoOutDeleteVblankEvent (#2753) * Implement sceVideoOutDeleteVblankEvent * Implement sceVideoOutGetEventCount Based on decompilation, needs testing. I also tidied up some types for other functions in here. * Change hexadecimal numbers to lowercase A minor change to make sure my implementation follows the formatting standards seen in https://github.com/shadps4-emu/shadPS4/pull/2423 --- src/core/libraries/videoout/video_out.cpp | 33 +++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index 219d0886b..3c839dadd 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -104,6 +104,20 @@ s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handl return ORBIS_OK; } +s32 PS4_SYSV_ABI sceVideoOutDeleteVblankEvent(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->vblank_events.erase(find(port->vblank_events.begin(), port->vblank_events.end(), eq)); + return ORBIS_OK; +} + s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum, const BufferAttribute* attribute) { if (!addresses || !attribute) { @@ -166,7 +180,7 @@ s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode return ORBIS_OK; } -int PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev) { +s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev) { if (ev == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; } @@ -194,7 +208,7 @@ int PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev) { } } -int PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, int64_t* data) { +s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* data) { if (ev == nullptr || data == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; } @@ -211,6 +225,17 @@ int PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, int64 return ORBIS_OK; } +s32 PS4_SYSV_ABI sceVideoOutGetEventCount(const Kernel::SceKernelEvent* ev) { + if (ev == nullptr) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; + } + if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; + } + + return (ev->data >> 0xc) & 0xf; +} + s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status) { if (!status) { LOG_ERROR(Lib_VideoOut, "Flip status is null"); @@ -447,12 +472,16 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("U2JJtSqNKZI", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutGetEventId); LIB_FUNCTION("rWUTcKdkUzQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutGetEventData); + LIB_FUNCTION("Mt4QHHkxkOc", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutGetEventCount); LIB_FUNCTION("DYhhWbJSeRg", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutColorSettingsSetGamma); LIB_FUNCTION("pv9CI5VC+R0", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutAdjustColor); LIB_FUNCTION("-Ozn0F1AFRg", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutDeleteFlipEvent); + LIB_FUNCTION("oNOQn3knW6s", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutDeleteVblankEvent); LIB_FUNCTION("pjkDsgxli6c", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutModeSetAny_); LIB_FUNCTION("N1bEoJ4SRw4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, From fb146f2a2057795957fb341d3d046efde34f2f2a Mon Sep 17 00:00:00 2001 From: davidantunes23 <147332596+davidantunes23@users.noreply.github.com> Date: Mon, 14 Apr 2025 07:17:28 +0100 Subject: [PATCH 129/194] Fix #2613: Game icon size not working properly on grid view (#2759) * Fix #2613: Game icon size not working properly on grid view When you select grid view mode and try to change the game icon size, the icons change to the wrong size, or don't change at all. To solve this I added a line to call the function that populates the game grid with the games when the user changes the icon size. That way the size of the icons is updated to match the size selected by the user, which is the intended behaviour. I also added code to update the check on the size when changing view mode. * Fix #2613: fixed clang format issue --- src/qt_gui/main_window.cpp | 59 ++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 60ab58274..36037fd4c 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -516,9 +516,11 @@ void MainWindow::CreateConnects() { Config::setIconSize(36); Config::setSliderPosition(0); } else { + m_game_grid_frame->icon_size = 69; ui->sizeSlider->setValue(0); // icone_size - 36 Config::setIconSizeGrid(69); Config::setSliderPositionGrid(0); + m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } }); @@ -529,9 +531,11 @@ void MainWindow::CreateConnects() { Config::setIconSize(64); Config::setSliderPosition(28); } else { + m_game_grid_frame->icon_size = 97; ui->sizeSlider->setValue(28); Config::setIconSizeGrid(97); Config::setSliderPositionGrid(28); + m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } }); @@ -542,9 +546,11 @@ void MainWindow::CreateConnects() { Config::setIconSize(128); Config::setSliderPosition(92); } else { + m_game_grid_frame->icon_size = 161; ui->sizeSlider->setValue(92); - Config::setIconSizeGrid(160); - Config::setSliderPositionGrid(91); + Config::setIconSizeGrid(161); + Config::setSliderPositionGrid(92); + m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } }); @@ -555,9 +561,11 @@ void MainWindow::CreateConnects() { Config::setIconSize(256); Config::setSliderPosition(220); } else { + m_game_grid_frame->icon_size = 256; ui->sizeSlider->setValue(220); Config::setIconSizeGrid(256); Config::setSliderPositionGrid(220); + m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } }); // List @@ -577,6 +585,7 @@ void MainWindow::CreateConnects() { ui->sizeSlider->setEnabled(true); ui->sizeSlider->setSliderPosition(slider_pos); ui->mw_searchbar->setText(""); + SetLastIconSizeBullet(); }); // Grid connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget.data(), [this]() { @@ -595,6 +604,7 @@ void MainWindow::CreateConnects() { ui->sizeSlider->setEnabled(true); ui->sizeSlider->setSliderPosition(slider_pos_grid); ui->mw_searchbar->setText(""); + SetLastIconSizeBullet(); }); // Elf Viewer connect(ui->setlistElfAct, &QAction::triggered, m_dock_widget.data(), [this]() { @@ -606,6 +616,7 @@ void MainWindow::CreateConnects() { isTableList = false; ui->sizeSlider->setDisabled(true); Config::setTableMode(2); + SetLastIconSizeBullet(); }); // Cheats/Patches Download. @@ -1031,19 +1042,37 @@ void MainWindow::SetLastUsedTheme() { void MainWindow::SetLastIconSizeBullet() { // set QAction bullet point if applicable int lastSize = Config::getIconSize(); - switch (lastSize) { - case 36: - ui->setIconSizeTinyAct->setChecked(true); - break; - case 64: - ui->setIconSizeSmallAct->setChecked(true); - break; - case 128: - ui->setIconSizeMediumAct->setChecked(true); - break; - case 256: - ui->setIconSizeLargeAct->setChecked(true); - break; + int lastSizeGrid = Config::getIconSizeGrid(); + if (isTableList) { + switch (lastSize) { + case 36: + ui->setIconSizeTinyAct->setChecked(true); + break; + case 64: + ui->setIconSizeSmallAct->setChecked(true); + break; + case 128: + ui->setIconSizeMediumAct->setChecked(true); + break; + case 256: + ui->setIconSizeLargeAct->setChecked(true); + break; + } + } else { + switch (lastSizeGrid) { + case 69: + ui->setIconSizeTinyAct->setChecked(true); + break; + case 97: + ui->setIconSizeSmallAct->setChecked(true); + break; + case 161: + ui->setIconSizeMediumAct->setChecked(true); + break; + case 256: + ui->setIconSizeLargeAct->setChecked(true); + break; + } } } From 657073b9e217d4005235d2089af234ab7864a7e7 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 13 Apr 2025 23:46:10 -0700 Subject: [PATCH 130/194] libraries: Initial Audio3d implementation. (#2776) * feat: Audio3d * feat: Audio3d * audio3d: disable output * audio3d: Implement central Audio3d output. * audio3d: Ignore AudioOut already initialized error. * audio3d: Convert and retain sample buffers when queued. * audio3d: Treat object audio as single channel. * audio3d: Clean up. --------- Co-authored-by: auser1337 <154299690+auser1337@users.noreply.github.com> --- CMakeLists.txt | 2 - src/core/libraries/audio/audioout.cpp | 3 +- src/core/libraries/audio/audioout.h | 10 +- src/core/libraries/audio3d/audio3d.cpp | 797 +++++++++++++------- src/core/libraries/audio3d/audio3d.h | 194 ++--- src/core/libraries/audio3d/audio3d_error.h | 2 - src/core/libraries/audio3d/audio3d_impl.cpp | 13 - src/core/libraries/audio3d/audio3d_impl.h | 16 - 8 files changed, 637 insertions(+), 400 deletions(-) delete mode 100644 src/core/libraries/audio3d/audio3d_impl.cpp delete mode 100644 src/core/libraries/audio3d/audio3d_impl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 63dc7b4c3..0ea8688df 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -450,8 +450,6 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/audio3d/audio3d.cpp src/core/libraries/audio3d/audio3d.h src/core/libraries/audio3d/audio3d_error.h - src/core/libraries/audio3d/audio3d_impl.cpp - src/core/libraries/audio3d/audio3d_impl.h src/core/libraries/game_live_streaming/gamelivestreaming.cpp src/core/libraries/game_live_streaming/gamelivestreaming.h src/core/libraries/remote_play/remoteplay.cpp diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index dea8115e9..92488443f 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -191,6 +191,7 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta case OrbisAudioOutPort::Main: case OrbisAudioOutPort::Bgm: case OrbisAudioOutPort::Voice: + case OrbisAudioOutPort::Audio3d: state->output = 1; state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels; break; @@ -316,7 +317,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; } if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::Padspk) && - (port_type != OrbisAudioOutPort::Aux)) { + (port_type != OrbisAudioOutPort::Audio3d && port_type != OrbisAudioOutPort::Aux)) { LOG_ERROR(Lib_AudioOut, "Invalid port type"); return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; } diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index 5eafb43a1..7fcc25095 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -20,7 +20,15 @@ class PortBackend; constexpr s32 SCE_AUDIO_OUT_NUM_PORTS = 22; constexpr s32 SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value -enum class OrbisAudioOutPort { Main = 0, Bgm = 1, Voice = 2, Personal = 3, Padspk = 4, Aux = 127 }; +enum class OrbisAudioOutPort { + Main = 0, + Bgm = 1, + Voice = 2, + Personal = 3, + Padspk = 4, + Audio3d = 126, + Aux = 127, +}; enum class OrbisAudioOutParamFormat : u32 { S16Mono = 0, diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp index d896524c6..646c28949 100644 --- a/src/core/libraries/audio3d/audio3d.cpp +++ b/src/core/libraries/audio3d/audio3d.cpp @@ -1,8 +1,13 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + +#include "common/assert.h" #include "common/logging/log.h" #include "core/libraries/audio/audioout.h" +#include "core/libraries/audio/audioout_error.h" #include "core/libraries/audio3d/audio3d.h" #include "core/libraries/audio3d/audio3d_error.h" #include "core/libraries/error_codes.h" @@ -10,331 +15,577 @@ namespace Libraries::Audio3d { -int PS4_SYSV_ABI sceAudio3dInitialize(s64 iReserved) { - LOG_INFO(Lib_Audio3d, "iReserved = {}", iReserved); - return ORBIS_OK; +static constexpr u32 AUDIO3D_SAMPLE_RATE = 48000; + +static constexpr AudioOut::OrbisAudioOutParamFormat AUDIO3D_OUTPUT_FORMAT = + AudioOut::OrbisAudioOutParamFormat::S16Stereo; +static constexpr u32 AUDIO3D_OUTPUT_NUM_CHANNELS = 2; +static constexpr u32 AUDIO3D_OUTPUT_BUFFER_FRAMES = 0x100; + +static std::unique_ptr state; + +s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) { + LOG_INFO(Lib_Audio3d, "called, handle = {}", handle); + return AudioOut::sceAudioOutClose(handle); } -int PS4_SYSV_ABI sceAudio3dTerminate() { - // TODO: When not initialized or some ports still open, return ORBIS_AUDIO3D_ERROR_NOT_READY - LOG_INFO(Lib_Audio3d, "called"); - return ORBIS_OK; -} - -void PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* parameters) { - if (parameters == nullptr) { - LOG_ERROR(Lib_Audio3d, "Invalid OpenParameters ptr"); - return; - } - - parameters->size_this = sizeof(OrbisAudio3dOpenParameters); - parameters->granularity = 256; - parameters->rate = OrbisAudio3dRate::Rate48000; - parameters->max_objects = 512; - parameters->queue_depth = 2; - parameters->buffer_mode = OrbisAudio3dBufferMode::AdvanceAndPush; - parameters->num_beds = 2; -} - -int PS4_SYSV_ABI sceAudio3dPortOpen(OrbisUserServiceUserId iUserId, - const OrbisAudio3dOpenParameters* pParameters, - OrbisAudio3dPortId* pId) { - LOG_INFO(Lib_Audio3d, "iUserId = {}", iUserId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortClose(OrbisAudio3dPortId uiPortId) { - LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortSetAttribute(OrbisAudio3dPortId uiPortId, - OrbisAudio3dAttributeId uiAttributeId, - const void* pAttribute, size_t szAttribute) { - LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiAttributeId = {}, szAttribute = {}", uiPortId, - uiAttributeId, szAttribute); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortFlush(OrbisAudio3dPortId uiPortId) { - LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortAdvance(OrbisAudio3dPortId uiPortId) { - LOG_TRACE(Lib_Audio3d, "uiPortId = {}", uiPortId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId uiPortId, OrbisAudio3dBlocking eBlocking) { - LOG_TRACE(Lib_Audio3d, "uiPortId = {}", uiPortId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported(OrbisAudio3dPortId uiPortId, - OrbisAudio3dAttributeId* pCapabilities, - u32* pNumCapabilities) { - LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId uiPortId, u32* pQueueLevel, - u32* pQueueAvailable) { - LOG_TRACE(Lib_Audio3d, "uiPortId = {}", uiPortId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dObjectReserve(OrbisAudio3dPortId uiPortId, OrbisAudio3dObjectId* pId) { - LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dObjectUnreserve(OrbisAudio3dPortId uiPortId, - OrbisAudio3dObjectId uiObjectId) { - LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiObjectId = {}", uiPortId, uiObjectId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dObjectSetAttributes(OrbisAudio3dPortId uiPortId, - OrbisAudio3dObjectId uiObjectId, - size_t szNumAttributes, - const OrbisAudio3dAttribute* pAttributeArray) { - LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiObjectId = {}, szNumAttributes = {}", uiPortId, - uiObjectId, szNumAttributes); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dBedWrite(OrbisAudio3dPortId uiPortId, u32 uiNumChannels, - OrbisAudio3dFormat eFormat, const void* pBuffer, - u32 uiNumSamples) { - LOG_TRACE(Lib_Audio3d, "uiPortId = {}, uiNumChannels = {}, uiNumSamples = {}", uiPortId, - uiNumChannels, uiNumSamples); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dBedWrite2(OrbisAudio3dPortId uiPortId, u32 uiNumChannels, - OrbisAudio3dFormat eFormat, const void* pBuffer, - u32 uiNumSamples, OrbisAudio3dOutputRoute eOutputRoute, - bool bRestricted) { - LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiNumChannels = {}, uiNumSamples = {}, bRestricted = {}", - uiPortId, uiNumChannels, uiNumSamples, bRestricted); - return ORBIS_OK; -} - -size_t PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMemorySize(u32 uiNumSpeakers, bool bIs3d) { - LOG_INFO(Lib_Audio3d, "uiNumSpeakers = {}, bIs3d = {}", uiNumSpeakers, bIs3d); - return ORBIS_OK; -} - -int PS4_SYSV_ABI -sceAudio3dCreateSpeakerArray(OrbisAudio3dSpeakerArrayHandle* pHandle, - const OrbisAudio3dSpeakerArrayParameters* pParameters) { - if (pHandle == nullptr || pParameters == nullptr) { - LOG_ERROR(Lib_Audio3d, "invalid SpeakerArray parameters"); - return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; - } - LOG_INFO(Lib_Audio3d, "called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray(OrbisAudio3dSpeakerArrayHandle handle) { - if (handle == nullptr) { - LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle"); - return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; - } - LOG_INFO(Lib_Audio3d, "called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients(OrbisAudio3dSpeakerArrayHandle handle, - OrbisAudio3dPosition pos, float fSpread, - float* pCoefficients, - u32 uiNumCoefficients) { - LOG_INFO(Lib_Audio3d, "fSpread = {}, uiNumCoefficients = {}", fSpread, uiNumCoefficients); - if (handle == nullptr) { - LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle"); - return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; - } - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2(OrbisAudio3dSpeakerArrayHandle handle, - OrbisAudio3dPosition pos, float fSpread, - float* pCoefficients, - u32 uiNumCoefficients, bool bHeightAware, - float fDownmixSpreadRadius) { +s32 PS4_SYSV_ABI +sceAudio3dAudioOutOpen(const OrbisAudio3dPortId port_id, const OrbisUserServiceUserId user_id, + s32 type, const s32 index, const u32 len, const u32 freq, + const AudioOut::OrbisAudioOutParamExtendedInformation param) { LOG_INFO(Lib_Audio3d, - "fSpread = {}, uiNumCoefficients = {}, bHeightAware = {}, fDownmixSpreadRadius = {}", - fSpread, uiNumCoefficients, bHeightAware, fDownmixSpreadRadius); - if (handle == nullptr) { - LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle"); + "called, port_id = {}, user_id = {}, type = {}, index = {}, len = {}, freq = {}", + port_id, user_id, type, index, len, freq); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + if (len != state->ports[port_id].parameters.granularity) { + LOG_ERROR(Lib_Audio3d, "len != state->ports[port_id].parameters.granularity"); return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } + + return sceAudioOutOpen(user_id, static_cast(type), index, len, + freq, param); +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(const s32 handle, void* ptr) { + LOG_DEBUG(Lib_Audio3d, "called, handle = {}, ptr = {}", handle, ptr); + + if (!ptr) { + LOG_ERROR(Lib_Audio3d, "!ptr"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + if (handle < 0 || (handle & 0xFFFF) > 25) { + LOG_ERROR(Lib_Audio3d, "handle < 0 || (handle & 0xFFFF) > 25"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + return AudioOut::sceAudioOutOutput(handle, ptr); +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* param, + const u32 num) { + LOG_DEBUG(Lib_Audio3d, "called, param = {}, num = {}", static_cast(param), num); + + if (!param || !num) { + LOG_ERROR(Lib_Audio3d, "!param || !num"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + return AudioOut::sceAudioOutOutputs(param, num); +} + +static s32 PortQueueAudio(Port& port, const OrbisAudio3dPcm& pcm, const u32 num_channels) { + // Audio3d output is configured for stereo signed 16-bit PCM. Convert the data to match. + const SDL_AudioSpec src_spec = { + .format = pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? SDL_AUDIO_S16LE + : SDL_AUDIO_F32LE, + .channels = static_cast(num_channels), + .freq = AUDIO3D_SAMPLE_RATE, + }; + constexpr SDL_AudioSpec dst_spec = { + .format = SDL_AUDIO_S16LE, + .channels = AUDIO3D_OUTPUT_NUM_CHANNELS, + .freq = AUDIO3D_SAMPLE_RATE, + }; + const auto src_size = pcm.num_samples * + (pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? 2 : 4) * + num_channels; + + u8* dst_data; + int dst_len; + if (!SDL_ConvertAudioSamples(&src_spec, static_cast(pcm.sample_buffer), + static_cast(src_size), &dst_spec, &dst_data, &dst_len)) { + LOG_ERROR(Lib_Audio3d, "SDL_ConvertAudioSamples failed: {}", SDL_GetError()); + return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY; + } + + port.queue.emplace_back(AudioData{ + .sample_buffer = dst_data, + .num_samples = pcm.num_samples, + }); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dBedWrite(const OrbisAudio3dPortId port_id, const u32 num_channels, + const OrbisAudio3dFormat format, void* buffer, + const u32 num_samples) { + return sceAudio3dBedWrite2(port_id, num_channels, format, buffer, num_samples, + OrbisAudio3dOutputRoute::ORBIS_AUDIO3D_OUTPUT_BOTH, false); +} + +s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32 num_channels, + const OrbisAudio3dFormat format, void* buffer, + const u32 num_samples, + const OrbisAudio3dOutputRoute output_route, + const bool restricted) { + LOG_DEBUG( + Lib_Audio3d, + "called, port_id = {}, num_channels = {}, format = {}, num_samples = {}, output_route " + "= {}, restricted = {}", + port_id, num_channels, magic_enum::enum_name(format), num_samples, + magic_enum::enum_name(output_route), restricted); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + if (output_route > OrbisAudio3dOutputRoute::ORBIS_AUDIO3D_OUTPUT_BOTH) { + LOG_ERROR(Lib_Audio3d, "output_route > ORBIS_AUDIO3D_OUTPUT_BOTH"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + if (format > OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_FLOAT) { + LOG_ERROR(Lib_Audio3d, "format > ORBIS_AUDIO3D_FORMAT_FLOAT"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + if (num_channels != 2 && num_channels != 8) { + LOG_ERROR(Lib_Audio3d, "num_channels != 2 && num_channels != 8"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + if (!buffer || !num_samples) { + LOG_ERROR(Lib_Audio3d, "!buffer || !num_samples"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + if (format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_FLOAT) { + if ((reinterpret_cast(buffer) & 3) != 0) { + LOG_ERROR(Lib_Audio3d, "buffer & 3 != 0"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + } else if (format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) { + if ((reinterpret_cast(buffer) & 1) != 0) { + LOG_ERROR(Lib_Audio3d, "buffer & 1 != 0"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + } + + return PortQueueAudio(state->ports[port_id], + OrbisAudio3dPcm{ + .format = format, + .sample_buffer = buffer, + .num_samples = num_samples, + }, + num_channels); +} + +s32 PS4_SYSV_ABI sceAudio3dCreateSpeakerArray() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params) { + LOG_DEBUG(Lib_Audio3d, "called"); + if (params) { + *params = OrbisAudio3dOpenParameters{ + .size_this = 0x20, + .granularity = 0x100, + .rate = OrbisAudio3dRate::ORBIS_AUDIO3D_RATE_48000, + .max_objects = 512, + .queue_depth = 2, + .buffer_mode = OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH, + }; + } return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId uiPortId, OrbisUserServiceUserId userId, - s32 type, s32 index, u32 len, u32 freq, u32 param) { +s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMemorySize() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dInitialize(const s64 reserved) { + LOG_INFO(Lib_Audio3d, "called, reserved = {}", reserved); + + if (reserved != 0) { + LOG_ERROR(Lib_Audio3d, "reserved != 0"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + if (state) { + LOG_ERROR(Lib_Audio3d, "already initialized"); + return ORBIS_AUDIO3D_ERROR_NOT_READY; + } + + state = std::make_unique(); + + if (const auto init_ret = AudioOut::sceAudioOutInit(); + init_ret < 0 && init_ret != ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT) { + return init_ret; + } + + AudioOut::OrbisAudioOutParamExtendedInformation ext_info{}; + ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT); + state->audio_out_handle = + AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0, + AUDIO3D_OUTPUT_BUFFER_FRAMES, AUDIO3D_SAMPLE_RATE, ext_info); + if (state->audio_out_handle < 0) { + return state->audio_out_handle; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dObjectReserve(const OrbisAudio3dPortId port_id, + OrbisAudio3dObjectId* object_id) { + LOG_INFO(Lib_Audio3d, "called, port_id = {}, object_id = {}", port_id, + static_cast(object_id)); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + if (!object_id) { + LOG_ERROR(Lib_Audio3d, "!object_id"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + static int last_id = 0; + *object_id = ++last_id; + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(const OrbisAudio3dPortId port_id, + OrbisAudio3dObjectId object_id, + const u64 num_attributes, + const OrbisAudio3dAttribute* attribute_array) { + LOG_DEBUG(Lib_Audio3d, + "called, port_id = {}, object_id = {}, num_attributes = {}, attribute_array = {}", + port_id, object_id, num_attributes, fmt::ptr(attribute_array)); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + + for (u64 i = 0; i < num_attributes; i++) { + const auto& attribute = attribute_array[i]; + + switch (attribute.attribute_id) { + case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_PCM: { + const auto pcm = static_cast(attribute.value); + // Object audio has 1 channel. + if (const auto ret = PortQueueAudio(port, *pcm, 1); ret != ORBIS_OK) { + return ret; + } + break; + } + default: + LOG_ERROR(Lib_Audio3d, "Unsupported attribute ID: {:#x}", + static_cast(attribute.attribute_id)); + break; + } + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortAdvance(const OrbisAudio3dPortId port_id) { + LOG_DEBUG(Lib_Audio3d, "called, port_id = {}", port_id); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + if (state->ports[port_id].parameters.buffer_mode == + OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) { + LOG_ERROR(Lib_Audio3d, "port doesn't have advance capability"); + return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED; + } + + auto& port = state->ports[port_id]; + if (port.current_buffer.has_value()) { + // Free existing buffer before replacing. + SDL_free(port.current_buffer->sample_buffer); + } + + if (!port.queue.empty()) { + port.current_buffer = port.queue.front(); + port.queue.pop_front(); + } else { + // Nothing to advance to. + LOG_DEBUG(Lib_Audio3d, "Port advance with no buffer queued"); + port.current_buffer = std::nullopt; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortClose() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortCreate() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortDestroy() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortFlush() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortFreeState() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortGetList() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortGetParameters() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(const OrbisAudio3dPortId port_id, u32* queue_level, + u32* queue_available) { + LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, queue_level = {}, queue_available = {}", port_id, + static_cast(queue_level), static_cast(queue_available)); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + if (!queue_level && !queue_available) { + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + const auto port = state->ports[port_id]; + const size_t size = port.queue.size(); + + if (queue_level) { + *queue_level = size; + } + + if (queue_available) { + *queue_available = port.parameters.queue_depth - size; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortGetState() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortGetStatus() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id, + const OrbisAudio3dOpenParameters* parameters, + OrbisAudio3dPortId* port_id) { + LOG_INFO(Lib_Audio3d, "called, user_id = {}, parameters = {}, id = {}", user_id, + static_cast(parameters), static_cast(port_id)); + + if (!state) { + LOG_ERROR(Lib_Audio3d, "!initialized"); + return ORBIS_AUDIO3D_ERROR_NOT_READY; + } + + if (!parameters || !port_id) { + LOG_ERROR(Lib_Audio3d, "!parameters || !id"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + const int id = static_cast(state->ports.size()) + 1; + + if (id > 3) { + LOG_ERROR(Lib_Audio3d, "id > 3"); + return ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES; + } + + *port_id = id; + std::memcpy(&state->ports[id].parameters, parameters, sizeof(OrbisAudio3dOpenParameters)); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortPush(const OrbisAudio3dPortId port_id, + const OrbisAudio3dBlocking blocking) { + LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, blocking = {}", port_id, + magic_enum::enum_name(blocking)); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + const auto& port = state->ports[port_id]; + if (port.parameters.buffer_mode != + OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH) { + LOG_ERROR(Lib_Audio3d, "port doesn't have push capability"); + return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED; + } + + if (!port.current_buffer.has_value()) { + // Nothing to push. + LOG_DEBUG(Lib_Audio3d, "Port push with no buffer ready"); + return ORBIS_OK; + } + + // TODO: Implement asynchronous blocking mode. + const auto& [sample_buffer, num_samples] = port.current_buffer.value(); + return AudioOut::sceAudioOutOutput(state->audio_out_handle, sample_buffer); +} + +s32 PS4_SYSV_ABI sceAudio3dPortQueryDebug() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dPortSetAttribute(const OrbisAudio3dPortId port_id, + const OrbisAudio3dAttributeId attribute_id, + void* attribute, const u64 attribute_size) { LOG_INFO(Lib_Audio3d, - "uiPortId = {}, userId = {}, type = {}, index = {}, len = {}, freq = {}, param = {}", - uiPortId, userId, type, index, len, freq, param); - return ORBIS_OK; -} + "called, port_id = {}, attribute_id = {}, attribute = {}, attribute_size = {}", + port_id, static_cast(attribute_id), attribute, attribute_size); -s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle) { - LOG_INFO(Lib_Audio3d, "handle = {}", handle); - return ORBIS_OK; -} + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } -s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(s32 handle, const void* ptr) { - LOG_TRACE(Lib_Audio3d, "handle = {}", handle); - if (ptr == nullptr) { - LOG_ERROR(Lib_Audio3d, "invalid Output ptr"); + if (!attribute) { + LOG_ERROR(Lib_Audio3d, "!attribute"); return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } + + // TODO + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(::Libraries::AudioOut::OrbisAudioOutOutputParam* param, - s32 num) { - LOG_INFO(Lib_Audio3d, "num = {}", num); - if (param == nullptr) { - LOG_ERROR(Lib_Audio3d, "invalid OutputParam ptr"); - return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; - } - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortCreate(u32 uiGranularity, OrbisAudio3dRate eRate, s64 iReserved, - OrbisAudio3dPortId* pId) { - LOG_INFO(Lib_Audio3d, "uiGranularity = {}, iReserved = {}", uiGranularity, iReserved); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortDestroy(OrbisAudio3dPortId uiPortId) { - LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); - return ORBIS_OK; -} - -// Audio3dPrivate - -const char* PS4_SYSV_ABI sceAudio3dStrError(int iErrorCode) { - LOG_ERROR(Lib_Audio3d, "(PRIVATE) called, iErrorCode = {}", iErrorCode); - return "NOT_IMPLEMENTED"; -} - -// Audio3dDebug - -int PS4_SYSV_ABI sceAudio3dPortQueryDebug() { - LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortQueryDebug called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortGetList() { - LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetList called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortGetParameters() { - LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetParameters called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortGetState() { - LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetState called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudio3dPortFreeState() { - LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortFreeState called"); - return ORBIS_OK; -} - -// Unknown - -int PS4_SYSV_ABI sceAudio3dPortGetBufferLevel() { +s32 PS4_SYSV_ABI sceAudio3dReportRegisterHandler() { LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudio3dPortGetStatus() { +s32 PS4_SYSV_ABI sceAudio3dReportUnregisterHandler() { LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudio3dReportRegisterHandler() { +s32 PS4_SYSV_ABI sceAudio3dSetGpuRenderer() { LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudio3dReportUnregisterHandler() { +s32 PS4_SYSV_ABI sceAudio3dStrError() { LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudio3dSetGpuRenderer() { +s32 PS4_SYSV_ABI sceAudio3dTerminate() { LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); return ORBIS_OK; } void RegisterlibSceAudio3d(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("pZlOm1aF3aA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutClose); + LIB_FUNCTION("ucEsi62soTo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutOpen); + LIB_FUNCTION("7NYEzJ9SJbM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dAudioOutOutput); + LIB_FUNCTION("HbxYY27lK6E", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dAudioOutOutputs); + LIB_FUNCTION("9tEwE0GV0qo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite); + LIB_FUNCTION("xH4Q9UILL3o", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite2); + LIB_FUNCTION("lvWMW6vEqFU", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dCreateSpeakerArray); + LIB_FUNCTION("8hm6YdoQgwg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dDeleteSpeakerArray); + LIB_FUNCTION("Im+jOoa5WAI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetDefaultOpenParameters); + LIB_FUNCTION("kEqqyDkmgdI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetSpeakerArrayMemorySize); LIB_FUNCTION("-R1DukFq7Dk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dGetSpeakerArrayMixCoefficients); LIB_FUNCTION("-Re+pCWvwjQ", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dGetSpeakerArrayMixCoefficients2); - LIB_FUNCTION("-pzYDZozm+M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dPortQueryDebug); - LIB_FUNCTION("1HXxo-+1qCw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dObjectUnreserve); + LIB_FUNCTION("UmCvjSmuZIw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dInitialize); + LIB_FUNCTION("jO2tec4dJ2M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dObjectReserve); LIB_FUNCTION("4uyHN9q4ZeU", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dObjectSetAttributes); - LIB_FUNCTION("7NYEzJ9SJbM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dAudioOutOutput); - LIB_FUNCTION("8hm6YdoQgwg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dDeleteSpeakerArray); + LIB_FUNCTION("1HXxo-+1qCw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dObjectUnreserve); + LIB_FUNCTION("lw0qrdSjZt8", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortAdvance); + LIB_FUNCTION("OyVqOeVNtSk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortClose); + LIB_FUNCTION("UHFOgVNz0kk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortCreate); + LIB_FUNCTION("Mw9mRQtWepY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortDestroy); + LIB_FUNCTION("ZOGrxWLgQzE", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFlush); + LIB_FUNCTION("uJ0VhGcxCTQ", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFreeState); LIB_FUNCTION("9ZA23Ia46Po", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetAttributesSupported); - LIB_FUNCTION("9tEwE0GV0qo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite); - LIB_FUNCTION("Aacl5qkRU6U", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dStrError); - LIB_FUNCTION("CKHlRW2E9dA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetState); - LIB_FUNCTION("HbxYY27lK6E", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dAudioOutOutputs); - LIB_FUNCTION("Im+jOoa5WAI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dGetDefaultOpenParameters); - LIB_FUNCTION("Mw9mRQtWepY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortDestroy); - LIB_FUNCTION("OyVqOeVNtSk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortClose); - LIB_FUNCTION("QfNXBrKZeI0", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dReportRegisterHandler); - LIB_FUNCTION("QqgTQQdzEMY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dPortGetBufferLevel); LIB_FUNCTION("SEggctIeTcI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetList); - LIB_FUNCTION("UHFOgVNz0kk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortCreate); - LIB_FUNCTION("UmCvjSmuZIw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dInitialize); - LIB_FUNCTION("VEVhZ9qd4ZY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortPush); - LIB_FUNCTION("WW1TS2iz5yc", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dTerminate); - LIB_FUNCTION("XeDDK0xJWQA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortOpen); - LIB_FUNCTION("YaaDbDwKpFM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dPortGetQueueLevel); - LIB_FUNCTION("Yq9bfUQ0uJg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dPortSetAttribute); - LIB_FUNCTION("ZOGrxWLgQzE", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFlush); LIB_FUNCTION("flPcUaXVXcw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetParameters); + LIB_FUNCTION("YaaDbDwKpFM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortGetQueueLevel); + LIB_FUNCTION("CKHlRW2E9dA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetState); LIB_FUNCTION("iRX6GJs9tvE", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetStatus); - LIB_FUNCTION("jO2tec4dJ2M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dObjectReserve); - LIB_FUNCTION("kEqqyDkmgdI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dGetSpeakerArrayMemorySize); - LIB_FUNCTION("lvWMW6vEqFU", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, - sceAudio3dCreateSpeakerArray); - LIB_FUNCTION("lw0qrdSjZt8", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortAdvance); - LIB_FUNCTION("pZlOm1aF3aA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutClose); + LIB_FUNCTION("XeDDK0xJWQA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortOpen); + LIB_FUNCTION("VEVhZ9qd4ZY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortPush); + LIB_FUNCTION("-pzYDZozm+M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortQueryDebug); + LIB_FUNCTION("Yq9bfUQ0uJg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortSetAttribute); + LIB_FUNCTION("QfNXBrKZeI0", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dReportRegisterHandler); LIB_FUNCTION("psv2gbihC1A", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dReportUnregisterHandler); - LIB_FUNCTION("uJ0VhGcxCTQ", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFreeState); - LIB_FUNCTION("ucEsi62soTo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutOpen); - LIB_FUNCTION("xH4Q9UILL3o", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite2); LIB_FUNCTION("yEYXcbAGK14", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dSetGpuRenderer); + LIB_FUNCTION("Aacl5qkRU6U", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dStrError); + LIB_FUNCTION("WW1TS2iz5yc", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dTerminate); }; } // namespace Libraries::Audio3d diff --git a/src/core/libraries/audio3d/audio3d.h b/src/core/libraries/audio3d/audio3d.h index 6f344226f..f4e9ada8a 100644 --- a/src/core/libraries/audio3d/audio3d.h +++ b/src/core/libraries/audio3d/audio3d.h @@ -1,11 +1,13 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include "common/types.h" +#include +#include -#include +#include "common/types.h" +#include "core/libraries/audio/audioout.h" namespace Core::Loader { class SymbolsResolver; @@ -13,123 +15,131 @@ class SymbolsResolver; namespace Libraries::Audio3d { -class Audio3d; - using OrbisUserServiceUserId = s32; -using OrbisAudio3dPortId = u32; -using OrbisAudio3dObjectId = u32; -using OrbisAudio3dAttributeId = u32; -enum class OrbisAudio3dFormat { - S16 = 0, - Float = 1, +enum class OrbisAudio3dRate : u32 { + ORBIS_AUDIO3D_RATE_48000 = 0, }; -enum class OrbisAudio3dRate { - Rate48000 = 0, +enum class OrbisAudio3dBufferMode : u32 { + ORBIS_AUDIO3D_BUFFER_NO_ADVANCE = 0, + ORBIS_AUDIO3D_BUFFER_ADVANCE_NO_PUSH = 1, + ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2, }; -enum class OrbisAudio3dBufferMode { NoAdvance = 0, AdvanceNoPush = 1, AdvanceAndPush = 2 }; - -enum class OrbisAudio3dBlocking { - Async = 0, - Sync = 1, -}; - -enum class OrbisAudio3dPassthrough { - None = 0, - Left = 1, - Right = 2, -}; - -enum class OrbisAudio3dOutputRoute { - Both = 0, - HmuOnly = 1, - TvOnly = 2, -}; - -enum class OrbisAudio3dAmbisonics : u32 { - None = ~0U, - W = 0, - X = 1, - Y = 2, - Z = 3, - R = 4, - S = 5, - T = 6, - U = 7, - V = 8, - K = 9, - L = 10, - M = 11, - N = 12, - O = 13, - P = 14, - Q = 15 -}; - -static const OrbisAudio3dAttributeId s_sceAudio3dAttributePcm = 0x00000001; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributePriority = 0x00000002; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributePosition = 0x00000003; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeSpread = 0x00000004; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeGain = 0x00000005; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributePassthrough = 0x00000006; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeResetState = 0x00000007; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeApplicationSpecific = 0x00000008; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeAmbisonics = 0x00000009; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeRestricted = 0x0000000A; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeOutputRoute = 0x0000000B; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeLateReverbLevel = 0x00010001; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeDownmixSpreadRadius = 0x00010002; -static const OrbisAudio3dAttributeId s_sceAudio3dAttributeDownmixSpreadHeightAware = 0x00010003; - -struct OrbisAudio3dSpeakerArray; -using OrbisAudio3dSpeakerArrayHandle = OrbisAudio3dSpeakerArray*; // head - struct OrbisAudio3dOpenParameters { - size_t size_this; + u64 size_this; u32 granularity; OrbisAudio3dRate rate; u32 max_objects; u32 queue_depth; OrbisAudio3dBufferMode buffer_mode; - char padding[32]; + int : 32; u32 num_beds; }; -struct OrbisAudio3dAttribute { - OrbisAudio3dAttributeId attribute_id; - char padding[32]; - const void* p_value; - size_t value; +enum class OrbisAudio3dFormat : u32 { + ORBIS_AUDIO3D_FORMAT_S16 = 0, + ORBIS_AUDIO3D_FORMAT_FLOAT = 1, }; -struct OrbisAudio3dPosition { - float fX; - float fY; - float fZ; +enum class OrbisAudio3dOutputRoute : u32 { + ORBIS_AUDIO3D_OUTPUT_BOTH = 0, + ORBIS_AUDIO3D_OUTPUT_HMU_ONLY = 1, + ORBIS_AUDIO3D_OUTPUT_TV_ONLY = 2, +}; + +enum class OrbisAudio3dBlocking : u32 { + ORBIS_AUDIO3D_BLOCKING_ASYNC = 0, + ORBIS_AUDIO3D_BLOCKING_SYNC = 1, }; struct OrbisAudio3dPcm { OrbisAudio3dFormat format; - const void* sample_buffer; + void* sample_buffer; u32 num_samples; }; -struct OrbisAudio3dSpeakerArrayParameters { - OrbisAudio3dPosition* speaker_position; - u32 num_speakers; - bool is_3d; - void* buffer; - size_t size; +enum class OrbisAudio3dAttributeId : u32 { + ORBIS_AUDIO3D_ATTRIBUTE_PCM = 1, }; -struct OrbisAudio3dApplicationSpecific { - size_t size_this; - u8 application_specific[32]; +using OrbisAudio3dPortId = u32; +using OrbisAudio3dObjectId = u32; + +struct OrbisAudio3dAttribute { + OrbisAudio3dAttributeId attribute_id; + int : 32; + void* value; + u64 value_size; }; -void PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* sParameters); +struct AudioData { + u8* sample_buffer; + u32 num_samples; +}; + +struct Port { + OrbisAudio3dOpenParameters parameters{}; + std::deque queue; // Only stores PCM buffers for now + std::optional current_buffer{}; +}; + +struct Audio3dState { + std::unordered_map ports; + s32 audio_out_handle; +}; + +s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle); +s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id, OrbisUserServiceUserId user_id, + s32 type, s32 index, u32 len, u32 freq, + AudioOut::OrbisAudioOutParamExtendedInformation param); +s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(s32 handle, void* ptr); +s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* param, u32 num); +s32 PS4_SYSV_ABI sceAudio3dBedWrite(OrbisAudio3dPortId port_id, u32 num_channels, + OrbisAudio3dFormat format, void* buffer, u32 num_samples); +s32 PS4_SYSV_ABI sceAudio3dBedWrite2(OrbisAudio3dPortId port_id, u32 num_channels, + OrbisAudio3dFormat format, void* buffer, u32 num_samples, + OrbisAudio3dOutputRoute output_route, bool restricted); +s32 PS4_SYSV_ABI sceAudio3dCreateSpeakerArray(); +s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray(); +s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params); +s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMemorySize(); +s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients(); +s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2(); +s32 PS4_SYSV_ABI sceAudio3dInitialize(s64 reserved); +s32 PS4_SYSV_ABI sceAudio3dObjectReserve(OrbisAudio3dPortId port_id, + OrbisAudio3dObjectId* object_id); +s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(OrbisAudio3dPortId port_id, + OrbisAudio3dObjectId object_id, u64 num_attributes, + const OrbisAudio3dAttribute* attribute_array); +s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(); +s32 PS4_SYSV_ABI sceAudio3dPortAdvance(OrbisAudio3dPortId port_id); +s32 PS4_SYSV_ABI sceAudio3dPortClose(); +s32 PS4_SYSV_ABI sceAudio3dPortCreate(); +s32 PS4_SYSV_ABI sceAudio3dPortDestroy(); +s32 PS4_SYSV_ABI sceAudio3dPortFlush(); +s32 PS4_SYSV_ABI sceAudio3dPortFreeState(); +s32 PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported(); +s32 PS4_SYSV_ABI sceAudio3dPortGetList(); +s32 PS4_SYSV_ABI sceAudio3dPortGetParameters(); +s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId port_id, u32* queue_level, + u32* queue_available); +s32 PS4_SYSV_ABI sceAudio3dPortGetState(); +s32 PS4_SYSV_ABI sceAudio3dPortGetStatus(); +s32 PS4_SYSV_ABI sceAudio3dPortOpen(OrbisUserServiceUserId user_id, + const OrbisAudio3dOpenParameters* parameters, + OrbisAudio3dPortId* port_id); +s32 PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId port_id, OrbisAudio3dBlocking blocking); +s32 PS4_SYSV_ABI sceAudio3dPortQueryDebug(); +s32 PS4_SYSV_ABI sceAudio3dPortSetAttribute(OrbisAudio3dPortId port_id, + OrbisAudio3dAttributeId attribute_id, void* attribute, + u64 attribute_size); +s32 PS4_SYSV_ABI sceAudio3dReportRegisterHandler(); +s32 PS4_SYSV_ABI sceAudio3dReportUnregisterHandler(); +s32 PS4_SYSV_ABI sceAudio3dSetGpuRenderer(); +s32 PS4_SYSV_ABI sceAudio3dStrError(); +s32 PS4_SYSV_ABI sceAudio3dTerminate(); void RegisterlibSceAudio3d(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Audio3d diff --git a/src/core/libraries/audio3d/audio3d_error.h b/src/core/libraries/audio3d/audio3d_error.h index 626ac8699..ff9d9749c 100644 --- a/src/core/libraries/audio3d/audio3d_error.h +++ b/src/core/libraries/audio3d/audio3d_error.h @@ -3,8 +3,6 @@ #pragma once -#include "core/libraries/error_codes.h" - constexpr int ORBIS_AUDIO3D_ERROR_UNKNOWN = 0x80EA0001; constexpr int ORBIS_AUDIO3D_ERROR_INVALID_PORT = 0x80EA0002; constexpr int ORBIS_AUDIO3D_ERROR_INVALID_OBJECT = 0x80EA0003; diff --git a/src/core/libraries/audio3d/audio3d_impl.cpp b/src/core/libraries/audio3d/audio3d_impl.cpp deleted file mode 100644 index 3069e8800..000000000 --- a/src/core/libraries/audio3d/audio3d_impl.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "audio3d_error.h" -#include "audio3d_impl.h" - -#include "common/logging/log.h" -#include "core/libraries/error_codes.h" -#include "core/libraries/kernel/kernel.h" - -using namespace Libraries::Kernel; - -namespace Libraries::Audio3d {} // namespace Libraries::Audio3d diff --git a/src/core/libraries/audio3d/audio3d_impl.h b/src/core/libraries/audio3d/audio3d_impl.h deleted file mode 100644 index 1213a030e..000000000 --- a/src/core/libraries/audio3d/audio3d_impl.h +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "audio3d.h" - -namespace Libraries::Audio3d { - -class Audio3d { -public: -private: - using OrbisAudio3dPluginId = u32; -}; - -} // namespace Libraries::Audio3d From bec1b9056f7ca5c94efef424985eb54a63cad518 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 13 Apr 2025 23:46:30 -0700 Subject: [PATCH 131/194] shader_recompiler: Misc shader fixes. (#2781) * shader_recompiler: Fix frexp exponent type. * shader_recompiler: Implement V_CMP_CLASS_F32 negative class mask. * shader_recompiler: Define operands for DS_ORDERED_COUNT. --- .../backend/spirv/emit_spirv_floating_point.cpp | 4 ++-- src/shader_recompiler/backend/spirv/spirv_emit_context.cpp | 4 ++-- src/shader_recompiler/frontend/format.cpp | 3 +-- src/shader_recompiler/frontend/translate/vector_alu.cpp | 4 +++- src/shader_recompiler/ir/reg.h | 1 + 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp index 8de903ce6..347c4cb0a 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp @@ -267,12 +267,12 @@ Id EmitFPFrexpSig64(EmitContext& ctx, Id value) { Id EmitFPFrexpExp32(EmitContext& ctx, Id value) { const auto frexp = ctx.OpFrexpStruct(ctx.frexp_result_f32, value); - return ctx.OpCompositeExtract(ctx.U32[1], frexp, 1); + return ctx.OpBitcast(ctx.U32[1], ctx.OpCompositeExtract(ctx.S32[1], frexp, 1)); } Id EmitFPFrexpExp64(EmitContext& ctx, Id value) { const auto frexp = ctx.OpFrexpStruct(ctx.frexp_result_f64, value); - return ctx.OpCompositeExtract(ctx.U32[1], frexp, 1); + return ctx.OpBitcast(ctx.U32[1], ctx.OpCompositeExtract(ctx.S32[1], frexp, 1)); } Id EmitFPOrdEqual16(EmitContext& ctx, Id lhs, Id rhs) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index e20cfeae2..8433251ff 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -153,9 +153,9 @@ void EmitContext::DefineArithmeticTypes() { full_result_i32x2 = Name(TypeStruct(S32[1], S32[1]), "full_result_i32x2"); full_result_u32x2 = Name(TypeStruct(U32[1], U32[1]), "full_result_u32x2"); - frexp_result_f32 = Name(TypeStruct(F32[1], U32[1]), "frexp_result_f32"); + frexp_result_f32 = Name(TypeStruct(F32[1], S32[1]), "frexp_result_f32"); if (info.uses_fp64) { - frexp_result_f64 = Name(TypeStruct(F64[1], U32[1]), "frexp_result_f64"); + frexp_result_f64 = Name(TypeStruct(F64[1], S32[1]), "frexp_result_f64"); } } diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 76b1cc818..f89f0a582 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -2784,8 +2784,7 @@ constexpr std::array InstructionFormatDS = {{ // 62 = DS_APPEND {InstClass::DsAppendCon, InstCategory::DataShare, 3, 1, ScalarType::Uint32, ScalarType::Uint32}, // 63 = DS_ORDERED_COUNT - {InstClass::GdsOrdCnt, InstCategory::DataShare, 3, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::GdsOrdCnt, InstCategory::DataShare, 3, 1, ScalarType::Uint32, ScalarType::Uint32}, // 64 = DS_ADD_U64 {InstClass::DsAtomicArith64, InstCategory::DataShare, 3, 1, ScalarType::Uint64, ScalarType::Uint64}, diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 22020d59f..da25f5434 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1010,8 +1010,10 @@ void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { value = ir.FPIsNan(src0); } else if ((class_mask & IR::FloatClassFunc::Infinity) == IR::FloatClassFunc::Infinity) { value = ir.FPIsInf(src0); + } else if ((class_mask & IR::FloatClassFunc::Negative) == IR::FloatClassFunc::Negative) { + value = ir.FPLessThanEqual(src0, ir.Imm32(-0.f)); } else { - UNREACHABLE(); + UNREACHABLE_MSG("Unsupported float class mask: {:#x}", static_cast(class_mask)); } } else { // We don't know the type yet, delay its resolution. diff --git a/src/shader_recompiler/ir/reg.h b/src/shader_recompiler/ir/reg.h index 40c4b61c3..622190cf0 100644 --- a/src/shader_recompiler/ir/reg.h +++ b/src/shader_recompiler/ir/reg.h @@ -25,6 +25,7 @@ enum class FloatClassFunc : u32 { NaN = SignalingNan | QuietNan, Infinity = PositiveInfinity | NegativeInfinity, + Negative = NegativeInfinity | NegativeNormal | NegativeDenorm | NegativeZero, Finite = NegativeNormal | NegativeDenorm | NegativeZero | PositiveNormal | PositiveDenorm | PositiveZero, }; From b8884d9591a878e9b6b1b92bdafe22c34cbf0c63 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Mon, 14 Apr 2025 10:26:39 +0100 Subject: [PATCH 132/194] Adding requirements to the App Bug Report template based on the Game Bug Report template (#2783) * Update app-bug-report.yaml Forcing people to fill information about their hardware to make it easier for debugging problems, example: "shad crashes after I open it" with this we can tell if the user has a CPU with an iGPU instead of asking them. * Update app-bug-report.yaml VRAM amount shouldn't be needed actually. --- .github/ISSUE_TEMPLATE/app-bug-report.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/app-bug-report.yaml b/.github/ISSUE_TEMPLATE/app-bug-report.yaml index cd540e06e..8959221a0 100644 --- a/.github/ISSUE_TEMPLATE/app-bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/app-bug-report.yaml @@ -53,3 +53,24 @@ body: placeholder: "Example: Windows 11, Arch Linux, MacOS 15" validations: required: true + - type: input + id: cpu + attributes: + label: CPU + placeholder: "Example: Intel Core i7-8700" + validations: + required: true + - type: input + id: gpu + attributes: + label: GPU + placeholder: "Example: nVidia GTX 1650" + validations: + required: true + - type: input + id: ram + attributes: + label: Amount of RAM in GB + placeholder: "Example: 16 GB" + validations: + required: true From 7d32efbd3130c5d5aa12729f7c8385bd028985ad Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 14 Apr 2025 12:26:54 +0300 Subject: [PATCH 133/194] New Crowdin updates (#2748) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Dutch) * New translations en_us.ts (Slovenian) * New translations en_us.ts (Arabic) * New translations en_us.ts (Arabic) --- src/qt_gui/translations/ar_SA.ts | 38 +- src/qt_gui/translations/nl_NL.ts | 4 +- src/qt_gui/translations/sl_SI.ts | 2081 ++++++++++++++++++++++++++++++ src/qt_gui/translations/zh_TW.ts | 840 ++++++------ 4 files changed, 2522 insertions(+), 441 deletions(-) create mode 100644 src/qt_gui/translations/sl_SI.ts diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 63ae733e8..f71ec7d3a 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -1892,43 +1892,43 @@ Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. - 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. 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 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 المرتبطة بأوامر فولكن، إضافةً إلى تخصيص أسماء لتصحيح الأخطاء للموارد.\nمن الأفضل تفعيل تشخيص الأعطال عند تفعيل هذه الخاصية.\nمفيد لبرامج مثل أداة تصحيح الأخطاء الرسومية. 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 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مفيد لبرامج مثل دوك. Save Data Path:\nThe folder where game save data will be saved. - Save Data Path:\nThe folder where game save data will be saved. + مسار حفظ البيانات:\nالمجلد الذي سيتم فيه حفظ بيانات تخزين اللعبة. Browse:\nBrowse for a folder to set as the save data path. - Browse:\nBrowse for a folder to set as the save data path. + تصفح:\nاختر مجلدًا لتحديده كمكان لحفظ بيانات التخزين. Release - Release + إصدار Nightly - Nightly + إصدار ليلي Set the volume of the background music. - Set the volume of the background music. + ضبط صوت الموسيقى في الخلفية. Enable Motion Controls - Enable Motion Controls + تفعيل التحكم بالحركة Save Data Path - Save Data Path + مسار بيانات الحفظ Browse @@ -1936,15 +1936,15 @@ async - async + غير متزامن sync - sync + متزامن Auto Select - Auto Select + تلقائي Directory to install games @@ -1952,11 +1952,11 @@ Directory to save data - Directory to save data + مسار الحفظ لبيانات الألعاب Video - Video + الفيديو Display Mode @@ -1992,7 +1992,7 @@ Separate Log Files:\nWrites a separate logfile for each game. - Separate Log Files:\nWrites a separate logfile for each game. + ملفات السجل المنفصلة:\nيتم كتابة سجل منفصل لكل لعبه. Trophy Notification Position @@ -2028,7 +2028,7 @@ Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + مجلد المستخدم القابل للنقل:\nتخزين إعدادات وبيانات المحاكي التي ستُطبق فقط على هذا الإصدار في المجلد الحالي. بعد إنشاء مجلد المستخدم القابل للنقل، يجب إعادة تشغيل التطبيق للبدء في استخدامه. Cannot create portable user folder @@ -2040,7 +2040,7 @@ Portable user folder created - Portable user folder created + تم إنشاء مجلد مستخدم محمول %1 successfully created. @@ -2048,7 +2048,7 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + افتح مجلد الصور/الأصوات الخاصة بالجوائز المخصصة:\nيمكنك إضافة صور مخصصة للجوائز وصوت مرفق.\nأضف الملفات إلى مجلد custom_trophy بالأسماء التالية:\ntrophy.wav أو trophy.mp3، bronze.png، gold.png، platinum.png، silver.png\nملاحظة: الصوت سيعمل فقط في الإصدارات التي تستخدم QT. diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 66872455e..2d75b74eb 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -7,7 +7,7 @@ AboutDialog About shadPS4 - About shadPS4 + Over shadPS4 shadPS4 is an experimental open-source emulator for the PlayStation 4. @@ -582,7 +582,7 @@ Could not open the file for reading - Could not open the file for reading + Could not open the file for writing diff --git a/src/qt_gui/translations/sl_SI.ts b/src/qt_gui/translations/sl_SI.ts new file mode 100644 index 000000000..ab61a5d3a --- /dev/null +++ b/src/qt_gui/translations/sl_SI.ts @@ -0,0 +1,2081 @@ + + + + + + AboutDialog + + About shadPS4 + About shadPS4 + + + shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 is an experimental open-source emulator for the PlayStation 4. + + + This software should not be used to play games you have not legally obtained. + This software should not be used to play games you have not legally obtained. + + + + CheatsPatches + + Cheats / Patches for + Cheats / Patches for + + + Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n + Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n + + + No Image Available + No Image Available + + + Serial: + Serial: + + + Version: + Version: + + + Size: + Size: + + + Select Cheat File: + Select Cheat File: + + + Repository: + Repository: + + + Download Cheats + Download Cheats + + + Delete File + Delete File + + + No files selected. + No files selected. + + + You can delete the cheats you don't want after downloading them. + You can delete the cheats you don't want after downloading them. + + + Do you want to delete the selected file?\n%1 + Do you want to delete the selected file?\n%1 + + + Select Patch File: + Select Patch File: + + + Download Patches + Download Patches + + + Save + Save + + + Cheats + Cheats + + + Patches + Patches + + + Error + Error + + + No patch selected. + No patch selected. + + + Unable to open files.json for reading. + Unable to open files.json for reading. + + + No patch file found for the current serial. + No patch file found for the current serial. + + + Unable to open the file for reading. + Unable to open the file for reading. + + + Unable to open the file for writing. + Unable to open the file for writing. + + + Failed to parse XML: + Failed to parse XML: + + + Success + Success + + + Options saved successfully. + Options saved successfully. + + + Invalid Source + Invalid Source + + + The selected source is invalid. + The selected source is invalid. + + + File Exists + File Exists + + + File already exists. Do you want to replace it? + File already exists. Do you want to replace it? + + + Failed to save file: + Failed to save file: + + + Failed to download file: + Failed to download file: + + + Cheats Not Found + Cheats Not Found + + + No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. + No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. + + + Cheats Downloaded Successfully + Cheats Downloaded Successfully + + + You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. + You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. + + + Failed to save: + Failed to save: + + + Failed to download: + Failed to download: + + + Download Complete + Download Complete + + + Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. + Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. + + + Failed to parse JSON data from HTML. + Failed to parse JSON data from HTML. + + + Failed to retrieve HTML page. + Failed to retrieve HTML page. + + + The game is in version: %1 + The game is in version: %1 + + + The downloaded patch only works on version: %1 + The downloaded patch only works on version: %1 + + + You may need to update your game. + You may need to update your game. + + + Incompatibility Notice + Incompatibility Notice + + + Failed to open file: + Failed to open file: + + + XML ERROR: + XML ERROR: + + + Failed to open files.json for writing + Failed to open files.json for writing + + + Author: + Author: + + + Directory does not exist: + Directory does not exist: + + + Failed to open files.json for reading. + Failed to open files.json for reading. + + + Name: + Name: + + + Can't apply cheats before the game is started + Can't apply cheats before the game is started + + + Close + Close + + + + CheckUpdate + + Auto Updater + Auto Updater + + + Error + Error + + + Network error: + Network error: + + + The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. + The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. + + + Failed to parse update information. + Failed to parse update information. + + + No pre-releases found. + No pre-releases found. + + + Invalid release data. + Invalid release data. + + + No download URL found for the specified asset. + No download URL found for the specified asset. + + + Your version is already up to date! + Your version is already up to date! + + + Update Available + Update Available + + + Update Channel + Update Channel + + + Current Version + Current Version + + + Latest Version + Latest Version + + + Do you want to update? + Do you want to update? + + + Show Changelog + Show Changelog + + + Check for Updates at Startup + Check for Updates at Startup + + + Update + Update + + + No + No + + + Hide Changelog + Hide Changelog + + + Changes + Changes + + + Network error occurred while trying to access the URL + Network error occurred while trying to access the URL + + + Download Complete + Download Complete + + + The update has been downloaded, press OK to install. + The update has been downloaded, press OK to install. + + + Failed to save the update file at + Failed to save the update file at + + + Starting Update... + Starting Update... + + + Failed to create the update script file + Failed to create the update script file + + + + CompatibilityInfoClass + + Fetching compatibility data, please wait + Fetching compatibility data, please wait + + + Cancel + Cancel + + + Loading... + Loading... + + + Error + Error + + + Unable to update compatibility data! Try again later. + Unable to update compatibility data! Try again later. + + + Unable to open compatibility_data.json for writing. + Unable to open compatibility_data.json for writing. + + + Unknown + Unknown + + + Nothing + Nothing + + + Boots + Boots + + + Menus + Menus + + + Ingame + Ingame + + + Playable + Playable + + + + ControlSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Stick Deadzone (def:2 max:127) + Left Stick Deadzone (def:2 max:127) + + + Left Deadzone + Left Deadzone + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + L1 / LB + L1 / LB + + + L2 / LT + L2 / LT + + + Back + Back + + + R1 / RB + R1 / RB + + + R2 / RT + R2 / RT + + + L3 + L3 + + + Options / Start + Options / Start + + + R3 + R3 + + + Face Buttons + Face Buttons + + + Triangle / Y + Triangle / Y + + + Square / X + Square / X + + + Circle / B + Circle / B + + + Cross / A + Cross / A + + + Right Stick Deadzone (def:2, max:127) + Right Stick Deadzone (def:2, max:127) + + + Right Deadzone + Right Deadzone + + + Right Stick + Right Stick + + + Color Adjustment + Color Adjustment + + + R: + R: + + + G: + G: + + + B: + B: + + + Override Lightbar Color + Override Lightbar Color + + + Override Color + Override Color + + + Unable to Save + Unable to Save + + + Cannot bind axis values more than once + Cannot bind axis values more than once + + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + + + + EditorDialog + + Edit Keyboard + Mouse and Controller input bindings + Edit Keyboard + Mouse and Controller input bindings + + + Use Per-Game configs + Use Per-Game configs + + + Error + Error + + + Could not open the file for reading + Could not open the file for reading + + + Could not open the file for writing + Could not open the file for writing + + + Save Changes + Save Changes + + + Do you want to save changes? + Do you want to save changes? + + + Help + Help + + + Do you want to reset your custom default config to the original default config? + Do you want to reset your custom default config to the original default config? + + + Do you want to reset this config to your custom default config? + Do you want to reset this config to your custom default config? + + + Reset to Default + Reset to Default + + + + ElfViewer + + Open Folder + Open Folder + + + + GameInfoClass + + Loading game list, please wait :3 + Loading game list, please wait :3 + + + Cancel + Cancel + + + Loading... + Loading... + + + + GameInstallDialog + + shadPS4 - Choose directory + shadPS4 - Choose directory + + + Directory to install games + Directory to install games + + + Browse + Browse + + + Error + Error + + + Directory to install DLC + Directory to install DLC + + + + GameListFrame + + Icon + Icon + + + Name + Name + + + Serial + Serial + + + Compatibility + Compatibility + + + Region + Region + + + Firmware + Firmware + + + Size + Size + + + Version + Version + + + Path + Path + + + Play Time + Play Time + + + 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 + + + Click to see details on github + Click to see details on github + + + Last updated + Last updated + + + + GameListUtils + + B + B + + + KB + KB + + + MB + MB + + + GB + GB + + + TB + TB + + + + GuiContextMenus + + Create Shortcut + Create Shortcut + + + Cheats / Patches + Cheats / Patches + + + SFO Viewer + SFO Viewer + + + Trophy Viewer + Trophy Viewer + + + Open Folder... + Open Folder... + + + Open Game Folder + Open Game Folder + + + Open Save Data Folder + Open Save Data Folder + + + Open Log Folder + Open Log Folder + + + Copy info... + Copy info... + + + Copy Name + Copy Name + + + Copy Serial + Copy Serial + + + Copy Version + Copy Version + + + Copy Size + Copy Size + + + Copy All + Copy All + + + Delete... + Delete... + + + Delete Game + Delete Game + + + Delete Update + Delete Update + + + Delete DLC + Delete DLC + + + Delete Trophy + Delete Trophy + + + Compatibility... + Compatibility... + + + Update database + Update database + + + View report + View report + + + Submit a report + Submit a report + + + Shortcut creation + Shortcut creation + + + Shortcut created successfully! + Shortcut created successfully! + + + Error + Error + + + Error creating shortcut! + Error creating shortcut! + + + Game + Game + + + This game has no update to delete! + This game has no update to delete! + + + Update + Update + + + This game has no DLC to delete! + This game has no DLC to delete! + + + DLC + DLC + + + Delete %1 + Delete %1 + + + Are you sure you want to delete %1's %2 directory? + Are you sure you want to delete %1's %2 directory? + + + Open Update Folder + Open Update Folder + + + Delete Save Data + Delete Save Data + + + This game has no update folder to open! + This game has no update folder to open! + + + No log file found for this game! + No log file found for this game! + + + Failed to convert icon. + Failed to convert icon. + + + This game has no save data to delete! + This game has no save data to delete! + + + This game has no saved trophies to delete! + This game has no saved trophies to delete! + + + Save Data + Save Data + + + Trophy + Trophy + + + SFO Viewer for + SFO Viewer for + + + + HelpDialog + + Quickstart + Quickstart + + + FAQ + FAQ + + + Syntax + Syntax + + + Special Bindings + Special Bindings + + + Keybindings + Keybindings + + + + KBMSettings + + Configure Controls + Configure Controls + + + D-Pad + D-Pad + + + Up + Up + + + unmapped + unmapped + + + Left + Left + + + Right + Right + + + Down + Down + + + Left Analog Halfmode + Left Analog Halfmode + + + hold to move left stick at half-speed + hold to move left stick at half-speed + + + Left Stick + Left Stick + + + Config Selection + Config Selection + + + Common Config + Common Config + + + Use per-game configs + Use per-game configs + + + L1 + L1 + + + L2 + L2 + + + Text Editor + Text Editor + + + Help + Help + + + R1 + R1 + + + R2 + R2 + + + L3 + L3 + + + Touchpad Click + Touchpad Click + + + Mouse to Joystick + Mouse to Joystick + + + *press F7 ingame to activate + *press F7 ingame to activate + + + R3 + R3 + + + Options + Options + + + Mouse Movement Parameters + Mouse Movement Parameters + + + note: click Help Button/Special Keybindings for more information + note: click Help Button/Special Keybindings for more information + + + Face Buttons + Face Buttons + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Right Analog Halfmode + Right Analog Halfmode + + + hold to move right stick at half-speed + hold to move right stick at half-speed + + + Right Stick + Right Stick + + + Speed Offset (def 0.125): + Speed Offset (def 0.125): + + + Copy from Common Config + Copy from Common Config + + + Deadzone Offset (def 0.50): + Deadzone Offset (def 0.50): + + + Speed Multiplier (def 1.0): + Speed Multiplier (def 1.0): + + + Common Config Selected + Common Config Selected + + + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + + + Copy values from Common Config + Copy values from Common Config + + + Do you want to overwrite existing mappings with the mappings from the Common Config? + Do you want to overwrite existing mappings with the mappings from the Common Config? + + + Unable to Save + Unable to Save + + + Cannot bind any unique input more than once + Cannot bind any unique input more than once + + + Press a key + Press a key + + + Cannot set mapping + Cannot set mapping + + + Mousewheel cannot be mapped to stick outputs + Mousewheel cannot be mapped to stick outputs + + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Cancel + Cancel + + + + MainWindow + + Open/Add Elf Folder + Open/Add Elf Folder + + + Boot Game + Boot Game + + + Check for Updates + Check for Updates + + + About shadPS4 + About shadPS4 + + + Configure... + Configure... + + + Recent Games + Recent Games + + + Open shadPS4 Folder + Open shadPS4 Folder + + + Exit + Exit + + + Exit shadPS4 + Exit shadPS4 + + + Exit the application. + Exit the application. + + + Show Game List + Show Game List + + + Game List Refresh + Game List Refresh + + + Tiny + Tiny + + + Small + Small + + + Medium + Medium + + + Large + Large + + + List View + List View + + + Grid View + Grid View + + + Elf Viewer + Elf Viewer + + + Game Install Directory + Game Install Directory + + + Download Cheats/Patches + Download Cheats/Patches + + + Dump Game List + Dump Game List + + + Trophy Viewer + Trophy Viewer + + + No games found. Please add your games to your library first. + No games found. Please add your games to your library first. + + + Search... + Search... + + + File + File + + + View + View + + + Game List Icons + Game List Icons + + + Game List Mode + Game List Mode + + + Settings + Settings + + + Utils + Utils + + + Themes + Themes + + + Help + Help + + + Dark + Dark + + + Light + Light + + + Green + Green + + + Blue + Blue + + + Violet + Violet + + + toolBar + toolBar + + + Game List + Game List + + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + + + Download Cheats For All Installed Games + Download Cheats For All Installed Games + + + Download Patches For All Games + Download Patches For All Games + + + Download Complete + Download Complete + + + You have downloaded cheats for all the games you have installed. + You have downloaded cheats for all the games you have installed. + + + Patches Downloaded Successfully! + Patches Downloaded Successfully! + + + All Patches available for all games have been downloaded. + All Patches available for all games have been downloaded. + + + Games: + Games: + + + ELF files (*.bin *.elf *.oelf) + ELF files (*.bin *.elf *.oelf) + + + Game Boot + Game Boot + + + Only one file can be selected! + Only one file can be selected! + + + Run Game + Run Game + + + Eboot.bin file not found + Eboot.bin file not found + + + Game is already running! + Game is already running! + + + shadPS4 + shadPS4 + + + Play + Play + + + Pause + Pause + + + Stop + Stop + + + Restart + Restart + + + Full Screen + Full Screen + + + Controllers + Controllers + + + Keyboard + Keyboard + + + Refresh List + Refresh List + + + Resume + Resume + + + Show Labels Under Icons + Show Labels Under Icons + + + + SettingsDialog + + Settings + Settings + + + General + General + + + System + System + + + Console Language + Console Language + + + Emulator Language + Emulator Language + + + Emulator + Emulator + + + Default tab when opening settings + Default tab when opening settings + + + Show Game Size In List + Show Game Size In List + + + Show Splash + Show Splash + + + Enable Discord Rich Presence + Enable Discord Rich Presence + + + Username + Username + + + Trophy Key + Trophy Key + + + Trophy + Trophy + + + Open the custom trophy images/sounds folder + Open the custom trophy images/sounds folder + + + Logger + Logger + + + Log Type + Log Type + + + Log Filter + Log Filter + + + Open Log Location + Open Log Location + + + Input + Input + + + Cursor + Cursor + + + Hide Cursor + Hide Cursor + + + Hide Cursor Idle Timeout + Hide Cursor Idle Timeout + + + s + s + + + Controller + Controller + + + Back Button Behavior + Back Button Behavior + + + Graphics + Graphics + + + GUI + GUI + + + User + User + + + Graphics Device + Graphics Device + + + Vblank Divider + Vblank Divider + + + Advanced + Advanced + + + Enable Shaders Dumping + Enable Shaders Dumping + + + Enable NULL GPU + Enable NULL GPU + + + Enable HDR + Enable HDR + + + Paths + Paths + + + Game Folders + Game Folders + + + Add... + Add... + + + Remove + Remove + + + Debug + Debug + + + Enable Debug Dumping + Enable Debug Dumping + + + Enable Vulkan Validation Layers + Enable Vulkan Validation Layers + + + Enable Vulkan Synchronization Validation + Enable Vulkan Synchronization Validation + + + Enable RenderDoc Debugging + Enable RenderDoc Debugging + + + 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 + Update + + + Check for Updates at Startup + Check for Updates at Startup + + + Always Show Changelog + Always Show Changelog + + + Update Channel + Update Channel + + + Check for Updates + Check for Updates + + + GUI Settings + GUI Settings + + + Title Music + Title Music + + + Disable Trophy Notification + Disable Trophy Notification + + + Background Image + Background Image + + + Show Background Image + Show Background Image + + + Opacity + Opacity + + + Play title music + 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 + Volume + + + Save + Save + + + Apply + Apply + + + Restore Defaults + Restore Defaults + + + Close + Close + + + Point your mouse at an option to display its description. + Point your mouse at an option to display its description. + + + Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. + Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. + + + Emulator Language:\nSets the language of the emulator's user interface. + Emulator Language:\nSets the language of the emulator's user interface. + + + Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. + Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. + + + Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + + + Username:\nSets the PS4's account username, which may be displayed by some games. + Username:\nSets the PS4's account username, which may be displayed by some games. + + + Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + + + Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. + Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. + + + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + + + 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. + 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. + + + Background Image:\nControl the opacity of the game background image. + Background Image:\nControl the opacity of the game background image. + + + Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + 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). + 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). + + + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + + + Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + + + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + + + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + Update Compatibility Database:\nImmediately update the compatibility database. + Update Compatibility Database:\nImmediately update the compatibility database. + + + Never + Never + + + Idle + Idle + + + Always + Always + + + Touchpad Left + Touchpad Left + + + Touchpad Right + Touchpad Right + + + Touchpad Center + Touchpad Center + + + None + None + + + Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. + Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. + + + Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. + Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. + + + Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! + Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! + + + Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. + Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. + + + Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + + + Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + + + Game Folders:\nThe list of folders to check for installed games. + Game Folders:\nThe list of folders to check for installed games. + + + Add:\nAdd a folder to the list. + Add:\nAdd a folder to the list. + + + Remove:\nRemove a folder from the list. + Remove:\nRemove a folder from the list. + + + Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. + Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. + + + Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. + Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. + + + Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. + Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. + + + Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. + Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. + + + Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + + + 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. + 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. + + + Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + + + 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 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. + + + 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 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. + + + Save Data Path:\nThe folder where game save data will be saved. + Save Data Path:\nThe folder where game save data will be saved. + + + Browse:\nBrowse for a folder to set as the save data path. + Browse:\nBrowse for a folder to set as the save data path. + + + Release + Release + + + Nightly + Nightly + + + Set the volume of the background music. + Set the volume of the background music. + + + Enable Motion Controls + Enable Motion Controls + + + Save Data Path + Save Data Path + + + Browse + Browse + + + async + async + + + sync + sync + + + Auto Select + Auto Select + + + Directory to install games + Directory to install games + + + Directory to save data + Directory to save data + + + Video + Video + + + Display Mode + Display Mode + + + Windowed + Windowed + + + Fullscreen + Fullscreen + + + Fullscreen (Borderless) + Fullscreen (Borderless) + + + Window Size + Window Size + + + W: + W: + + + H: + H: + + + Separate Log Files + Separate Log Files + + + Separate Log Files:\nWrites a separate logfile for each game. + Separate Log Files:\nWrites a separate logfile for each game. + + + Trophy Notification Position + Trophy Notification Position + + + Left + Left + + + Right + Right + + + Top + Top + + + Bottom + Bottom + + + Notification Duration + Notification Duration + + + Portable User Folder + Portable User Folder + + + Create Portable User Folder from Common User Folder + Create Portable User Folder from Common User Folder + + + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + + + Cannot create portable user folder + Cannot create portable user folder + + + %1 already exists + %1 already exists + + + Portable user folder created + Portable user folder created + + + %1 successfully created. + %1 successfully created. + + + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + + + TrophyViewer + + Trophy Viewer + Trophy Viewer + + + Select Game: + Select Game: + + + Progress + Progress + + + Show Earned Trophies + Show Earned Trophies + + + Show Not Earned Trophies + Show Not Earned Trophies + + + Show Hidden Trophies + Show Hidden Trophies + + + diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 077455558..bd051651d 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -7,26 +7,26 @@ AboutDialog About shadPS4 - About shadPS4 + 關於 shadPS4 shadPS4 is an experimental open-source emulator for the PlayStation 4. - 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. - This software should not be used to play games you have not legally obtained. + 不得用此軟體來遊玩你未曾合法獲取的遊戲。 CheatsPatches Cheats / Patches for - Cheats / Patches for + 金手指/修補程式: Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n - 作弊/補丁為實驗性功能。\n請小心使用。\n\n透過選擇儲存庫並點擊下載按鈕來單獨下載作弊程式。\n在“補丁”標籤頁中,您可以一次下載所有補丁,選擇要使用的補丁並保存您的選擇。\n\n由於我們不開發作弊/補丁,\n請將問題報告給作弊程式的作者。\n\n創建了新的作弊程式?請訪問:\n + 金手指/修補檔屬於試驗性質功能。\n請小心使用。\n\n透過選取儲存庫並點擊下載按鈕來單獨下載作弊程式。\n在“修補檔”標籤頁中,你可以一次下載所有修補檔,選擇要使用的補丁並保存您的選擇。\n\n由於我們不負責開發金手指/修補檔,\n請將問題匯報給作弊程式的作者。\n\n建立了新的作弊程式?請瀏覽:\n No Image Available @@ -34,27 +34,27 @@ Serial: - 序號: + 序號: Version: - 版本: + 版本: Size: - 大小: + 大小: Select Cheat File: - 選擇作弊檔案: + 選取金手指檔案: Repository: - 儲存庫: + 儲存庫: Download Cheats - 下載作弊碼 + 下載金手指 Delete File @@ -62,19 +62,19 @@ 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 + 確定要刪除選取的檔案?\n%1 Select Patch File: - 選擇修補檔案: + 選取修補檔案: Download Patches @@ -86,7 +86,7 @@ Cheats - 作弊碼 + 金手指 Patches @@ -98,27 +98,27 @@ No patch selected. - 未選擇修補檔。 + 未選取修補檔。 Unable to open files.json for reading. - 無法打開 files.json 進行讀取。 + 無法開啟 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 失敗: + 解析 XML 失敗: Success @@ -126,7 +126,7 @@ Options saved successfully. - 選項已成功儲存。 + 已成功儲存選項。 Invalid Source @@ -134,7 +134,7 @@ The selected source is invalid. - 選擇的來源無效。 + 選取的來源無效。 File Exists @@ -142,39 +142,39 @@ File already exists. Do you want to replace it? - 檔案已存在。您是否希望替換它? + 檔案已存在,您要取代嗎? Failed to save file: - 無法儲存檔案: + 無法儲存檔案: Failed to download file: - 無法下載檔案: + 無法下載檔案: Cheats Not Found - 未找到作弊碼 + 找不到金手指 No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. - 在此版本的儲存庫中未找到該遊戲的作弊碼,請嘗試另一個儲存庫或不同版本的遊戲。 + 在此版本的儲存庫中未找到該遊戲的金手指,請嘗試另一個儲存庫或不同版本的遊戲。 Cheats Downloaded Successfully - 作弊碼下載成功 + 全手指下載成功 You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. - 您已成功下載該遊戲版本的作弊碼 從選定的儲存庫中。 您可以嘗試從其他儲存庫下載,如果可用,您也可以選擇從列表中選擇檔案來使用它。 + 你已成功從選取的儲存庫中下載該遊戲版本的金手指。 可用的話,你可以嘗試從其他儲存庫下載,你也可以從列表中選取檔案來使用它。 Failed to save: - 儲存失敗: + 儲存失敗: Failed to download: - 下載失敗: + 下載失敗: Download Complete @@ -182,15 +182,15 @@ Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. - 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像作弊碼那樣為每個遊戲單獨下載。如果補丁未顯示,可能是該補丁不適用於特定的序號和遊戲版本。 + 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像金手指那樣為每個遊戲單獨下載。如果修補檔仍未顯示,可能該修補檔是不適用於特定的序號和遊戲版本。 Failed to parse JSON data from HTML. - 無法從 HTML 解析 JSON 數據。 + 無法從 HTML 分析 JSON 資料。 Failed to retrieve HTML page. - 無法檢索 HTML 頁面。 + 無法接收 HTML 頁面。 The game is in version: %1 @@ -198,11 +198,11 @@ The downloaded patch only works on version: %1 - 下載的補丁僅適用於版本: %1 + 下載的修補檔僅適用於版本: %1 You may need to update your game. - 您可能需要更新遊戲。 + 你可能需要更新你的遊戲。 Incompatibility Notice @@ -210,35 +210,35 @@ Failed to open file: - 無法打開檔案: + 無法開啟檔案: XML ERROR: - XML 錯誤: + XML 錯誤: Failed to open files.json for writing - 無法打開 files.json 進行寫入 + 無法開啟 files.json 進行寫入 Author: - 作者: + 作者: Directory does not exist: - 目錄不存在: + 目錄不存在: Failed to open files.json for reading. - 無法打開 files.json 進行讀取。 + 無法開啟 files.json 進行讀取。 Name: - 名稱: + 名稱: Can't apply cheats before the game is started - 在遊戲開始之前無法應用作弊。 + 在遊戲'開始之前無法套用金手指 Close @@ -265,19 +265,19 @@ Failed to parse update information. - 無法解析更新資訊。 + 無法分析更新資訊。 No pre-releases found. - 未找到預發布版本。 + 未找到預發佈版本。 Invalid release data. - 無效的發行數據。 + 無效的發佈資料。 No download URL found for the specified asset. - 未找到指定資產的下載 URL。 + 未找到指定資源的下載 URL。 Your version is already up to date! @@ -293,7 +293,7 @@ Current Version - 當前版本 + 目前版本 Latest Version @@ -301,7 +301,7 @@ Do you want to update? - 您想要更新嗎? + 你要更新嗎? Show Changelog @@ -321,7 +321,7 @@ Hide Changelog - 隱藏變更日誌 + 隱藏變更日誌紀錄 Changes @@ -329,7 +329,7 @@ Network error occurred while trying to access the URL - 嘗試訪問 URL 時發生網路錯誤 + 嘗試存取 URL 時發生了網路錯誤 Download Complete @@ -337,11 +337,11 @@ The update has been downloaded, press OK to install. - 更新已下載,按 OK 安裝。 + 已下載更新,按 OK 安裝。 Failed to save the update file at - 無法將更新文件保存到 + 無法將更新檔案儲存到 Starting Update... @@ -349,7 +349,7 @@ Failed to create the update script file - 無法創建更新腳本文件 + 無法建立更新程式碼檔案 @@ -364,7 +364,7 @@ Loading... - 載入中... + 正在載入… Error @@ -388,134 +388,134 @@ Boots - 靴子 + 可啟動 Menus - 選單 + 可顯示標題畫面 Ingame - 遊戲內 + 可進入遊戲 Playable - 可玩 + 可如常遊玩 ControlSettings Configure Controls - 操控設定 + 操控組態 D-Pad - D-Pad + 十字方向鍵 Up - Up + Left - Left + Right - Right + Down - Down + Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + 左搖桿無效區域(預設:2 最大:127) Left Deadzone - Left Deadzone + 左無效區域 Left Stick - Left Stick + 左搖桿 Config Selection - Config Selection + 選取組態 Common Config - Common Config + 通用組態 Use per-game configs - Use per-game configs + 使用個別遊戲組態 L1 / LB - L1 / LB + L1 / LB L2 / LT - L2 / LT + L2 / LT Back - Back + Back R1 / RB - R1 / RB + R1 / RB R2 / RT - R2 / RT + R2 / RT L3 - L3 + L3 Options / Start - Options / Start + Options / Start R3 - R3 + R3 Face Buttons - Face Buttons + 功能鍵(動作按鈕) Triangle / Y - Triangle / Y + 三角 / Y Square / X - Square / X + 正方 / X Circle / B - Circle / B + 圖形 / B Cross / A - Cross / A + 交叉 / A Right Stick Deadzone (def:2, max:127) - Right Stick Deadzone (def:2, max:127) + 右搖桿無效區域(預設:2 最大:127) Right Deadzone - Right Deadzone + 右無效區域 Right Stick - Right Stick + 右搖桿 Color Adjustment @@ -523,35 +523,35 @@ R: - R: + 紅: G: - G: + 綠: B: - B: + 藍: Override Lightbar Color - Override Lightbar Color + 覆寫燈條顏色 Override Color - Override Color + 覆寫顏色 Unable to Save - 無法保存 + 無法儲存 Cannot bind axis values more than once - Cannot bind axis values more than once + 不能同時連結多個方向軸設定值 Save - 保存 + 儲存 Apply @@ -559,7 +559,7 @@ Restore Defaults - Restore Defaults + 還原成預設值 Cancel @@ -570,11 +570,11 @@ EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + 編輯鍵盤滑鼠和控制器按鈕連結 Use Per-Game configs - Use Per-Game configs + 使用個別遊戲組態 Error @@ -582,11 +582,11 @@ Could not open the file for reading - Could not open the file for reading + 無法開啟檔案進行讀取 Could not open the file for writing - Could not open the file for writing + 無法開啟檔案進行寫入 Save Changes @@ -594,68 +594,68 @@ Do you want to save changes? - Do you want to save changes? + 你要儲存變更嗎? Help - Help + 幫助 Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + 你要將自訂預設組態還原成原始預設組態嗎? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + 你要將此組態還原成自訂預設組態嗎? Reset to Default - Reset to Default + 還原成預設值 ElfViewer Open Folder - Open Folder + 開啟資料夾 GameInfoClass Loading game list, please wait :3 - Loading game list, please wait :3 + 正在載入遊戲列表,請稍候 :3 Cancel - Cancel + 取消 Loading... - Loading... + 正在載入… GameInstallDialog shadPS4 - Choose directory - shadPS4 - Choose directory + shadPS4 - 選取檔案目錄 Directory to install games - Directory to install games + 要安裝遊戲的目錄 Browse - Browse + 瀏覽 Error - Error + 錯誤 Directory to install DLC - Directory to install DLC + 要安裝DLC的目錄 @@ -674,7 +674,7 @@ Compatibility - Compatibility + 相容性 Region @@ -682,7 +682,7 @@ Firmware - 固件 + 韌體 Size @@ -702,43 +702,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 + 遊戲有嚴重的BUG或者效能過低而無法遊玩 Game can be completed with playable performance and no major glitches - Game can be completed with playable performance and no major glitches + 遊戲能在可以合理遊玩的效能下破關也沒有重大BUG Click to see details on github @@ -753,447 +753,447 @@ GameListUtils B - B + B KB - KB + KB MB - MB + MB GB - GB + GB TB - TB + TB GuiContextMenus Create Shortcut - Create Shortcut + 建立捷徑 Cheats / Patches - Zuòbì / Xiūbǔ chéngshì + 金手指/修補程式 SFO Viewer - SFO Viewer + SFO 檢視器 Trophy Viewer - Trophy Viewer + 獎盃檢視器 Open Folder... - 打開資料夾... + 開啟資料夾… Open Game Folder - 打開遊戲資料夾 + 開啟遊戲資料夾 Open Save Data Folder - 打開存檔資料夾 + 開啟遊戲存檔資料夾 Open Log Folder - 打開日誌資料夾 + 開啟日誌紀錄資料夾 Copy info... - Copy info... + 複製資訊... Copy Name - Copy Name + 複製名稱 Copy Serial - Copy Serial + 複製序號 Copy Version - Copy Version + 複製版本資訊 Copy Size - Copy Size + 複製大小 Copy All - Copy All + 全部複製 Delete... - Delete... + 刪除… Delete Game - Delete Game + 刪除遊戲 Delete Update - Delete Update + 刪除更新 Delete DLC - Delete DLC + 刪除DLC Delete Trophy - Delete Trophy + 刪除獎盃 Compatibility... - Compatibility... + 相容性… Update database - Update database + 更新資料庫 View report - View report + 檢視報告 Submit a report - Submit a report + 提交報告 Shortcut creation - Shortcut creation + 建立捷徑 Shortcut created successfully! - Shortcut created successfully! + 成功建立捷徑! Error - Error + 錯誤 Error creating shortcut! - Error creating shortcut! + 建立捷徑出錯! Game - Game + 遊戲 This game has no update to delete! - This game has no update to delete! + 此遊戲沒有可刪除的更新! Update - Update + 更新 This game has no DLC to delete! - This game has no DLC to delete! + 此遊戲沒有可刪除的DLC! DLC - DLC + DLC Delete %1 - Delete %1 + 刪除 %1 Are you sure you want to delete %1's %2 directory? - Are you sure you want to delete %1's %2 directory? + 確定要刪除 %1' 的%2目錄? Open Update Folder - Open Update Folder + 開啟更新資料夾 Delete Save Data - Delete Save Data + 刪除遊戲存檔 This game has no update folder to open! - This game has no update folder to open! + 此遊戲沒有可開啟的更新資料夾! No log file found for this game! - No log file found for this game! + 找不到此遊戲的日誌紀錄檔! Failed to convert icon. - Failed to convert icon. + 轉換圖示失敗。 This game has no save data to delete! - This game has no save data to delete! + 此遊戲沒有可刪除的遊戲存檔! This game has no saved trophies to delete! - This game has no saved trophies to delete! + 此遊戲沒有可刪除的已儲存獎盃! Save Data - Save Data + 遊戲存檔 Trophy - Trophy + 獎盃 SFO Viewer for - SFO Viewer for + SFO 檢視器: HelpDialog Quickstart - Quickstart + 快速入門 FAQ - FAQ + 常見問答 Syntax - Syntax + 語法 Special Bindings - Special Bindings + 特殊連結 Keybindings - Keybindings + 按鍵連結 KBMSettings Configure Controls - Configure Controls + 操控組態 D-Pad - D-Pad + 十字方向鍵 Up - Up + unmapped - unmapped + 鍵位未有對應 Left - Left + Right - Right + Down - Down + Left Analog Halfmode - Left Analog Halfmode + 左類比搖桿半速模式 hold to move left stick at half-speed - hold to move left stick at half-speed + 按住可半速移動左搖桿 Left Stick - Left Stick + 左搖桿 Config Selection - Config Selection + 選取組態 Common Config - Common Config + 通用組態 Use per-game configs - Use per-game configs + 使用個別遊戲組態 L1 - L1 + L1 L2 - L2 + L2 Text Editor - Text Editor + 文字編輯器 Help - Help + 幫助 R1 - R1 + R1 R2 - R2 + R2 L3 - L3 + L3 Touchpad Click - Touchpad Click + 觸控板點擊 Mouse to Joystick - Mouse to Joystick + 滑鼠操控操縱桿 *press F7 ingame to activate - *press F7 ingame to activate + *在遊戲中按F7啟動 R3 - R3 + R3 Options - Options + Options Mouse Movement Parameters - Mouse Movement Parameters + 滑鼠指標移動參數 note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + 注意:點擊 幫助 按鈕/特殊按鍵連結以取得更多資訊 Face Buttons - Face Buttons + 功能鍵(動作按鈕) Triangle - Triangle + 三角 Square - Square + 正方 Circle - Circle + 圓形 Cross - Cross + 交叉 Right Analog Halfmode - Right Analog Halfmode + 右類比搖桿半速模式 hold to move right stick at half-speed - hold to move right stick at half-speed + 按住可半速移動右搖桿 Right Stick - Right Stick + 右搖桿 Speed Offset (def 0.125): - Speed Offset (def 0.125): + 速度偏移量(預設為 0.125): Copy from Common Config - Copy from Common Config + 從通用組態中複製設定值 Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + 無效區偏移量(預設為 0.50): Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + 速度倍數(預設為 1.0): Common Config Selected - Common Config Selected + 已選取通用組態 This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. + 此按鈕會將通用組態中的鍵位對應複製到目前選取的組態檔案,選取通用組態時無法使用此按鈕。 Copy values from Common Config - Copy values from Common Config + 從通用組態中複製設定值 Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + 你要將通用組態中的鍵位對應覆寫到現有的鍵位對應嗎? Unable to Save - Unable to Save + 無法儲存 Cannot bind any unique input more than once - Cannot bind any unique input more than once + 任何唯一的鍵位都不能重複連結 Press a key - Press a key + 按下按鍵 Cannot set mapping - Cannot set mapping + 無法設定鍵位對應 Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + 滑鼠滾輪無法對應到搖桿輸出 Save - Save + 儲存 Apply - Apply + 套用 Restore Defaults - Restore Defaults + 還原成預設值 Cancel - Cancel + 取消 MainWindow Open/Add Elf Folder - Open/Add Elf Folder + 開啟/新增 Elf 資料夾 Boot Game - Boot Game + 啟動遊戲 Check for Updates @@ -1201,119 +1201,119 @@ About shadPS4 - About shadPS4 + 關於 shadPS4 Configure... - Configure... + 組態… Recent Games - Recent Games + 最近遊玩的遊戲 Open shadPS4 Folder - Open shadPS4 Folder + 開啟 shadPS4 資料夾 Exit - Exit + 退出 Exit shadPS4 - Exit shadPS4 + 退出 shadPS4 Exit the application. - Exit the application. + 退出應用程式。 Show Game List - Show Game List + 顯示遊戲列表 Game List Refresh - Game List Refresh + 重新整理遊戲列表 Tiny - Tiny + 微小 Small - Small + Medium - Medium + Large - Large + List View - List View + 以列表顯示 Grid View - Grid View + 以網格顯視 Elf Viewer - Elf Viewer + Elf 檢視器 Game Install Directory - Game Install Directory + 遊戲安裝目錄 Download Cheats/Patches - Xiàzài Zuòbì / Xiūbǔ chéngshì + 下載金手指/修補檔 Dump Game List - Dump Game List + 偵印遊戲列表 Trophy Viewer - Trophy Viewer + 獎盃檢視器 No games found. Please add your games to your library first. - No games found. Please add your games to your library first. + 找不到遊戲,請先將你的遊戲加入遊戲庫中。 Search... - Search... + 搜尋… File - File + 檔案 View - View + 檢視 Game List Icons - Game List Icons + 遊戲列表圖示 Game List Mode - Game List Mode + 遊戲列表模式 Settings - Settings + 設定 Utils - Utils + 工具 Themes - Themes + 主題 Help @@ -1321,27 +1321,27 @@ Dark - Dark + 深色 Light - Light + 淺色 Green - Green + 綠色 Blue - Blue + 藍色 Violet - Violet + 紫羅蘭色 toolBar - toolBar + 工具列 Game List @@ -1353,7 +1353,7 @@ Download Cheats For All Installed Games - 下載所有已安裝遊戲的作弊碼 + 下載所有已安裝遊戲的金手指 Download Patches For All Games @@ -1365,7 +1365,7 @@ You have downloaded cheats for all the games you have installed. - 您已經下載了所有已安裝遊戲的作弊碼。 + 你已下載所有已安裝遊戲的金手指。 Patches Downloaded Successfully! @@ -1377,7 +1377,7 @@ Games: - 遊戲: + 遊戲: ELF files (*.bin *.elf *.oelf) @@ -1389,102 +1389,102 @@ Only one file can be selected! - 只能選擇一個檔案! + 只能選取一個檔案! Run Game - Run Game + 執行遊戲 Eboot.bin file not found - Eboot.bin file not found + 找不到 Eboot.bin 檔案 Game is already running! - Game is already running! + 已在執行遊戲! shadPS4 - shadPS4 + shadPS4 Play - Play + 遊玩 Pause - Pause + 暫停 Stop - Stop + 停止 Restart - Restart + 重新啟動 Full Screen - Full Screen + 全螢幕 Controllers - Controllers + 控制器 Keyboard - Keyboard + 鍵盤 Refresh List - Refresh List + 重新整理列表 Resume - Resume + 繼續 Show Labels Under Icons - Show Labels Under Icons + 在圖示下方顯示標籤 SettingsDialog Settings - Settings + 設定 General - General + 一般 System - System + 系統 Console Language - Console Language + 主機語言 Emulator Language - Emulator Language + 模擬器語言 Emulator - Emulator + 模擬器 Default tab when opening settings - 打開設置時的默認選項卡 + 開啟設定時的預設選項頁面 Show Game Size In List - 顯示遊戲大小在列表中 + 在列表中顯示遊戲大小 Show Splash - Show Splash + 顯示啟動畫面 Enable Discord Rich Presence @@ -1492,35 +1492,35 @@ Username - Username + 使用者名稱 Trophy Key - Trophy Key + 獎盃金鑰 Trophy - Trophy + 獎盃 Open the custom trophy images/sounds folder - Open the custom trophy images/sounds folder + 開啟自訂獎盃圖像/音效資料夾 Logger - Logger + 日誌紀錄器 Log Type - Log Type + 日誌記錄類型 Log Filter - Log Filter + 日誌紀錄過濾器 Open Log Location - 開啟日誌位置 + 開啟日誌記錄位置 Input @@ -1528,19 +1528,19 @@ Cursor - 游標 + 滑鼠遊標 Hide Cursor - 隱藏游標 + 隱藏滑鼠指標 Hide Cursor Idle Timeout - 游標空閒超時隱藏 + 空閒滑鼠指標逾時隱藏 s - s + Controller @@ -1552,7 +1552,7 @@ Graphics - Graphics + 圖形 GUI @@ -1564,27 +1564,27 @@ Graphics Device - Graphics Device + 圖形裝置 Vblank Divider - Vblank Divider + Vblank 分隔符 Advanced - Advanced + 進階 Enable Shaders Dumping - Enable Shaders Dumping + 啟用著色器傾印 Enable NULL GPU - Enable NULL GPU + 啟用空值GPU Enable HDR - Enable HDR + 啟用 HDR Paths @@ -1596,7 +1596,7 @@ Add... - 添加... + 新增… Remove @@ -1604,43 +1604,43 @@ Debug - Debug + 偵錯 Enable Debug Dumping - Enable Debug Dumping + 啟用偵錯傾印 Enable Vulkan Validation Layers - Enable Vulkan Validation Layers + 啟用 Vulkan 驗證層 Enable Vulkan Synchronization Validation - Enable Vulkan Synchronization Validation + 啟用 Vulkan 同步驗證 Enable RenderDoc Debugging - Enable RenderDoc Debugging + 啟用 RenderDoc 偵錯 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 + Guest 偵錯標記 Update @@ -1664,27 +1664,27 @@ GUI Settings - 介面設置 + 介面設定 Title Music - Title Music + 標題音樂 Disable Trophy Notification - Disable Trophy Notification + 停用獎盃通知 Background Image - Background Image + 背景圖像 Show Background Image - Show Background Image + 顯示背景影像 Opacity - Opacity + 透明度 Play title music @@ -1692,19 +1692,19 @@ 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 @@ -1720,7 +1720,7 @@ Restore Defaults - 還原預設值 + 還原成預設值 Close @@ -1728,79 +1728,79 @@ Point your mouse at an option to display its description. - 將鼠標指向選項以顯示其描述。 + 將滑鼠指標指向選項就會顯示其描述。 Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. - 主機語言:\n設定PS4遊戲使用的語言。\n建議將其設置為遊戲支持的語言,這會因地區而異。 + 主機語言:\n設定 PS4 遊戲使用的語言。 \n建議將其設定為遊戲'支援的語言,該語言因地區而異。 Emulator Language:\nSets the language of the emulator's user interface. - 模擬器語言:\n設定模擬器的用戶介面的語言。 + 模擬器語言:\n設定模擬器'的使用者介面語言。 Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. - 顯示啟動畫面:\n在遊戲啟動時顯示遊戲的啟動畫面(特殊圖片)。 + 顯示啟動畫面:\n遊戲'開始時顯示遊戲的啟動畫面(特殊圖像)。 Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. - 啟用 Discord Rich Presence:\n在您的 Discord 個人檔案上顯示模擬器圖標和相關信息。 + 啟用 Discord Rich Presence:\n在你的 Discord 個人檔案上顯示模擬器圖示和相關資訊。 Username:\nSets the PS4's account username, which may be displayed by some games. - 用戶名:\n設定PS4帳號的用戶名,某些遊戲中可能會顯示。 + 使用者名稱:\n設定 PS4 '的帳號使用者名稱,某些遊戲可能會顯示該使用者名稱。 Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + 獎盃金鑰:\n用於解密獎盃的金鑰,必須從你的越獄主機中獲取。 \n僅包含十六進制字元。 Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. - 日誌類型:\n設定是否同步日誌窗口的輸出以提高性能。可能對模擬產生不良影響。 + 日誌記錄類型:\n設定是否同步日誌記錄視窗的輸出以提高效能。可能會對模擬產生負面影響。 Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. - 日誌過濾器:\n過濾日誌以僅打印特定信息。\n範例:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 等級: Trace, Debug, Info, Warning, Error, Critical - 以此順序,特定級別靜音所有前面的級別,並記錄其後的每個級別。 + 日誌記錄過濾器:\n過濾日誌記錄以僅列出特定資訊。 \n範例:{[="-=]}Core:Trace{[="-=]} {[="-=]}Lib.Pad:Debug Common.Filesystem:Error{[="-=]} {[="-=]}*:Critical{[="-=]}\n等級:Trace、Debug、Info、Warning、Error、Critical - 按照此順序,指定等級之後會不列出列表中等級低於其的所有資訊,並記錄列出等級高於其/同級的資訊。 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. - 更新:\nRelease: 每月發布的官方版本,可能非常舊,但更可靠且經過測試。\nNightly: 開發版本,擁有所有最新的功能和修復,但可能包含錯誤,穩定性較差。 + 更新:\n正式版:每月發布的正式版本可能會過時,但更可靠而且有經過測試。 \n預覽版:具有所有最新功能和修復的開發版本,但可能含有一些錯誤而且不太穩定。 Background Image:\nControl the opacity of the game background image. - Background Image:\nControl the opacity of the game background image. + 背景圖像:\n控制遊戲背​​景圖像的能見度。 Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. - 播放標題音樂:\n如果遊戲支持,啟用在GUI中選擇遊戲時播放特殊音樂。 + 播放標題音樂:\n如果遊戲支援的話,則啟用在 GUI 中選取遊戲時播放特殊音樂。 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). - 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停用遊戲內獎盃通知,仍然可以使用獎盃檢視器(在主視窗中右鍵單擊遊戲)追蹤獎盃進度。 Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. - 隱藏游標:\n選擇游標何時消失:\n從不: 您將始終看到滑鼠。\n閒置: 設定在閒置後消失的時間。\n始終: 您將永遠看不到滑鼠。 + 隱藏滑鼠指標:\n選擇滑鼠指標何時消失:\n從不:你始終會看到滑鼠指標。 \n閒置:設定滑鼠指標空閒多久後消失的時間。 \n始終:你始終看不到滑鼠指標。 Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - 設定滑鼠在閒置後消失的時間。 + 空閒滑鼠指標隱藏逾時:\n閒置滑鼠指標隱藏自身之前的持續顯示時間(秒)。 Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - 返回按鈕行為:\n設定控制器的返回按鈕模擬在 PS4 觸控板上指定位置的觸碰。 + 返回按鈕行為:\n設定控制器'的返回按鈕以模擬點擊 PS4 控制器觸控板上的指定位置。 Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + 顯示相容性資料:\n在表格顯視模式中顯示遊戲相容性資訊。啟用「啟動"時更新相容性」以取得"最新資訊。 Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + 啟動時更新相容性:\n在 shadPS4 啟動時自動更新相容性資料庫。 Update Compatibility Database:\nImmediately update the compatibility database. - Update Compatibility Database:\nImmediately update the compatibility database. + 更新相容性資料庫:\n立即更新相容性資料庫。 Never @@ -1832,250 +1832,250 @@ Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. - 圖形設備:\n在多GPU系統中,從下拉列表中選擇模擬器將使用的GPU,\n或選擇「自動選擇」以自動確定。 + 圖形裝置:\n在多GPU系統中,從下拉列表中選取模擬器將使用的GPU,\n或選取「自動選取」以自動選用適合的GPU。 Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. - 寬度/高度:\n設定模擬器啟動時的窗口大小,可以在遊戲過程中調整。\n這與遊戲內解析度不同。 + 寬度/高度:\n設定模擬器啟動時的視窗大小,可以在遊戲過程中調整。\n這與遊戲內部解析度不同。 Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! - Vblank分隔符:\n模擬器的幀速率將乘以這個數字。更改此數字可能會有不良影響,例如增加遊戲速度,或破壞不預期此變化的關鍵遊戲功能! + Vblank分隔符:\n模擬器的畫格速率將乘以這個數字。變更此數字可能會有不良影響,例如增加遊戲速度,或可能會不似預期地破壞關鍵的遊戲功能! Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. - 啟用著色器轉儲:\n為了技術調試,將遊戲的著色器在渲染時保存到文件夾中。 + 啟用著色器傾印:\n作為技術偵錯用途,將遊戲的著色器在渲染時儲存到資料夾中。 Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - 啟用空GPU:\n為了技術調試,禁用遊戲渲染,彷彿沒有顯示卡。 + 啟用空值GPU:\n作為技術偵錯用途,停用遊戲渲染,彷如沒有顯示卡。 Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. - Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + 啟用 HDR:\n在支援 HDR 的遊戲中啟用 HDR。 \n您的顯示器必須支援 BT2020 PQ 色彩空間和 RGB10A2 交換鏈格式。 Game Folders:\nThe list of folders to check for installed games. - 遊戲資料夾:\n檢查已安裝遊戲的資料夾列表。 + 遊戲資料夾:\n檢查已安裝遊戲的資料夾列表。 Add:\nAdd a folder to the list. - 添加:\n將資料夾添加到列表。 + 新增:\n將資料夾新增到列表中。 Remove:\nRemove a folder from the list. - 移除:\n從列表中移除資料夾。 + 移除:\n從列表中移除資料夾。 Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. - 啟用調試轉儲:\n將當前運行的PS4程序的輸入和輸出符號及文件頭信息保存到目錄中。 + 啟用偵錯傾印:\n將目前執行的PS4程式的輸入和輸出符號以及檔案頭資訊儲存到目錄中。 Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. - 啟用Vulkan驗證層:\n啟用一個系統來驗證Vulkan渲染器的狀態並記錄其內部狀態的信息。這將降低性能並可能改變模擬行為。 + 啟用Vulkan驗證層:\n啟用一個系統來驗證Vulkan渲染器的狀態並記錄其內部狀態的資訊。這樣將會降低效能並可能改變模擬行為。 Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. - 啟用Vulkan同步驗證:\n啟用一個系統來驗證Vulkan渲染任務的時間。這將降低性能並可能改變模擬行為。 + 啟用Vulkan同步驗證:\n啟用一個系統來驗證Vulkan渲染工作的時間。\n這樣將會降低效能並可能改變模擬行為。 Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. - 啟用RenderDoc調試:\n如果啟用,模擬器將提供與Renderdoc的兼容性,以允許捕獲和分析當前渲染的幀。 + 啟用RenderDoc偵錯:\n如果啟用的話,模擬器將會提供與Renderdoc的相容性,以便允許擷取和分析目前渲染的畫格。 Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). - Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + 收集著色器:\n您需要啟用此功能才能使用偵錯功能表 (Ctrl + F10) 編輯著色器。 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. - 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對於偵錯「'裝置遺失」的'錯誤很有用。如果啟用了此功能,則應啟用主機和來賓偵錯標記。 \n不適用於 Intel GPU。 \n你需要啟用 Vulkan 驗證層和 Vulkan SDK 才能使此功能如常運作。 Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. - 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 的當機。 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 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 指令的特定 AMDGPU 指令的標記,以及提供資源偵錯名稱。 \n如果啟用此功能,則應啟用當機診斷。 \n對於 RenderDoc 等程式很有用。 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 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 等程式很有用。 Save Data Path:\nThe folder where game save data will be saved. - Save Data Path:\nThe folder where game save data will be saved. + 遊戲存檔路徑:\n存放遊戲存檔資料的資料夾。 Browse:\nBrowse for a folder to set as the save data path. - Browse:\nBrowse for a folder to set as the save data path. + 瀏覽:\n選取要設定為存放遊戲存檔路徑的資料夾。 Release - Release + 正式版 Nightly - Nightly + 預覽版 Set the volume of the background music. - Set the volume of the background music. + 設定背景音樂的音量。 Enable Motion Controls - Enable Motion Controls + 啟用動態感應控制 Save Data Path - Save Data Path + 遊戲存檔路徑 Browse - Browse + 瀏覽 async - async + 異步 sync - sync + 同步 Auto Select - Auto Select + 自動選取 Directory to install games - Directory to install games + 要安裝遊戲的目錄 Directory to save data - Directory to save data + 遊戲存檔的目錄 Video - Video + 影像 Display Mode - Display Mode + 顯示模式 Windowed - Windowed + 視窗 Fullscreen - Fullscreen + 全螢幕 Fullscreen (Borderless) - Fullscreen (Borderless) + 全螢幕(無邊框) Window Size - Window Size + 視窗大小 W: - W: + 寬: H: - H: + 高: Separate Log Files - Separate Log Files + 單獨的日誌紀錄檔 Separate Log Files:\nWrites a separate logfile for each game. - Separate Log Files:\nWrites a separate logfile for each game. + 單獨的日誌紀錄檔:\n為個別遊戲寫入單獨的日誌記錄檔。 Trophy Notification Position - Trophy Notification Position + 獎盃通知位置 Left - Left + 左側 Right - Right + 右側 Top - Top + 頂部 Bottom - Bottom + 底部 Notification Duration - Notification Duration + 通知顯示持續時間 Portable User Folder - Portable User Folder + 可移植的使用者資料夾 Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + 從通用使用者資料夾建立可移植的使用者資料夾 Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + 可移植的使用者資料夾:\n儲存僅套用於目前資料夾中的 shadPS4 版本的 shadPS4 設定和資料。建立可移植的使用者資料夾後重新啟動應用程式以便開始使用它。 Cannot create portable user folder - Cannot create portable user folder + 無法建立可移植的使用者資料夾 %1 already exists - %1 already exists + %1 已存在 Portable user folder created - Portable user folder created + 已建立可移植的使用者資料夾 %1 successfully created. - %1 successfully created. + 已成功建立 %1。 Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + 開啟自訂獎盃影像/聲音資料夾:\n您可以將自訂影像新增至獎盃和音訊。 \n將檔案加入 custom_tropy,名稱如下:\ntropy.wav OR trophy.mp3、bronze.png、gold.png、platinum.png、silver.png\n注意:聲音僅在 QT 版本中有效。 TrophyViewer Trophy Viewer - Trophy Viewer + 獎盃檢視器 Select Game: - Select Game: + 選取遊戲: Progress - Progress + 進度 Show Earned Trophies - Show Earned Trophies + 顯示已獲得的獎盃 Show Not Earned Trophies - Show Not Earned Trophies + 顯示未獲得的獎盃 Show Hidden Trophies - Show Hidden Trophies + 顯示隱藏的獎盃 From cd8e2c4e7974c99a035acb8bac2b3114737f8610 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 14 Apr 2025 12:37:06 +0300 Subject: [PATCH 134/194] hotfix: fix libpng issues under linux --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ea8688df..6412e51fb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,7 +213,7 @@ find_package(fmt 10.2.0 CONFIG) find_package(glslang 15 CONFIG) find_package(half 1.12.0 MODULE) find_package(magic_enum 0.9.7 CONFIG) -find_package(PNG 1.6 MODULE) +find_package(PNG 1.6.47 MODULE) find_package(RenderDoc 1.6.0 MODULE) find_package(SDL3 3.1.2 CONFIG) find_package(stb MODULE) From 2b633fd3c566fbabf5a63fd2e14a049e28bab3ef Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 14 Apr 2025 14:08:39 +0300 Subject: [PATCH 135/194] downgrade libpng to fix an issue with linux-qt (#2784) * downgrade libpng * revert cmake change --- CMakeLists.txt | 2 +- externals/libpng | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6412e51fb..0ea8688df 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,7 +213,7 @@ find_package(fmt 10.2.0 CONFIG) find_package(glslang 15 CONFIG) find_package(half 1.12.0 MODULE) find_package(magic_enum 0.9.7 CONFIG) -find_package(PNG 1.6.47 MODULE) +find_package(PNG 1.6 MODULE) find_package(RenderDoc 1.6.0 MODULE) find_package(SDL3 3.1.2 CONFIG) find_package(stb MODULE) diff --git a/externals/libpng b/externals/libpng index 34005e3d3..c1cc0f3f4 160000 --- a/externals/libpng +++ b/externals/libpng @@ -1 +1 @@ -Subproject commit 34005e3d3d373c0c36898cc55eae48a79c8238a1 +Subproject commit c1cc0f3f4c3d4abd11ca68c59446a29ff6f95003 From 683a223c1b771e212421ee123d9575a19c75f2e9 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 14 Apr 2025 20:58:33 -0700 Subject: [PATCH 136/194] vk_rasterizer: Control mapped_ranges access with shared lock. (#2788) --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 16 ++++++++++++---- src/video_core/renderer_vulkan/vk_rasterizer.h | 3 +++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 30102960a..13779977d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -930,12 +930,17 @@ bool Rasterizer::IsMapped(VAddr addr, u64 size) { // There is no memory, so not mapped. return false; } - return mapped_ranges.find(boost::icl::interval::right_open(addr, addr + size)) != - mapped_ranges.end(); + const auto range = decltype(mapped_ranges)::interval_type::right_open(addr, addr + size); + + std::shared_lock lock{mapped_ranges_mutex}; + return boost::icl::contains(mapped_ranges, range); } void Rasterizer::MapMemory(VAddr addr, u64 size) { - mapped_ranges += boost::icl::interval::right_open(addr, addr + size); + { + std::unique_lock lock{mapped_ranges_mutex}; + mapped_ranges += decltype(mapped_ranges)::interval_type::right_open(addr, addr + size); + } page_manager.OnGpuMap(addr, size); } @@ -943,7 +948,10 @@ void Rasterizer::UnmapMemory(VAddr addr, u64 size) { buffer_cache.InvalidateMemory(addr, size); texture_cache.UnmapMemory(addr, size); page_manager.OnGpuUnmap(addr, size); - mapped_ranges -= boost::icl::interval::right_open(addr, addr + size); + { + std::unique_lock lock{mapped_ranges_mutex}; + mapped_ranges -= decltype(mapped_ranges)::interval_type::right_open(addr, addr + size); + } } void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) const { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 54bf3d253..4e0ed0996 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" @@ -106,6 +108,7 @@ private: AmdGpu::Liverpool* liverpool; Core::MemoryManager* memory; boost::icl::interval_set mapped_ranges; + std::shared_mutex mapped_ranges_mutex; PipelineCache pipeline_cache; boost::container::static_vector< From 4bea00135dbb47c79a15fd31a1dd8fc192d34b17 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 14 Apr 2025 20:58:49 -0700 Subject: [PATCH 137/194] resource_tracking_pass: Add heuristic to detect incorrectly tracked buffer sharp. (#2786) --- .../ir/passes/resource_tracking_pass.cpp | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index c5bfe5796..778da149f 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -235,9 +235,12 @@ std::pair TryDisableAnisoLod0(const IR::Inst* inst) { return {prod2, true}; } -SharpLocation TrackSharp(const IR::Inst* inst, const Shader::Info& info) { +SharpLocation AttemptTrackSharp(const IR::Inst* inst, auto& visited_insts) { // Search until we find a potential sharp source. - const auto pred = [](const IR::Inst* inst) -> std::optional { + const auto pred = [&visited_insts](const IR::Inst* inst) -> std::optional { + if (std::ranges::find(visited_insts, inst) != visited_insts.end()) { + return std::nullopt; + } if (inst->GetOpcode() == IR::Opcode::GetUserData || inst->GetOpcode() == IR::Opcode::ReadConst) { return inst; @@ -247,6 +250,7 @@ SharpLocation TrackSharp(const IR::Inst* inst, const Shader::Info& info) { const auto result = IR::BreadthFirstSearch(inst, pred); ASSERT_MSG(result, "Unable to track sharp source"); inst = result.value(); + visited_insts.emplace_back(inst); if (inst->GetOpcode() == IR::Opcode::GetUserData) { return static_cast(inst->Arg(0).ScalarReg()); } else { @@ -256,6 +260,29 @@ SharpLocation TrackSharp(const IR::Inst* inst, const Shader::Info& info) { } } +/// Tracks a sharp with validation of the chosen data type. +template +std::pair TrackSharp(const IR::Inst* inst, const Info& info) { + boost::container::small_vector visited_insts{}; + while (true) { + const auto prev_size = visited_insts.size(); + const auto sharp = AttemptTrackSharp(inst, visited_insts); + if (const auto data = info.ReadUdSharp(sharp); data.Valid()) { + return std::make_pair(sharp, data); + } + if (prev_size == visited_insts.size()) { + // No change in visited instructions, we've run out of paths. + UNREACHABLE_MSG("Unable to find valid sharp."); + } + } +} + +/// Tracks a sharp without data validation. +SharpLocation TrackSharp(const IR::Inst* inst, const Info& info) { + boost::container::static_vector visited_insts{}; + return AttemptTrackSharp(inst, visited_insts); +} + s32 TryHandleInlineCbuf(IR::Inst& inst, Info& info, Descriptors& descriptors, AmdGpu::Buffer& cbuf) { @@ -293,8 +320,8 @@ void PatchBufferSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& if (binding = TryHandleInlineCbuf(inst, info, descriptors, buffer); binding == -1) { IR::Inst* handle = inst.Arg(0).InstRecursive(); IR::Inst* producer = handle->Arg(0).InstRecursive(); - const auto sharp = TrackSharp(producer, info); - buffer = info.ReadUdSharp(sharp); + SharpLocation sharp; + std::tie(sharp, buffer) = TrackSharp(producer, info); binding = descriptors.Add(BufferResource{ .sharp_idx = sharp, .used_types = BufferDataType(inst, buffer.GetNumberFmt()), From 247e935353852bdb64bcfe76a5f74ab2c6c01239 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 15 Apr 2025 17:13:19 -0700 Subject: [PATCH 138/194] externals: Update MoltenVK (#2791) --- .gitmodules | 2 +- externals/MoltenVK/MoltenVK | 2 +- externals/MoltenVK/SPIRV-Cross | 2 +- src/video_core/renderer_vulkan/vk_instance.cpp | 5 +---- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.gitmodules b/.gitmodules index 065a4570f..9daefe305 100644 --- a/.gitmodules +++ b/.gitmodules @@ -97,7 +97,7 @@ shallow = true [submodule "externals/MoltenVK/SPIRV-Cross"] path = externals/MoltenVK/SPIRV-Cross - url = https://github.com/KhronosGroup/SPIRV-Cross + url = https://github.com/billhollings/SPIRV-Cross shallow = true [submodule "externals/MoltenVK/MoltenVK"] path = externals/MoltenVK/MoltenVK diff --git a/externals/MoltenVK/MoltenVK b/externals/MoltenVK/MoltenVK index 83510e0f3..067fc6c85 160000 --- a/externals/MoltenVK/MoltenVK +++ b/externals/MoltenVK/MoltenVK @@ -1 +1 @@ -Subproject commit 83510e0f3835c3c43651dda087305abc42572e17 +Subproject commit 067fc6c85b02f37dfda58eeda49d8458e093ed60 diff --git a/externals/MoltenVK/SPIRV-Cross b/externals/MoltenVK/SPIRV-Cross index 68300dc07..185833a61 160000 --- a/externals/MoltenVK/SPIRV-Cross +++ b/externals/MoltenVK/SPIRV-Cross @@ -1 +1 @@ -Subproject commit 68300dc07ac3dc592dbbdb87e02d5180f984ad12 +Subproject commit 185833a61cbe29ce3bfb5a499ffb3dfeaee3bbe7 diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index d45889054..f83f63036 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -249,7 +249,7 @@ bool Instance::CreateDevice() { add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); - const bool maintenance4 = add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); + add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); @@ -422,9 +422,6 @@ bool Instance::CreateDevice() { #endif }; - if (!maintenance4) { - device_chain.unlink(); - } if (!custom_border_color) { device_chain.unlink(); } From 3bc876ca78a680d76a346d175febe0f8ac4003dd Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 15 Apr 2025 22:31:58 -0700 Subject: [PATCH 139/194] renderer_vulkan: Improve handling of required vs optional extensions. (#2792) * renderer_vulkan: Improve handling of required vs optional extensions. * documents: Update quickstart Vulkan requirements. --- documents/Quickstart/Quickstart.md | 4 +- .../renderer_vulkan/vk_instance.cpp | 25 +++++++------ src/video_core/renderer_vulkan/vk_instance.h | 6 +++ .../renderer_vulkan/vk_rasterizer.cpp | 37 +++++++++++-------- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/documents/Quickstart/Quickstart.md b/documents/Quickstart/Quickstart.md index 55825ac7d..62df95e71 100644 --- a/documents/Quickstart/Quickstart.md +++ b/documents/Quickstart/Quickstart.md @@ -29,8 +29,8 @@ SPDX-License-Identifier: GPL-2.0-or-later ### GPU - A graphics card with at least 1GB of VRAM -- Keep your graphics drivers up to date -- Vulkan 1.3 support (required) +- Up-to-date graphics drivers +- Vulkan 1.3 with the `VK_KHR_swapchain` and `VK_KHR_push_descriptor` extensions ### RAM diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index f83f63036..f20e91ec8 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -242,18 +242,21 @@ bool Instance::CreateDevice() { // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 // with extensions. - add_extension(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME); - add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); - add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); - add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); - add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); - add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); - add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); - add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); + ASSERT(add_extension(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME)); + ASSERT(add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)); + ASSERT(add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME)); + ASSERT(add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME)); + ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)); + ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME)); + ASSERT(add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME)); + ASSERT(add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME)); - add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); - add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); - add_extension(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); + // Required + ASSERT(add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME)); + ASSERT(add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME)); + + // Optional + depth_range_unrestricted = add_extension(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); dynamic_state_3 = add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); if (dynamic_state_3) { dynamic_state_3_features = diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 6de419041..a9de01f84 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -104,6 +104,11 @@ public: return depth_clip_control; } + /// Returns true when VK_EXT_depth_range_unrestricted is supported + bool IsDepthRangeUnrestrictedSupported() const { + return depth_range_unrestricted; + } + /// Returns true when the extendedDynamicState3ColorWriteMask feature of /// VK_EXT_extended_dynamic_state3 is supported. bool IsDynamicColorWriteMaskSupported() const { @@ -340,6 +345,7 @@ private: bool custom_border_color{}; bool fragment_shader_barycentric{}; bool depth_clip_control{}; + bool depth_range_unrestricted{}; bool dynamic_state_3{}; bool vertex_input_dynamic_state{}; bool robustness2{}; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 13779977d..f8d09f011 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1018,32 +1018,37 @@ void Rasterizer::UpdateViewportScissorState() const { const auto zoffset = vp_ctl.zoffset_enable ? vp.zoffset : 0.f; const auto zscale = vp_ctl.zscale_enable ? vp.zscale : 1.f; + + vk::Viewport viewport = { + .minDepth = zoffset - zscale * reduce_z, + .maxDepth = zscale + zoffset, + }; + if (!instance.IsDepthRangeUnrestrictedSupported()) { + // Unrestricted depth range not supported by device. Make best attempt + // by restricting to valid range. + viewport.minDepth = std::max(viewport.minDepth, 0.f); + viewport.maxDepth = std::min(viewport.maxDepth, 1.f); + } if (regs.IsClipDisabled()) { // In case if clipping is disabled we patch the shader to convert vertex position // from screen space coordinates to NDC by defining a render space as full hardware // window range [0..16383, 0..16383] and setting the viewport to its size. - viewports.push_back({ - .x = 0.f, - .y = 0.f, - .width = float(std::min(instance.GetMaxViewportWidth(), 16_KB)), - .height = float(std::min(instance.GetMaxViewportHeight(), 16_KB)), - .minDepth = zoffset - zscale * reduce_z, - .maxDepth = zscale + zoffset, - }); + viewport.x = 0.f; + viewport.y = 0.f; + viewport.width = float(std::min(instance.GetMaxViewportWidth(), 16_KB)); + viewport.height = float(std::min(instance.GetMaxViewportHeight(), 16_KB)); } else { const auto xoffset = vp_ctl.xoffset_enable ? vp.xoffset : 0.f; const auto xscale = vp_ctl.xscale_enable ? vp.xscale : 1.f; const auto yoffset = vp_ctl.yoffset_enable ? vp.yoffset : 0.f; const auto yscale = vp_ctl.yscale_enable ? vp.yscale : 1.f; - viewports.push_back({ - .x = xoffset - xscale, - .y = yoffset - yscale, - .width = xscale * 2.0f, - .height = yscale * 2.0f, - .minDepth = zoffset - zscale * reduce_z, - .maxDepth = zscale + zoffset, - }); + + viewport.x = xoffset - xscale; + viewport.y = yoffset - yscale; + viewport.width = xscale * 2.0f; + viewport.height = yscale * 2.0f; } + viewports.push_back(viewport); auto vp_scsr = scsr; if (regs.mode_control.vport_scissor_enable) { From 52ab1ed04b900206c989a3cb78932217421d545a Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 16 Apr 2025 08:08:09 -0700 Subject: [PATCH 140/194] shader_recompiler: Implement S_FLBIT_I32_B32 and V_MUL_HI_I32. (#2793) --- .../backend/spirv/emit_spirv_instructions.h | 4 ++-- .../backend/spirv/emit_spirv_integer.cpp | 13 +++++++++---- .../frontend/translate/scalar_alu.cpp | 13 +++++++++++++ .../frontend/translate/translate.h | 1 + .../frontend/translate/vector_alu.cpp | 4 +++- src/shader_recompiler/ir/ir_emitter.cpp | 4 ++-- src/shader_recompiler/ir/ir_emitter.h | 2 +- src/shader_recompiler/ir/opcodes.inc | 4 ++-- 8 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index fb37799f5..68438fbba 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -338,8 +338,8 @@ Id EmitIAdd64(EmitContext& ctx, Id a, Id b); Id EmitIAddCary32(EmitContext& ctx, Id a, Id b); Id EmitISub32(EmitContext& ctx, Id a, Id b); Id EmitISub64(EmitContext& ctx, Id a, Id b); -Id EmitSMulExt(EmitContext& ctx, Id a, Id b); -Id EmitUMulExt(EmitContext& ctx, Id a, Id b); +Id EmitSMulHi(EmitContext& ctx, Id a, Id b); +Id EmitUMulHi(EmitContext& ctx, Id a, Id b); Id EmitIMul32(EmitContext& ctx, Id a, Id b); Id EmitIMul64(EmitContext& ctx, Id a, Id b); Id EmitSDiv32(EmitContext& ctx, Id a, Id b); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index 36726b6df..10bfbb2ab 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -72,12 +72,17 @@ Id EmitISub64(EmitContext& ctx, Id a, Id b) { return ctx.OpISub(ctx.U64, a, b); } -Id EmitSMulExt(EmitContext& ctx, Id a, Id b) { - return ctx.OpSMulExtended(ctx.full_result_i32x2, a, b); +Id EmitSMulHi(EmitContext& ctx, Id a, Id b) { + const auto signed_a{ctx.OpBitcast(ctx.S32[1], a)}; + const auto signed_b{ctx.OpBitcast(ctx.S32[1], b)}; + const auto mul_ext{ctx.OpSMulExtended(ctx.full_result_i32x2, signed_a, signed_b)}; + const auto signed_hi{ctx.OpCompositeExtract(ctx.S32[1], mul_ext, 1)}; + return ctx.OpBitcast(ctx.U32[1], signed_hi); } -Id EmitUMulExt(EmitContext& ctx, Id a, Id b) { - return ctx.OpUMulExtended(ctx.full_result_u32x2, a, b); +Id EmitUMulHi(EmitContext& ctx, Id a, Id b) { + const auto mul_ext{ctx.OpUMulExtended(ctx.full_result_u32x2, a, b)}; + return ctx.OpCompositeExtract(ctx.U32[1], mul_ext, 1); } Id EmitIMul32(EmitContext& ctx, Id a, Id b) { diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index 39f972848..c21c9b611 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -110,6 +110,8 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { return S_FF1_I32_B32(inst); case Opcode::S_FF1_I32_B64: return S_FF1_I32_B64(inst); + case Opcode::S_FLBIT_I32_B32: + return S_FLBIT_I32_B32(inst); case Opcode::S_BITSET0_B32: return S_BITSET_B32(inst, 0); case Opcode::S_BITSET1_B32: @@ -660,6 +662,17 @@ void Translator::S_FF1_I32_B64(const GcnInst& inst) { SetDst(inst.dst[0], result); } +void Translator::S_FLBIT_I32_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + // Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB) + // position + const IR::U32 msb_pos = ir.FindUMsb(src0); + const IR::U32 pos_from_left = ir.ISub(ir.Imm32(31), msb_pos); + // Select 0xFFFFFFFF if src0 was 0 + const IR::U1 cond = ir.INotEqual(src0, ir.Imm32(0)); + SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))}); +} + void Translator::S_BITSET_B32(const GcnInst& inst, u32 bit_value) { const IR::U32 old_value{GetSrc(inst.dst[0])}; const IR::U32 offset{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0U), ir.Imm32(5U))}; diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 6803cda25..520720b0f 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -119,6 +119,7 @@ public: void S_BCNT1_I32_B64(const GcnInst& inst); void S_FF1_I32_B32(const GcnInst& inst); void S_FF1_I32_B64(const GcnInst& inst); + void S_FLBIT_I32_B32(const GcnInst& inst); void S_BITSET_B32(const GcnInst& inst, u32 bit_value); void S_GETPC_B64(u32 pc, const GcnInst& inst); void S_SAVEEXEC_B64(NegateMode negate, bool is_or, const GcnInst& inst); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index da25f5434..787cf6ad3 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -394,6 +394,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_MUL_HI_U32(false, inst); case Opcode::V_MUL_LO_I32: return V_MUL_LO_U32(inst); + case Opcode::V_MUL_HI_I32: + return V_MUL_HI_U32(true, inst); case Opcode::V_MAD_U64_U32: return V_MAD_U64_U32(inst); case Opcode::V_NOP: @@ -1279,7 +1281,7 @@ void Translator::V_MUL_LO_U32(const GcnInst& inst) { void Translator::V_MUL_HI_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 hi{ir.CompositeExtract(ir.IMulExt(src0, src1, is_signed), 1)}; + const IR::U32 hi{ir.IMulHi(src0, src1, is_signed)}; SetDst(inst.dst[0], hi); } diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index a171d32a2..a51d126c7 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1388,8 +1388,8 @@ U32U64 IREmitter::ISub(const U32U64& a, const U32U64& b) { } } -IR::Value IREmitter::IMulExt(const U32& a, const U32& b, bool is_signed) { - return Inst(is_signed ? Opcode::SMulExt : Opcode::UMulExt, a, b); +U32 IREmitter::IMulHi(const U32& a, const U32& b, bool is_signed) { + return Inst(is_signed ? Opcode::SMulHi : Opcode::UMulHi, a, b); } U32U64 IREmitter::IMul(const U32U64& a, const U32U64& b) { diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 48cc02725..f1d564b80 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -240,7 +240,7 @@ public: [[nodiscard]] U32U64 IAdd(const U32U64& a, const U32U64& b); [[nodiscard]] Value IAddCary(const U32& a, const U32& b); [[nodiscard]] U32U64 ISub(const U32U64& a, const U32U64& b); - [[nodiscard]] Value IMulExt(const U32& a, const U32& b, bool is_signed = false); + [[nodiscard]] U32 IMulHi(const U32& a, const U32& b, bool is_signed = false); [[nodiscard]] U32U64 IMul(const U32U64& a, const U32U64& b); [[nodiscard]] U32 IDiv(const U32& a, const U32& b, bool is_signed = false); [[nodiscard]] U32 IMod(const U32& a, const U32& b, bool is_signed = false); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 93d759b74..10819f898 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -317,8 +317,8 @@ OPCODE(ISub32, U32, U32, OPCODE(ISub64, U64, U64, U64, ) OPCODE(IMul32, U32, U32, U32, ) OPCODE(IMul64, U64, U64, U64, ) -OPCODE(SMulExt, U32x2, U32, U32, ) -OPCODE(UMulExt, U32x2, U32, U32, ) +OPCODE(SMulHi, U32, U32, U32, ) +OPCODE(UMulHi, U32, U32, U32, ) OPCODE(SDiv32, U32, U32, U32, ) OPCODE(UDiv32, U32, U32, U32, ) OPCODE(SMod32, U32, U32, U32, ) From 04e4ce0775a6a9aa4aa33456a92b68860c1c129f Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 16 Apr 2025 09:52:18 -0700 Subject: [PATCH 141/194] fix: Bad Intel drivers. --- src/video_core/renderer_vulkan/vk_instance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index f20e91ec8..c28e22985 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -248,7 +248,7 @@ bool Instance::CreateDevice() { ASSERT(add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME)); ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)); ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME)); - ASSERT(add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME)); + ASSERT(add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME) || driver_id == vk::DriverId::eIntelProprietaryWindows); ASSERT(add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME)); // Required From 243ee04b1c37b319a3bcf6d3bbeccaff3eb041a8 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Wed, 16 Apr 2025 17:54:05 +0100 Subject: [PATCH 142/194] Implement DS_READ2ST64_B64 (#2795) --- src/shader_recompiler/frontend/translate/data_share.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 22f5b8644..acffae14b 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -67,6 +67,8 @@ void Translator::EmitDataShare(const GcnInst& inst) { return DS_READ(64, false, false, false, inst); case Opcode::DS_READ2_B64: return DS_READ(64, false, true, false, inst); + case Opcode::DS_READ2ST64_B64: + return DS_READ(64, false, true, true, inst); default: LogMissingOpcode(inst); } From ddf3df7f564baf6de5e472b2a060e53acbb467ca Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 16 Apr 2025 10:48:17 -0700 Subject: [PATCH 143/194] fix: clang-format --- src/video_core/renderer_vulkan/vk_instance.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index c28e22985..0df020116 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -248,7 +248,8 @@ bool Instance::CreateDevice() { ASSERT(add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME)); ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)); ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME)); - ASSERT(add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME) || driver_id == vk::DriverId::eIntelProprietaryWindows); + ASSERT(add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME) || + driver_id == vk::DriverId::eIntelProprietaryWindows); ASSERT(add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME)); // Required From aa8dab5371777105a3112498faa821d79aa3cab4 Mon Sep 17 00:00:00 2001 From: Fire Cube Date: Wed, 16 Apr 2025 23:24:18 +0200 Subject: [PATCH 144/194] shader_recompiler: Implement S_SUBB_U32 instruction (#2796) * add S_SUBB_U32 instruction * add missing case * move case to match enum --- .../frontend/translate/scalar_alu.cpp | 13 +++++++++++++ .../frontend/translate/translate.h | 1 + 2 files changed, 14 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index c21c9b611..3a8e894ae 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -30,6 +30,8 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { return S_SUB_I32(inst); case Opcode::S_ADDC_U32: return S_ADDC_U32(inst); + case Opcode::S_SUBB_U32: + return S_SUBB_U32(inst); case Opcode::S_MIN_I32: return S_MIN_U32(true, inst); case Opcode::S_MIN_U32: @@ -238,6 +240,17 @@ void Translator::S_SUB_U32(const GcnInst& inst) { ir.SetScc(ir.IGreaterThan(src1, src0, false)); } +void Translator::S_SUBB_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 borrow{ir.Select(ir.GetScc(), ir.Imm32(1U), ir.Imm32(0U))}; + const IR::U32 result{ir.ISub(ir.ISub(src0, src1), borrow)}; + SetDst(inst.dst[0], result); + + const IR::U32 sum_with_borrow{ir.IAdd(src1, borrow)}; + ir.SetScc(ir.ILessThan(src0, sum_with_borrow, false)); +} + void Translator::S_ADD_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 520720b0f..32185a21f 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -80,6 +80,7 @@ public: // SOP2 void S_ADD_U32(const GcnInst& inst); void S_SUB_U32(const GcnInst& inst); + void S_SUBB_U32(const GcnInst& inst); void S_ADD_I32(const GcnInst& inst); void S_SUB_I32(const GcnInst& inst); void S_ADDC_U32(const GcnInst& inst); From 62a4182aca6a33b1899e871c8ff35410708d3752 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 16 Apr 2025 17:35:14 -0700 Subject: [PATCH 145/194] shader_recompiler: Fill in IMAGE_GATHER4_* variants in table. (#2798) --- src/shader_recompiler/frontend/format.cpp | 64 +++++++++++------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index f89f0a582..52c8c733e 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -3569,19 +3569,19 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Float32}, // 65 = IMAGE_GATHER4_CL - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, {}, {}, // 68 = IMAGE_GATHER4_L - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 69 = IMAGE_GATHER4_B - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 70 = IMAGE_GATHER4_B_CL - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 71 = IMAGE_GATHER4_LZ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Float32}, @@ -3589,19 +3589,19 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Float32}, // 73 = IMAGE_GATHER4_C_CL - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, {}, {}, // 76 = IMAGE_GATHER4_C_L - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 77 = IMAGE_GATHER4_C_B - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 78 = IMAGE_GATHER4_C_B_CL - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 79 = IMAGE_GATHER4_C_LZ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Float32}, @@ -3609,19 +3609,19 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Float32}, // 81 = IMAGE_GATHER4_CL_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, {}, {}, // 84 = IMAGE_GATHER4_L_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 85 = IMAGE_GATHER4_B_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 86 = IMAGE_GATHER4_B_CL_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 87 = IMAGE_GATHER4_LZ_O {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Float32}, @@ -3629,19 +3629,19 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Float32}, // 89 = IMAGE_GATHER4_C_CL_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, {}, {}, // 92 = IMAGE_GATHER4_C_L_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 93 = IMAGE_GATHER4_C_B_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 94 = IMAGE_GATHER4_C_B_CL_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 95 = IMAGE_GATHER4_C_LZ_O {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Float32}, From ddc05e8a5f43819ce76340552cfcd2b9db6641ff Mon Sep 17 00:00:00 2001 From: Dmugetsu <168934208+diegolix29@users.noreply.github.com> Date: Wed, 16 Apr 2025 18:56:27 -0600 Subject: [PATCH 146/194] Implementing DS_SUB_U32, DS_INC_U32, DS_DEC_U32. (#2797) * Implementing DS_SUB_U32, DS_INC_U32, DS_DEC_U32, DS_WRITE_SRC2_B32, DS_WRITE_SRC2_B64. * Added ir instructions for new opcodes. Removing Write implementations. Maping operation S_BFE_I32 as it was added in translate but wasnt pointing to anything. * Suggestions --- .../backend/spirv/emit_spirv_atomic.cpp | 21 ++++++++++ .../backend/spirv/emit_spirv_instructions.h | 4 ++ .../frontend/translate/data_share.cpp | 42 +++++++++++++++++++ .../frontend/translate/translate.h | 3 ++ src/shader_recompiler/ir/ir_emitter.cpp | 12 ++++++ src/shader_recompiler/ir/ir_emitter.h | 4 ++ src/shader_recompiler/ir/opcodes.inc | 3 ++ 7 files changed, 89 insertions(+) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 4faa99fe8..c6ec65606 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -21,6 +21,15 @@ Id SharedAtomicU32(EmitContext& ctx, Id offset, Id value, return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); } +Id SharedAtomicU32_IncDec(EmitContext& ctx, Id offset, + Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) { + const Id shift_id{ctx.ConstU32(2U)}; + const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; + const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)}; + const auto [scope, semantics]{AtomicArgs(ctx)}; + return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics); +} + Id BufferAtomicU32BoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) { if (Sirit::ValidId(buffer_size)) { // Bounds checking enabled, wrap in a conditional branch to make sure that @@ -99,6 +108,18 @@ Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicXor); } +Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicISub); +} + +Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset) { + return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement); +} + +Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset) { + return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement); +} + Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 68438fbba..9b7528be8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -130,6 +130,10 @@ Id EmitSharedAtomicSMin32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicAnd32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicOr32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset); +Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset); +Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value); + Id EmitCompositeConstructU32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2); Id EmitCompositeConstructU32x3(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3); Id EmitCompositeConstructU32x4(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3, Id e4); diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index acffae14b..c29497ada 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -13,6 +13,12 @@ void Translator::EmitDataShare(const GcnInst& inst) { // DS case Opcode::DS_ADD_U32: return DS_ADD_U32(inst, false); + case Opcode::DS_SUB_U32: + return DS_SUB_U32(inst, false); + case Opcode::DS_INC_U32: + return DS_INC_U32(inst, false); + case Opcode::DS_DEC_U32: + return DS_DEC_U32(inst, false); case Opcode::DS_MIN_I32: return DS_MIN_U32(inst, true, false); case Opcode::DS_MAX_I32: @@ -35,6 +41,8 @@ void Translator::EmitDataShare(const GcnInst& inst) { return DS_WRITE(32, false, true, true, inst); case Opcode::DS_ADD_RTN_U32: return DS_ADD_U32(inst, true); + case Opcode::DS_SUB_RTN_U32: + return DS_SUB_U32(inst, true); case Opcode::DS_MIN_RTN_U32: return DS_MIN_U32(inst, false, true); case Opcode::DS_MAX_RTN_U32: @@ -228,6 +236,40 @@ void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { SetDst(inst.dst[0], ir.QuadShuffle(src, index)); } +void Translator::DS_INC_U32(const GcnInst& inst, bool rtn) { + const IR::U32 addr{GetSrc(inst.src[0])}; + const IR::U32 offset = + ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); + const IR::U32 addr_offset = ir.IAdd(addr, offset); + const IR::Value original_val = ir.SharedAtomicIIncrement(addr_offset); + if (rtn) { + SetDst(inst.dst[0], IR::U32{original_val}); + } +} + +void Translator::DS_DEC_U32(const GcnInst& inst, bool rtn) { + const IR::U32 addr{GetSrc(inst.src[0])}; + const IR::U32 offset = + ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); + const IR::U32 addr_offset = ir.IAdd(addr, offset); + const IR::Value original_val = ir.SharedAtomicIDecrement(addr_offset); + if (rtn) { + SetDst(inst.dst[0], IR::U32{original_val}); + } +} + +void Translator::DS_SUB_U32(const GcnInst& inst, bool rtn) { + const IR::U32 addr{GetSrc(inst.src[0])}; + const IR::U32 data{GetSrc(inst.src[1])}; + const IR::U32 offset = + ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); + const IR::U32 addr_offset = ir.IAdd(addr, offset); + const IR::Value original_val = ir.SharedAtomicISub(addr_offset, data); + if (rtn) { + SetDst(inst.dst[0], IR::U32{original_val}); + } +} + void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst) { const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 32185a21f..68d5e8dc8 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -275,6 +275,9 @@ public: void DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); void DS_APPEND(const GcnInst& inst); void DS_CONSUME(const GcnInst& inst); + void DS_SUB_U32(const GcnInst& inst, bool rtn); + void DS_INC_U32(const GcnInst& inst, bool rtn); + void DS_DEC_U32(const GcnInst& inst, bool rtn); // Buffer Memory // MUBUF / MTBUF diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index a51d126c7..e8836bb4c 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -357,6 +357,18 @@ U32 IREmitter::SharedAtomicXor(const U32& address, const U32& data) { return Inst(Opcode::SharedAtomicXor32, address, data); } +U32 IREmitter::SharedAtomicIIncrement(const U32& address) { + return Inst(Opcode::SharedAtomicIIncrement32, address); +} + +U32 IREmitter::SharedAtomicIDecrement(const U32& address) { + return Inst(Opcode::SharedAtomicIDecrement32, address); +} + +U32 IREmitter::SharedAtomicISub(const U32& address, const U32& data) { + return Inst(Opcode::SharedAtomicISub32, address, data); +} + U32 IREmitter::ReadConst(const Value& base, const U32& offset) { return Inst(Opcode::ReadConst, base, offset); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index f1d564b80..186d83a07 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -106,6 +106,10 @@ public: [[nodiscard]] U32 SharedAtomicOr(const U32& address, const U32& data); [[nodiscard]] U32 SharedAtomicXor(const U32& address, const U32& data); + [[nodiscard]] U32 SharedAtomicIIncrement(const U32& address); + [[nodiscard]] U32 SharedAtomicIDecrement(const U32& address); + [[nodiscard]] U32 SharedAtomicISub(const U32& address, const U32& data); + [[nodiscard]] U32 ReadConst(const Value& base, const U32& offset); [[nodiscard]] U32 ReadConstBuffer(const Value& handle, const U32& index); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 10819f898..4932ff9a0 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -44,6 +44,9 @@ OPCODE(SharedAtomicUMax32, U32, U32, OPCODE(SharedAtomicAnd32, U32, U32, U32, ) OPCODE(SharedAtomicOr32, U32, U32, U32, ) OPCODE(SharedAtomicXor32, U32, U32, U32, ) +OPCODE(SharedAtomicISub32, U32, U32, U32, ) +OPCODE(SharedAtomicIIncrement32, U32, U32, ) +OPCODE(SharedAtomicIDecrement32, U32, U32, ) // Context getters/setters OPCODE(GetUserData, U32, ScalarReg, ) From b1b7538afdf02b3bacf8c13b42cfb19d09bf5380 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 17 Apr 2025 09:42:26 +0300 Subject: [PATCH 147/194] New Crowdin updates (#2785) * New translations en_us.ts (Arabic) * New translations en_us.ts (Italian) * New translations en_us.ts (Arabic) * New translations en_us.ts (Arabic) * New translations en_us.ts (Arabic) --- src/qt_gui/translations/ar_SA.ts | 299 ++++++++++++++++--------------- src/qt_gui/translations/it_IT.ts | 2 +- 2 files changed, 151 insertions(+), 150 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index f71ec7d3a..7ef3c6171 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -7,34 +7,34 @@ AboutDialog About shadPS4 - حول shadPS4 + حول محاكي الظل PS4 shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 هو محاكي تجريبي مفتوح المصدر لجهاز PlayStation 4. + محاكي الظل هو محاكي تجريبي مفتوح المصدر مخصص لـ PS4 This software should not be used to play games you have not legally obtained. - يجب عدم استخدام هذا البرنامج لتشغيل الألعاب التي لم تحصل عليها بشكل قانوني. + لا تستخدم هذا البرنامج لتشغيل الألعاب بطريقة غير قانونية. CheatsPatches Cheats / Patches for - الغِشّ / التصحيحات + شفرات / إصلاحات لـ Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n - الغش والتصحيحات هي ميزات تجريبية.\nاستخدمها بحذر.\n\nقم بتنزيل الغش بشكل فردي عن طريق اختيار المستودع والنقر على زر التنزيل.\nفي علامة تبويب التصحيحات، يمكنك تنزيل جميع التصحيحات دفعة واحدة، واختيار ما تريد استخدامه، وحفظ اختياراتك.\n\nنظرًا لأننا لا نقوم بتطوير الغش/التصحيحات،\nيرجى الإبلاغ عن أي مشاكل إلى مؤلف الغش.\n\nهل قمت بإنشاء غش جديد؟ قم بزيارة:\n + الشفرات والإصلاحات هي ميزات تجريبية.\nاستخدمها بحذر.\n\nقم بتنزيل الشفرات بشكل فردي عن طريق اختيار المستودع والضغط على تنزيل.\nفي علامة تبويب الإصلاحات، يمكنك تنزيل جميع الإصلاحات دفعة واحدة، واختيار ما تريد استخدامه، وحفظ اختياراتك.\n\nنظرًا لأننا لا نقوم بتطوير الشفرات والإصلاحات ،\nيرجى الإبلاغ عن أي مشاكل إلى مؤلف الشفرات.\n\nهل قمت بإنشاء شفرة جديد؟ قم بزيارة:\n No Image Available - لا تتوفر صورة + الصورة غير موجودة Serial: - الرقم التسلسلي: + الرَّقَم التسلسلي: Version: @@ -46,7 +46,7 @@ Select Cheat File: - اختر ملف الغش: + حدد مِلَفّ الشفرات: Repository: @@ -54,7 +54,7 @@ Download Cheats - تنزيل الغش + تحميل الشفرات Delete File @@ -66,19 +66,19 @@ You can delete the cheats you don't want after downloading them. - يمكنك حذف الغش الذي لا تريده بعد تنزيله. + يمكنك حذف الشفرات التي لا تريدها بعد تنزيلها. Do you want to delete the selected file?\n%1 - هل تريد حذف الملف المحدد؟\n%1 + هل ترغب في حذف الملف المحدد؟\n%1 Select Patch File: - اختر ملف التصحيح: + إختر ملف الباتش: Download Patches - تنزيل التصحيحات + تحميل الباتشات Save @@ -86,7 +86,7 @@ Cheats - الغش + الشفرات Patches @@ -106,7 +106,7 @@ No patch file found for the current serial. - لم يتم العثور على ملف تصحيح للرقم التسلسلي الحالي. + لم يتم العثور على مِلَفّ باتش للسيريال الحالي. Unable to open the file for reading. @@ -142,7 +142,7 @@ File already exists. Do you want to replace it? - الملف موجود بالفعل. هل تريد استبداله؟ + يوجد ملف بنفس الاسم. هل ترغب في استبداله؟ Failed to save file: @@ -154,19 +154,19 @@ Cheats Not Found - لم يتم العثور على الغش + لم يتم العثور على أي شفرات No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. - لم يتم العثور على غش لهذه اللعبة في هذا الإصدار من المستودع المحدد. حاول استخدام مستودع آخر أو إصدار آخر من اللعبة. + لم يتم العثور على شفرات لهذه اللعبة في هذه النسخة من المستودع المحدد. حاول استخدام مستودع آخر أو نسخة مختلفة من اللعبة. Cheats Downloaded Successfully - تم تنزيل الغش بنجاح + تم تحميل الشفرات بنجاح You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. - لقد نجحت في تنزيل الغش لهذا الإصدار من اللعبة من المستودع المحدد. يمكنك محاولة التنزيل من مستودع آخر. إذا كان متاحًا، يمكنك اختياره عن طريق تحديد الملف من القائمة. + تم تحميل الشفرات بنجاح لهذه النسخة من اللعبة من المستودع الذي اخترته. إذا أردت، يمكنك تجربة مستودع آخر، وإذا كان متاحًا، يمكنك استخدام الشفرات عن طريق اختيار الملف من القائمة. Failed to save: @@ -194,7 +194,7 @@ The game is in version: %1 - اللعبة في الإصدار: %1 + النسخة الحالية للعبة هي: %1 The downloaded patch only works on version: %1 @@ -329,7 +329,7 @@ Network error occurred while trying to access the URL - حدث خطأ في الشبكة أثناء محاولة الوصول إلى عنوان URL + حدث خطأ في الشبكة عند محاولة الوصول إلى الموقع Download Complete @@ -388,11 +388,11 @@ Boots - أحذية + إقلاع Menus - قوائم + القائمة Ingame @@ -407,7 +407,7 @@ ControlSettings Configure Controls - تعديل عناصر التحكم + إعدادات التحكم D-Pad @@ -431,11 +431,11 @@ Left Stick Deadzone (def:2 max:127) - مدى تسجيل الإدخال للعصا اليسرى (التلقائي:2 حد أقصى:127) + النقطة العمياء للعصا اليسرى (الافتراضي: 2، الحد الأقصى: 127) Left Deadzone - إعدادات مدى تسجيل الإدخال لعصا التحكم اليسرى + النقطة العمياء لليسار Left Stick @@ -507,11 +507,11 @@ Right Stick Deadzone (def:2, max:127) - مدى تسجيل الإدخال للعصا اليمنى (التلقائي:2 حد أقصى:127) + النقطة العمياء للعصا اليمنى (الافتراضي: 2، الحد الأقصى: 127) Right Deadzone - إعدادات مدى تسجيل الإدخال لعصا التحكم اليمنى + النقطة العمياء اليمنى Right Stick @@ -617,7 +617,7 @@ ElfViewer Open Folder - فتح المجلد + افتح مجلد @@ -647,7 +647,7 @@ Browse - تصفح + استعراض Error @@ -718,15 +718,15 @@ 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 @@ -734,15 +734,15 @@ Game has game-breaking glitches or unplayable performance - اللعبة بها قلتشات أو أداء غير قابل للتشغيل + اللعبة بها أخطاء جسيمة أو أداء يجعلها غير قابلة للعب Game can be completed with playable performance and no major glitches - يمكن الانتهاء من اللعبة مع الأداء القابل للتشغيل و لا توجد قلتشات كبيرة + يمكن إنهاء اللعبة بأداء جيد وبدون أعطال رئيسية Click to see details on github - انقر لرؤية التفاصيل على GitHub + اضغط لعرض التفاصيل على GitHub Last updated @@ -780,15 +780,15 @@ Cheats / Patches - الغش / التصحيحات + الشفرات / التصحيحات SFO Viewer - عارض SFO + عارض معلومات اللعبة (SFO) Trophy Viewer - عارض الجوائز + عارض الكؤوس Open Folder... @@ -820,11 +820,11 @@ Copy Version - إصدار النسخة + نسخ الإصدار Copy Size - حجم النسخة + نسخ حجم Copy All @@ -864,7 +864,7 @@ Submit a report - إرسال بلاغ + إرسال تقرير Shortcut creation @@ -888,7 +888,7 @@ This game has no update to delete! - لا تحتوي اللعبة على تحديث لحذفه! + لا يوجد تحديث لهذه اللعبة لحذفه! Update @@ -896,7 +896,7 @@ This game has no DLC to delete! - لا تحتوي اللعبة على DLC لحذفه! + لا توجد محتويات إضافية (DLC) لهذه اللعبة لحذفها! DLC @@ -916,11 +916,11 @@ Delete Save Data - حذف التخزينه + حذف بيانات الحفظ This game has no update folder to open! - لا تحتوي اللعبة على تحديث لفتحه! + لا يوجد مجلد تحديث لهذه اللعبة لفتحه! No log file found for this game! @@ -932,15 +932,15 @@ This game has no save data to delete! - هذه اللعبة لا تحتوي على أي تخزينات لحذفها! + لا توجد بيانات حفظ لهذه اللعبة لحذفها! This game has no saved trophies to delete! - هذه اللعبة ليس لديها كؤوس محفوظة للحذف! + لا توجد كؤوس محفوظة لهذه اللعبة لحذفها! Save Data - حفظ البيانات + بيانات الحفظ Trophy @@ -955,7 +955,7 @@ HelpDialog Quickstart - التشغيل السريع + البدء السريع FAQ @@ -963,22 +963,22 @@ Syntax - الصّيغة + البنية Special Bindings - إدخالات خاصة + الارتباطات الخاصة Keybindings - أزرار التحكم + تعيين الأزرار KBMSettings Configure Controls - تعديل عناصر التحكم + إعدادات جهاز التحكم D-Pad @@ -1026,7 +1026,7 @@ Use per-game configs - استخدام إعدادات كل لُعْبَة + استخدام إعدادات خاصة لكل لعبه L1 @@ -1038,7 +1038,7 @@ Text Editor - محرر النص + محرّر النصوص Help @@ -1058,15 +1058,15 @@ Touchpad Click - النقر على لوحة اللمس + زر لوحة اللمس Mouse to Joystick - الفأرة إلى عصا التحكم + تحويل الماوس إلى عصا التحكم *press F7 ingame to activate - * اضغط على F7 للتفعيل + * اضغط F7 داخل اللعبة للتفعيل R3 @@ -1078,15 +1078,15 @@ Mouse Movement Parameters - معطيات حركة الفأرة + إعدادات حركة الماوس note: click Help Button/Special Keybindings for more information - ملاحظة: انقر فوق زر المساعدة/روابط المفاتيح الخاصة للحصول على مزيد من المعلومات + ملاحظة: لمزيد من المعلومات عن التعيينات الخاصة، اضغط على زر المساعدة Face Buttons - أزرار الوجه + الأزرار الأمامية Triangle @@ -1106,11 +1106,11 @@ Right Analog Halfmode - تقليل سرعة عصا التحكم اليمنى للنصف + وضع النصف للعصا اليمنى (نصف الحساسية أو نصف الحركة) hold to move right stick at half-speed - الضغط باستمرار لتحريك العصا اليمنى بنصف السرعة + اضغط مع الاستمرار لتحريك العصا اليمنى بسرعة منخفضة Right Stick @@ -1118,15 +1118,15 @@ Speed Offset (def 0.125): - إزاحة السرعة (تلقائي 0.125): + تعويض السرعة (الافتراضي 0.125): Copy from Common Config - نسخ من الإعدادات الشائعة + نسخ من الإعدادات العامة Deadzone Offset (def 0.50): - إزاحة المدى الغير فعال (الأصل ٠.٥٠). + تعويض منطقة الخمول (الافتراضي 0.50): Speed Multiplier (def 1.0): @@ -1134,39 +1134,39 @@ Common Config Selected - الإعدادات الشائعة محدده + تم اختيار الإعدادات العامة This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - هذا الزر يقوم بنسخ تعيينات الأزرار من إعدادات المستخدم العامة لإعدادات المستخدم المحددة حالياً، ولا يمكن استعماله عندما تكون الإعدادات المستخدمة هي الإعدادات العامة. + يقوم هذا الزر بنسخ التعيينات من الإعدادات العامة إلى الملف الشخصي المحدد حاليًا، ولا يمكن استخدامه إذا كانت الإعدادات العامة هي الملف المحدد. Copy values from Common Config - نسخ من الإعدادات الشائعة + نسخ القيم من الإعدادات العامة Do you want to overwrite existing mappings with the mappings from the Common Config? - هل تريد استبدال التعيينات الحالية بالتعيينات العامة؟ + هل تريد استبدال التعيينات الحالية بالتعيينات من الإعدادات العامة؟ Unable to Save - غير قادر على الحفظ + تعذّر الحفظ Cannot bind any unique input more than once - لا يمكن ربط أي إدخال فريد أكثر من مرة + لا يمكن تعيين نفس الزر لأكثر من وظيفة Press a key - اضغط على مفتاح + اضغط زرًا Cannot set mapping - لا يمكن تعيين الأزرار + تعذّر تعيين الزر Mousewheel cannot be mapped to stick outputs - عجلة الفأرة لا يمكن تعيينها لعصا التحكم + لا يمكن تعيين عجلة الفأرة لمخرجات العصا Save @@ -1189,7 +1189,7 @@ MainWindow Open/Add Elf Folder - Elf فتح/إضافة مجلد + فتح / إضافة مجلد Elf Boot Game @@ -1225,11 +1225,11 @@ Exit the application. - الخروج من التطبيق. + إغلاق التطبيق Show Game List - إظهار قائمة الألعاب + عرض قائمة الألعاب Game List Refresh @@ -1281,7 +1281,7 @@ No games found. Please add your games to your library first. - لم يتم العثور على ألعاب. الرجاء إضافة ألعابك إلى مكتبتك أولاً. + لم يتم العثور على ألعاب. يرجى إضافة ألعابك إلى المكتبة أولاً. Search... @@ -1345,7 +1345,7 @@ Game List - ققائمة الألعاب + قائمة الألعاب * Unsupported Vulkan Version @@ -1353,7 +1353,7 @@ Download Cheats For All Installed Games - تنزيل الغش لجميع الألعاب المثبتة + تحميل الشفرات لجميع الألعاب المثبتة Download Patches For All Games @@ -1365,7 +1365,7 @@ You have downloaded cheats for all the games you have installed. - لقد قمت بتنزيل الغش لجميع الألعاب التي قمت بتثبيتها. + تم تحميل الشفرات لجميع الألعاب المثبتة. Patches Downloaded Successfully! @@ -1409,11 +1409,11 @@ Play - أبدأ اللعب + تشغيل Pause - توقف مؤقت + إيقاف مؤقت Stop @@ -1425,11 +1425,11 @@ Full Screen - وضع ملء الشاشة + الشاشة الكاملة Controllers - أذرعة التحكم + وحدات التحكم Keyboard @@ -1445,7 +1445,7 @@ Show Labels Under Icons - إظهار العلامات أسفل الأيقونات + إظهار التسميات تحت الأيقونات @@ -1464,7 +1464,7 @@ Console Language - لغة وحدة التحكم + لغة النظام Emulator Language @@ -1476,11 +1476,11 @@ Default tab when opening settings - علامة التبويب الافتراضية عند فتح الإعدادات + التبويب الافتراضي عند فتح الإعدادات Show Game Size In List - عرض حجم اللعبة في القائمة + إظهار حجم اللعبة في القائمة Show Splash @@ -1496,7 +1496,7 @@ Trophy Key - زر الميداليات + مفتاح الكأس Trophy @@ -1504,11 +1504,11 @@ Open the custom trophy images/sounds folder - افتح مجلد تخصيص اصوات/صور الميداليات + فتح مجلد الصور/الأصوات المخصصة للكؤوس Logger - المسجل + سجل الأحداث Log Type @@ -1520,23 +1520,23 @@ Open Log Location - افتح موقع السجل + فتح موقع السجل Input - إدخال + إعدادات المدخلات Cursor - مؤشر + مؤشر الماوس Hide Cursor - إخفاء المؤشر + إخفاء مؤشر الماوس Hide Cursor Idle Timeout - مهلة إخفاء المؤشر عند الخمول + إخفاء المؤشر بعد فترة الخمول s @@ -1552,7 +1552,7 @@ Graphics - الرسومات + الرسوميات GUI @@ -1564,7 +1564,7 @@ Graphics Device - جهاز الرسومات + جهاز الرسوميات Vblank Divider @@ -1572,15 +1572,15 @@ Advanced - متقدم + الإعدادات المتقدمة Enable Shaders Dumping - تمكين تفريغ الشيدرات + تمكين تصدير الشيدرات Enable NULL GPU - تمكين وحدة معالجة الرسومات الفارغة + تمكين GPU فارغ Enable HDR @@ -1604,23 +1604,23 @@ Debug - تصحيح الأخطاء + التصحيح Enable Debug Dumping - تمكين تفريغ التصحيح + تمكين تصدير بيانات التصحيح Enable Vulkan Validation Layers - Vulkan تمكين طبقات التحقق من + تمكين طبقات التحقق لـ Vulkan Enable Vulkan Synchronization Validation - Vulkan تمكين التحقق من تزامن + تمكين التحقق من تزامن Vulkan Enable RenderDoc Debugging - RenderDoc تمكين تصحيح أخطاء + تمكين التصحيح باستخدام RenderDoc Enable Crash Diagnostics @@ -1628,19 +1628,19 @@ Collect Shaders - اجمع برامج التظليل + جمع الشيدرات Copy GPU Buffers - انسخ التخزين المؤقت لوحدة معالجة الرُسوم + نسخ مخازن الرسوميات Host Debug Markers - استضافة علامات التصحيح + علامات التصحيح على المضيف Guest Debug Markers - ضيف علامات التصحيح + علامات التصحيح على الضيف Update @@ -1648,11 +1648,11 @@ Check for Updates at Startup - تحقق من التحديثات عند بدء التشغيل + التحقق من التحديثات عند بدء التشغيل Always Show Changelog - اظهر سجل التغيرات دائماً + عرض سجل التغييرات دائمًا Update Channel @@ -1672,7 +1672,7 @@ Disable Trophy Notification - إغلاق إشعارات الميداليات + تعطيل إشعار التروفي Background Image @@ -1684,7 +1684,7 @@ Opacity - درجة السواد + الشفافية Play title music @@ -1692,7 +1692,7 @@ Update Compatibility Database On Startup - تحديث قاعدة بيانات التوافق عند التشغيل + تحديث بيانات التوافق تلقائيًا عند التشغيل Game Compatibility @@ -1700,7 +1700,7 @@ Display Compatibility Data - إظهار معلومات التوافق + عرض بيانات التوافق Update Compatibility Database @@ -1728,7 +1728,7 @@ Point your mouse at an option to display its description. - وجّه الماوس نحو خيار لعرض وصفه. + ✅ مرّر مؤشر الماوس على أي خِيار لعرض وصفه. Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. @@ -1736,11 +1736,11 @@ Emulator Language:\nSets the language of the emulator's user interface. - لغة المحاكي:\nتحدد لغة واجهة المستخدم الخاصة بالمحاكي. + لغة المحاكي:\nتحدد اللغة المستخدمة في واجهة المحاكي. Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. - إظهار شاشة البداية:\nيعرض شاشة البداية الخاصة باللعبة (صورة خاصة) أثناء بدء التشغيل. + عرض شاشة البداية:\nيعرض شاشة البداية الخاصة باللعبة (صورة مميزة) أثناء بدء التشغيل. Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. @@ -1748,59 +1748,60 @@ Username:\nSets the PS4's account username, which may be displayed by some games. - اسم المستخدم:\nيضبط اسم حساب PS4، الذي قد يتم عرضه في بعض الألعاب. + اسم المستخدم:\nيحدد اسم حساب'PS4، وقد يظهر في بعض الألعاب. Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - مفتاح الميداليات:\nمفتاح يستخدم لفتح تشفير الميداليات. يجب أن يكون من جهاز مكسور الحماية.\nيجي أن يحتوي على أحرف نظام العد السداسي. + مفتاح الكؤوس:\nيُستخدم لفك تشفير الجوائز داخل الألعاب. يُستخرج من جهاز معدل.\nيجب أن يُكتب باستخدام رموز ست عشرية فقط. Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. - نوع السجل:\nيضبط ما إذا كان سيتم مزامنة مخرجات نافذة السجل للأداء. قد يؤثر سلبًا على المحاكاة. + نوع السجل:\nييحدد ما إذا كان سيتم مزامنة مخرجات نافذة السجل لتحسين الأداء. قد يؤدي ذلك إلى تأثيرات سلبية على المحاكاة. Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. - فلتر السجل:\nيقوم بتصفية السجل لطباعة معلومات محددة فقط.\nأمثلة: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" المستويات: Trace, Debug, Info, Warning, Error, Critical - بالترتيب، مستوى محدد يخفي جميع المستويات التي تسبقه ويعرض جميع المستويات بعده. + تصفية السجل:\nيمكنك تحديد نوع المعلومات التي سيتم عرضها في السجل فقط.\nأمثلة: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" االمستويات حسب الترتيب:: Trace, Debug, Info, Warning, Error, Critical - عند اختيار مستوى معين، يتم تجاهل كل ما قبله، وتسجيل كل ما بعده. 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. - تحديث: Release: إصدارات رسمية تصدر شهريًا، قد تكون قديمة بعض الشيء، لكنها أكثر استقرارًا واختبارًا. Nightly: إصدارات تطوير تحتوي على أحدث الميزات والإصلاحات، لكنها قد تحتوي على أخطاء وأقل استقرارًا. + تحديث: Release: نُسخ شهرية مستقرة وخضعت للاختبار، لكنها قد تكون قديمة. +Nightly: نُسخ تحتوي على أحدث الميزات، لكنها أقل استقرارًا وقد تحتوي على أخطاء. Background Image:\nControl the opacity of the game background image. - صورة الخلفية:\nيتحكم في درجة سواد صورة خلفية اللعبة. + صورة الخلفية:\nتحكم في شفافية صورة خلفية اللعبة. Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. - تشغيل موسيقى العنوان:\nإذا كانت اللعبة تدعم ذلك، قم بتمكين تشغيل موسيقى خاصة عند اختيار اللعبة في واجهة المستخدم. + تشغيل موسيقى العنوان:\nفي حال كانت اللعبة تدعم ذلك، يتم تشغيل موسيقى خاصة عند تحديدها في الواجهة. 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 إغلاق إشعارات الميداليات داخل اللعبة. تقدم الميداليات يمكن تتبعه باستخدام عارض الميداليات (قم بالضغط على زر الفأرة الأيمن داخل النافذة الرئيسية). + تعطيل إشعارات التروفي:\nيمنع ظهور إشعارات الجوائز أثناء اللعب، بينما يظل بإمكانك متابعة التقدم من خلال "عارض التروفي" (انقر بزر الفأرة الأيمن على اللعبة في النافذة الرئيسية). Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. - إخفاء المؤشر:\nاختر متى سيختفي المؤشر:\nأبداً: سترى الفأرة دائماً.\nعاطل: حدد وقتاً لاختفائه بعد أن يكون غير مستخدم.\nدائماً: لن ترى الفأرة أبداً. + إخفاء المؤشر:\nاختر متى يتم إخفاء مؤشر الماوس:\nأبدًا: يظهر دائمًا.\nعند الخمول: يختفي بعد فترة من عدم الحركة.\nدائمًا: لا يظهر إطلاقًا. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - حدد وقتاً لاختفاء الفأرة بعد أن تكون غير مستخدم. + مدة إخفاء المؤشر عند الخمول:\nالوقت (بالثواني) الذي ينتظره المؤشر قبل أن يختفي تلقائيًا عند عدم استخدامه. Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - سلوك زر العودة:\nيضبط زر العودة في وحدة التحكم ليحاكي الضغط على الموضع المحدد على لوحة اللمس في PS4. + سلوك زر الرجوع:\nيحدد وظيفة زر' الرجوع في وحدة التحكم لمحاكاة اللمس في موقع معيّن على لوحة اللمس الخاصة بـ PS4. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. - عرض بيانات التوافق:\nيقوم بإظهار معلومات توافق اللعبة في طريقة عرض الطاولة. تشغيل"تحديث التوافق عند التشغيل" للحصول على معلومات محدثة. + عرض بيانات التوافق:\nيعرض معلومات توافق الألعاب في عرض جدولي. فعّل ""تحديث التوافق عند بدء التشغيل"" للحصول على أحدث المعلومات. Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. - تحديث التوافق عند التشغيل:\nتحديث قاعدة بيانات التوافق تلقائياً عند تشغيل shadps4. + تحديث التوافق عند بدء التشغيل:\nيتم تحديث قاعدة بيانات التوافق تلقائيًا عند بدء تشغيل shadPS4. Update Compatibility Database:\nImmediately update the compatibility database. - تحديث قاعدة بيانات التوافق:\nقم بتحديث قاعدة بيانات التوافق حالاً. + تحديث قاعدة بيانات التوافق:\nتحديث فوري لقاعدة بيانات التوافق. Never @@ -1816,15 +1817,15 @@ Touchpad Left - لوحة اللمس اليسرى + الجانب الأيسر من لوحة اللمس Touchpad Right - لوحة اللمس اليمنى + الجانب الأيمن من لوحة اللمس Touchpad Center - وسط لوحة اللمس + مركز لوحة اللمس None @@ -1832,11 +1833,11 @@ Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. - جهاز الرسومات:\nعلى الأنظمة متعددة وحدات معالجة الرسومات، اختر وحدة معالجة الرسومات التي سيستخدمها المحاكي من قائمة منسدلة،\nأو اختر "Auto Select" لتحديدها تلقائيًا. + جهاز الرسوميات:\nفي الأنظمة التي تحتوي على أكثر من معالج رسومي، اختر وحدة المعالجة الرسومية GPU التي سيستخدمها المحاكي من القائمة المنسدلة،\nأو اختر ""تحديد تلقائي"" ليتم اختيارها تلقائيًا. Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. - العرض / الارتفاع:\nيضبط حجم نافذة المحاكي عند التشغيل، والذي يمكن تغيير حجمه أثناء اللعب.\nهذا يختلف عن دقة اللعبة نفسها. + العرض/الارتفاع:\nتحدد حجم نافذة المحاكي عند التشغيل، ويمكن تغيير حجمها أثناء اللعب.\nيختلف هذا عن دقة اللعبة داخل اللعبة. Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! @@ -1844,11 +1845,11 @@ Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. - تمكين تفريغ الـ Shaders:\nلأغراض تصحيح الأخطاء التقنية، يحفظ الـ Shaders الخاصة باللعبة في مجلد أثناء التشغيل. + تمكين تفريغ الشيدرات:\nلأغراض تصحيح الأخطاء التقنية، يقوم بحفظ شيدرات اللعبة في مجلد أثناء عرضها. Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - تمكين GPU الافتراضية:\nلأغراض تصحيح الأخطاء التقنية، يقوم بتعطيل عرض اللعبة كما لو لم يكن هناك بطاقة رسومات. + تمكين GPU فارغ:\nلأغراض تصحيح الأخطاء التقنية، يتم تعطيل عرض اللعبة كما لو لم يكن هناك كرت شاشة. Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. @@ -1856,7 +1857,7 @@ Game Folders:\nThe list of folders to check for installed games. - مجلدات اللعبة:\nقائمة بالمجلدات للتحقق من الألعاب المثبتة. + مجلدات اللعبة:\nقائمة المجلدات التي يتم فحصها للبحث عن الألعاب المثبتة. Add:\nAdd a folder to the list. @@ -1868,7 +1869,7 @@ Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. - تمكين تفريغ التصحيح:\nيحفظ رموز الاستيراد والتصدير ومعلومات رأس الملف للبرنامج الحالي لجهاز PS4 إلى دليل. + تمكين تفريغ التصحيح:\nيقوم بحفظ الرموز المستوردة والمصدرة، بالإضافة إلى معلومات رأس الملف للبرنامج الجاري تشغيله على PS4 في مجلد. Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. @@ -1980,11 +1981,11 @@ W: - W: + العرض: H: - H: + الارتفاع: Separate Log Files @@ -2055,7 +2056,7 @@ TrophyViewer Trophy Viewer - عارض الجوائز + عارض التروفي Select Game: diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 908013004..8c9e53611 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -138,7 +138,7 @@ File Exists - Il file è presente + Il file esiste già File already exists. Do you want to replace it? From 703620c7cd17d336f78ff897c66b3d48e448b0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Thu, 17 Apr 2025 08:43:14 +0200 Subject: [PATCH 148/194] [Windows] Adding Properties to the Executable (#2789) --- CMakeLists.txt | 9 ++++++++- src/shadps4.rc | 44 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ea8688df..76e7cb0ee 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,7 +200,14 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) -set(APP_VERSION "0.7.1 WIP") +# Set Version +set(EMULATOR_VERSION_MAJOR "0") +set(EMULATOR_VERSION_MINOR "7") +set(EMULATOR_VERSION_PATCH "1") + +set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") + +set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH} WIP") set(APP_IS_RELEASE false) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) diff --git a/src/shadps4.rc b/src/shadps4.rc index 8c984f260..9edccfbfd 100644 --- a/src/shadps4.rc +++ b/src/shadps4.rc @@ -1 +1,43 @@ -IDI_ICON1 ICON "images/shadps4.ico" \ No newline at end of file +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "winresrc.h" + +#define xstr(s) str(s) +#define str(s) #s + +// NEUTRAL LANG +LANGUAGE LANG_NEUTRAL, 0x8 + +// ICON +IDI_ICON1 ICON "images/shadps4.ico" + +// VERSION +VS_VERSION_INFO VERSIONINFO + FILEVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0 + PRODUCTVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0 + FILEFLAGSMASK 0x3fL + FILEFLAGS 0x0L + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "shadPS4 - PlayStation 4 Emulator\0" + VALUE "FileVersion", xstr(EMULATOR_VERSION_MAJOR) "." xstr(EMULATOR_VERSION_MINOR) "." xstr(EMULATOR_VERSION_PATCH) ".0\0" + VALUE "InternalName", "shadPS4\0" + VALUE "LegalCopyright", "Copyright 2025 shadPS4 Team\0" + VALUE "OriginalFilename", "shadPS4.exe\0" + VALUE "ProductName", "shadPS4\0" + VALUE "ProductVersion", xstr(EMULATOR_VERSION_MAJOR) "." xstr(EMULATOR_VERSION_MINOR) "." xstr(EMULATOR_VERSION_PATCH) ".0\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END From df89241eb81c9512f348ba066c5c52a8984b3bbf Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 17 Apr 2025 13:04:48 -0700 Subject: [PATCH 149/194] vk_rasterizer: Improve viewport depth calculations. (#2799) --- .../renderer_vulkan/vk_rasterizer.cpp | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index f8d09f011..b04b4a07e 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -999,16 +999,13 @@ void Rasterizer::UpdateViewportScissorState() const { boost::container::static_vector viewports; boost::container::static_vector scissors; - const auto& vp_ctl = regs.viewport_control; - const float reduce_z = - regs.clipper_control.clip_space == AmdGpu::Liverpool::ClipSpace::MinusWToW ? 1.0f : 0.0f; - if (regs.polygon_control.enable_window_offset && (regs.window_offset.window_x_offset != 0 || regs.window_offset.window_y_offset != 0)) { LOG_ERROR(Render_Vulkan, "PA_SU_SC_MODE_CNTL.VTX_WINDOW_OFFSET_ENABLE support is not yet implemented."); } + const auto& vp_ctl = regs.viewport_control; for (u32 i = 0; i < Liverpool::NumViewports; i++) { const auto& vp = regs.viewports[i]; const auto& vp_d = regs.viewport_depths[i]; @@ -1019,16 +1016,32 @@ void Rasterizer::UpdateViewportScissorState() const { const auto zoffset = vp_ctl.zoffset_enable ? vp.zoffset : 0.f; const auto zscale = vp_ctl.zscale_enable ? vp.zscale : 1.f; - vk::Viewport viewport = { - .minDepth = zoffset - zscale * reduce_z, - .maxDepth = zscale + zoffset, - }; + vk::Viewport viewport{}; + + // https://gitlab.freedesktop.org/mesa/mesa/-/blob/209a0ed/src/amd/vulkan/radv_pipeline_graphics.c#L688-689 + // https://gitlab.freedesktop.org/mesa/mesa/-/blob/209a0ed/src/amd/vulkan/radv_cmd_buffer.c#L3103-3109 + // When the clip space is ranged [-1...1], the zoffset is centered. + // By reversing the above viewport calculations, we get the following: + if (regs.clipper_control.clip_space == AmdGpu::Liverpool::ClipSpace::MinusWToW) { + viewport.minDepth = zoffset - zscale; + viewport.maxDepth = zoffset + zscale; + } else { + viewport.minDepth = zoffset; + viewport.maxDepth = zoffset + zscale; + } + + if (!regs.depth_render_override.disable_viewport_clamp) { + // Apply depth clamp. + viewport.minDepth = std::max(viewport.minDepth, vp_d.zmin); + viewport.maxDepth = std::min(viewport.maxDepth, vp_d.zmax); + } + if (!instance.IsDepthRangeUnrestrictedSupported()) { - // Unrestricted depth range not supported by device. Make best attempt - // by restricting to valid range. + // Unrestricted depth range not supported by device. Restrict to valid range. viewport.minDepth = std::max(viewport.minDepth, 0.f); viewport.maxDepth = std::min(viewport.maxDepth, 1.f); } + if (regs.IsClipDisabled()) { // In case if clipping is disabled we patch the shader to convert vertex position // from screen space coordinates to NDC by defining a render space as full hardware @@ -1048,6 +1061,7 @@ void Rasterizer::UpdateViewportScissorState() const { viewport.width = xscale * 2.0f; viewport.height = yscale * 2.0f; } + viewports.push_back(viewport); auto vp_scsr = scsr; From 20b11f2d63353c8f7c10a7d1ae1f180e9a96e172 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 18 Apr 2025 03:00:14 -0500 Subject: [PATCH 150/194] libkernel: Fix sceKernelAllocateDirectMemory and sceKernelAvailableDirectMemorySize (#2803) * AllocateDirectMemory fixes search_start and search_end were ignored in certain cases, this fixes that issue. I've also basically rewritten the function in the process, since the lack of documentation made it difficult to make the proper adjustments. * DirectQueryAvailable fixes remaining_size was calculated incorrectly in cases where a free dmem_area had a base earlier than search_start, or an end after search_end. * Reduce sceKernelGetDirectMemorySize log severity By this point, we've confirmed that sceKernelGetDirectMemorySize is hardware-accurate. There's no reason to clog logs with this function, which games usually call before every sceKernelAllocateDirectMemory call. * Clang * Fix phys_addr_out phys_addr_out should be equal to search_start in cases where search_start is greater than the dmem_area base. * Dividing by zero is fun Need to check for alignment when aligning things. * Update memory.cpp * Clang --- src/core/libraries/kernel/memory.cpp | 2 +- src/core/memory.cpp | 65 ++++++++++++++++++---------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 7b3ac5646..cbc5ad77e 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -19,7 +19,7 @@ namespace Libraries::Kernel { u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() { - LOG_WARNING(Kernel_Vmm, "called"); + LOG_TRACE(Kernel_Vmm, "called"); const auto* memory = Core::Memory::Instance(); return memory->GetTotalDirectSize(); } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 8b108a654..f88cede4e 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -139,35 +139,35 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, alignment = alignment > 0 ? alignment : 16_KB; auto dmem_area = FindDmemArea(search_start); + auto mapping_start = search_start > dmem_area->second.base + ? Common::AlignUp(search_start, alignment) + : Common::AlignUp(dmem_area->second.base, alignment); + auto mapping_end = Common::AlignUp(mapping_start + size, alignment); - const auto is_suitable = [&] { - if (dmem_area == dmem_map.end()) { - return false; - } - const auto aligned_base = Common::AlignUp(dmem_area->second.base, alignment); - const auto alignment_size = aligned_base - dmem_area->second.base; - const auto remaining_size = - dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0; - return dmem_area->second.is_free && remaining_size >= size; - }; - while (dmem_area != dmem_map.end() && !is_suitable() && - dmem_area->second.GetEnd() <= search_end) { - ++dmem_area; + // Find the first free, large enough dmem area in the range. + while ((!dmem_area->second.is_free || dmem_area->second.GetEnd() < mapping_end) && + dmem_area != dmem_map.end()) { + // The current dmem_area isn't suitable, move to the next one. + dmem_area++; + + // Update local variables based on the new dmem_area + mapping_start = search_start > dmem_area->second.base + ? Common::AlignUp(search_start, alignment) + : Common::AlignUp(dmem_area->second.base, alignment); + mapping_end = Common::AlignUp(mapping_start + size, alignment); } - if (!is_suitable()) { + + if (dmem_area == dmem_map.end()) { + // There are no suitable mappings in this range 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; - free_addr = Common::AlignUp(free_addr, alignment); - // Add the allocated region to the list and commit its pages. - auto& area = CarveDmemArea(free_addr, size)->second; + auto& area = CarveDmemArea(mapping_start, size)->second; area.memory_type = memory_type; area.is_free = false; - return free_addr; + return mapping_start; } void MemoryManager::Free(PAddr phys_addr, size_t size) { @@ -632,17 +632,34 @@ int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, si auto dmem_area = FindDmemArea(search_start); PAddr paddr{}; size_t max_size{}; - while (dmem_area != dmem_map.end() && dmem_area->second.GetEnd() <= search_end) { + + while (dmem_area != dmem_map.end()) { if (!dmem_area->second.is_free) { dmem_area++; continue; } - const auto aligned_base = alignment > 0 ? Common::AlignUp(dmem_area->second.base, alignment) - : dmem_area->second.base; + auto aligned_base = alignment > 0 ? Common::AlignUp(dmem_area->second.base, alignment) + : dmem_area->second.base; const auto alignment_size = aligned_base - dmem_area->second.base; - const auto remaining_size = + auto remaining_size = dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0; + + if (dmem_area->second.base < search_start) { + // We need to trim remaining_size to ignore addresses before search_start + remaining_size = remaining_size > (search_start - dmem_area->second.base) + ? remaining_size - (search_start - dmem_area->second.base) + : 0; + aligned_base = alignment > 0 ? Common::AlignUp(search_start, alignment) : search_start; + } + + if (dmem_area->second.GetEnd() > search_end) { + // We need to trim remaining_size to ignore addresses beyond search_end + remaining_size = remaining_size > (search_start - dmem_area->second.base) + ? remaining_size - (dmem_area->second.GetEnd() - search_end) + : 0; + } + if (remaining_size > max_size) { paddr = aligned_base; max_size = remaining_size; From 23c616c647a20c5c5f0f8a5785af003460e50ba9 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Fri, 18 Apr 2025 22:41:13 +0200 Subject: [PATCH 151/194] why was this hardcoded in this one specific place (#2809) --- src/qt_gui/kbm_gui.cpp | 55 +++--------------------------------------- 1 file changed, 3 insertions(+), 52 deletions(-) diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 8777dda95..2e1f6ddce 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -127,58 +127,9 @@ tr("Do you want to overwrite existing mappings with the mappings from the Common } void KBMSettings::ButtonConnects() { - connect(ui->CrossButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->CrossButton); }); - connect(ui->CircleButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->CircleButton); }); - connect(ui->TriangleButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->TriangleButton); }); - connect(ui->SquareButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->SquareButton); }); - - connect(ui->L1Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->L1Button); }); - connect(ui->L2Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->L2Button); }); - connect(ui->L3Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->L3Button); }); - connect(ui->R1Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->R1Button); }); - connect(ui->R2Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->R2Button); }); - connect(ui->R3Button, &QPushButton::clicked, this, [this]() { StartTimer(ui->R3Button); }); - - connect(ui->TouchpadButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->TouchpadButton); }); - connect(ui->OptionsButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->OptionsButton); }); - - connect(ui->DpadUpButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->DpadUpButton); }); - connect(ui->DpadDownButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->DpadDownButton); }); - connect(ui->DpadLeftButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->DpadLeftButton); }); - connect(ui->DpadRightButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->DpadRightButton); }); - - connect(ui->LStickUpButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->LStickUpButton); }); - connect(ui->LStickDownButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->LStickDownButton); }); - connect(ui->LStickLeftButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->LStickLeftButton); }); - connect(ui->LStickRightButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->LStickRightButton); }); - - connect(ui->RStickUpButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->RStickUpButton); }); - connect(ui->RStickDownButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->RStickDownButton); }); - connect(ui->RStickLeftButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->RStickLeftButton); }); - connect(ui->RStickRightButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->RStickRightButton); }); - - connect(ui->LHalfButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->LHalfButton); }); - connect(ui->RHalfButton, &QPushButton::clicked, this, - [this]() { StartTimer(ui->RHalfButton); }); + for (auto& button : ButtonsList) { + connect(button, &QPushButton::clicked, this, [this, &button]() { StartTimer(button); }); + } } void KBMSettings::DisableMappingButtons() { From 0feb2e7211bb4cb5cedfd6a1363e42b505b628b2 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:41:33 -0500 Subject: [PATCH 152/194] Emulate memory behavior of libSceGnmDriver initialization (#2807) * Emulate memory behavior of libSceGnmDriver _DT_INIT Due to the unique way some games check for sceKernelAllocateDirectMemory failures, emulating this properly is necessary. * Clang * Fix address input for direct memory call * Fix bug with DirectQueryAvailable Missed this in my prior PR. * DirectQueryAvailable fix Fixes error cases to be more hardware accurate. --- src/core/libraries/gnmdriver/gnmdriver.cpp | 2 +- src/core/libraries/kernel/memory.cpp | 6 ------ src/core/linker.cpp | 16 ++++++++++++++-- src/core/memory.cpp | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index e8560b2b8..25ac4921c 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -2804,7 +2804,7 @@ void RegisterlibSceGnmDriver(Core::Loader::SymbolsResolver* sym) { liverpool = std::make_unique(); presenter = std::make_unique(*g_window, liverpool.get()); - const int result = sceKernelGetCompiledSdkVersion(&sdk_version); + const s32 result = sceKernelGetCompiledSdkVersion(&sdk_version); if (result != ORBIS_OK) { sdk_version = 0; } diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index cbc5ad77e..8a0c91479 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -106,12 +106,6 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE if (physAddrOut == nullptr || sizeOut == nullptr) { return ORBIS_KERNEL_ERROR_EINVAL; } - if (searchEnd > sceKernelGetDirectMemorySize()) { - return ORBIS_KERNEL_ERROR_EINVAL; - } - if (searchEnd <= searchStart) { - return ORBIS_KERNEL_ERROR_ENOMEM; - } auto* memory = Core::Memory::Instance(); diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 4ccb9d943..69deb464f 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -108,10 +108,22 @@ void Linker::Execute(const std::vector args) { static constexpr s64 InternalMemorySize = 0x1000000; void* addr_out{reinterpret_cast(KernelAllocBase)}; - const s32 ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory( - &addr_out, InternalMemorySize, 3, 0, "SceKernelInternalMemory"); + s32 ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory(&addr_out, InternalMemorySize, 3, + 0, "SceKernelInternalMemory"); ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping"); + // Simulate libSceGnmDriver initialization, which maps a chunk of direct memory. + // Some games fail without accurately emulating this behavior. + s64 phys_addr{}; + ret = Libraries::Kernel::sceKernelAllocateDirectMemory( + 0, Libraries::Kernel::sceKernelGetDirectMemorySize(), 0x10000, 0x10000, 3, &phys_addr); + if (ret == 0) { + void* addr{reinterpret_cast(0xfe0000000)}; + ret = Libraries::Kernel::sceKernelMapNamedDirectMemory(&addr, 0x10000, 0x13, 0, phys_addr, + 0x10000, "SceGnmDriver"); + } + ASSERT_MSG(ret == 0, "Unable to emulate libSceGnmDriver initialization"); + main_thread.Run([this, module, args](std::stop_token) { Common::SetCurrentThreadName("GAME_MainThread"); LoadSharedLibraries(); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index f88cede4e..cb80d6be4 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -655,7 +655,7 @@ int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, si if (dmem_area->second.GetEnd() > search_end) { // We need to trim remaining_size to ignore addresses beyond search_end - remaining_size = remaining_size > (search_start - dmem_area->second.base) + remaining_size = remaining_size > (dmem_area->second.GetEnd() - search_end) ? remaining_size - (dmem_area->second.GetEnd() - search_end) : 0; } From 81ada41baa113da41446e093041ca5066c9b5409 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Fri, 18 Apr 2025 22:42:17 +0200 Subject: [PATCH 153/194] NpAuth library stub (#2808) * Add the NpAuth library * clang * you didn't see anything * Add some random return to make games at least start using this library * i'm once again asking for your continued ignorance of what i'm forgetting to not push * debug logging * apparently just this is still enough * this isn't used but it still shouldn't be incorrect --- CMakeLists.txt | 2 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + src/core/libraries/libs.cpp | 2 + src/core/libraries/np_auth/np_auth.cpp | 99 ++++++++++++++++++++++++++ src/core/libraries/np_auth/np_auth.h | 29 ++++++++ 6 files changed, 134 insertions(+) create mode 100644 src/core/libraries/np_auth/np_auth.cpp create mode 100644 src/core/libraries/np_auth/np_auth.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 76e7cb0ee..beea8a1e0 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -570,6 +570,8 @@ set(NP_LIBS src/core/libraries/np_common/np_common.cpp src/core/libraries/np_web_api/np_web_api.h src/core/libraries/np_party/np_party.cpp src/core/libraries/np_party/np_party.h + src/core/libraries/np_auth/np_auth.cpp + src/core/libraries/np_auth/np_auth.h ) set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index bed7802ed..867d62916 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -101,6 +101,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, Ssl2) \ SUB(Lib, SysModule) \ SUB(Lib, Move) \ + SUB(Lib, NpAuth) \ SUB(Lib, NpCommon) \ SUB(Lib, NpManager) \ SUB(Lib, NpScore) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index c07efbc0d..e5714a81a 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -69,6 +69,7 @@ enum class Class : u8 { Lib_Http2, ///< The LibSceHttp2 implementation. Lib_SysModule, ///< The LibSceSysModule implementation Lib_NpCommon, ///< The LibSceNpCommon implementation + Lib_NpAuth, ///< The LibSceNpAuth implementation Lib_NpManager, ///< The LibSceNpManager implementation Lib_NpScore, ///< The LibSceNpScore implementation Lib_NpTrophy, ///< The LibSceNpTrophy implementation diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index d0f82556e..3f5baf640 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -27,6 +27,7 @@ #include "core/libraries/network/netctl.h" #include "core/libraries/network/ssl.h" #include "core/libraries/network/ssl2.h" +#include "core/libraries/np_auth/np_auth.h" #include "core/libraries/np_common/np_common.h" #include "core/libraries/np_manager/np_manager.h" #include "core/libraries/np_party/np_party.h" @@ -88,6 +89,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::NpScore::RegisterlibSceNpScore(sym); Libraries::NpTrophy::RegisterlibSceNpTrophy(sym); Libraries::NpWebApi::RegisterlibSceNpWebApi(sym); + Libraries::NpAuth::RegisterlibSceNpAuth(sym); Libraries::ScreenShot::RegisterlibSceScreenShot(sym); Libraries::AppContent::RegisterlibSceAppContent(sym); Libraries::PngDec::RegisterlibScePngDec(sym); diff --git a/src/core/libraries/np_auth/np_auth.cpp b/src/core/libraries/np_auth/np_auth.cpp new file mode 100644 index 000000000..9ec986f3c --- /dev/null +++ b/src/core/libraries/np_auth/np_auth.cpp @@ -0,0 +1,99 @@ +// 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/libs.h" +#include "core/libraries/np_auth/np_auth.h" + +namespace Libraries::NpAuth { + +s32 PS4_SYSV_ABI sceNpAuthGetAuthorizationCode() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthGetIdToken() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthAbortRequest() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthCreateAsyncRequest() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthCreateRequest() { + LOG_WARNING(Lib_NpAuth, "(DUMMY) called"); + return 1; +} + +s32 PS4_SYSV_ABI sceNpAuthDeleteRequest(s32 id) { + LOG_WARNING(Lib_NpAuth, "(DUMMY) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthGetAuthorizationCodeA() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthGetAuthorizationCodeV3() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthGetIdTokenA() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthGetIdTokenV3() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthPollAsync() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthSetTimeout() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpAuthWaitAsync() { + LOG_ERROR(Lib_NpAuth, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceNpAuth(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("KxGkOrQJTqY", "libSceNpAuthCompat", 1, "libSceNpAuth", 1, 1, + sceNpAuthGetAuthorizationCode); + LIB_FUNCTION("uaB-LoJqHis", "libSceNpAuthCompat", 1, "libSceNpAuth", 1, 1, sceNpAuthGetIdToken); + LIB_FUNCTION("cE7wIsqXdZ8", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, sceNpAuthAbortRequest); + LIB_FUNCTION("N+mr7GjTvr8", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, + sceNpAuthCreateAsyncRequest); + LIB_FUNCTION("6bwFkosYRQg", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, sceNpAuthCreateRequest); + LIB_FUNCTION("H8wG9Bk-nPc", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, sceNpAuthDeleteRequest); + LIB_FUNCTION("KxGkOrQJTqY", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, + sceNpAuthGetAuthorizationCode); + LIB_FUNCTION("qAUXQ9GdWp8", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, + sceNpAuthGetAuthorizationCodeA); + LIB_FUNCTION("KI4dHLlTNl0", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, + sceNpAuthGetAuthorizationCodeV3); + LIB_FUNCTION("uaB-LoJqHis", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, sceNpAuthGetIdToken); + LIB_FUNCTION("CocbHVIKPE8", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, sceNpAuthGetIdTokenA); + LIB_FUNCTION("RdsFVsgSpZY", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, sceNpAuthGetIdTokenV3); + LIB_FUNCTION("gjSyfzSsDcE", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, sceNpAuthPollAsync); + LIB_FUNCTION("PM3IZCw-7m0", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, sceNpAuthSetTimeout); + LIB_FUNCTION("SK-S7daqJSE", "libSceNpAuth", 1, "libSceNpAuth", 1, 1, sceNpAuthWaitAsync); +}; + +} // namespace Libraries::NpAuth \ No newline at end of file diff --git a/src/core/libraries/np_auth/np_auth.h b/src/core/libraries/np_auth/np_auth.h new file mode 100644 index 000000000..a6a66b452 --- /dev/null +++ b/src/core/libraries/np_auth/np_auth.h @@ -0,0 +1,29 @@ +// 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::NpAuth { + +s32 PS4_SYSV_ABI sceNpAuthGetAuthorizationCode(); +s32 PS4_SYSV_ABI sceNpAuthGetIdToken(); +s32 PS4_SYSV_ABI sceNpAuthAbortRequest(); +s32 PS4_SYSV_ABI sceNpAuthCreateAsyncRequest(); +s32 PS4_SYSV_ABI sceNpAuthCreateRequest(); +s32 PS4_SYSV_ABI sceNpAuthDeleteRequest(s32 id); +s32 PS4_SYSV_ABI sceNpAuthGetAuthorizationCodeA(); +s32 PS4_SYSV_ABI sceNpAuthGetAuthorizationCodeV3(); +s32 PS4_SYSV_ABI sceNpAuthGetIdTokenA(); +s32 PS4_SYSV_ABI sceNpAuthGetIdTokenV3(); +s32 PS4_SYSV_ABI sceNpAuthPollAsync(); +s32 PS4_SYSV_ABI sceNpAuthSetTimeout(); +s32 PS4_SYSV_ABI sceNpAuthWaitAsync(); + +void RegisterlibSceNpAuth(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::NpAuth \ No newline at end of file From 6e963a7f7154ada20533d5efa3f705ffc5005c76 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Fri, 18 Apr 2025 21:42:48 +0100 Subject: [PATCH 154/194] Changing the maximum amount of contributors on the README (#2804) * Update README.md * Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e2248970..ba0fe225d 100644 --- a/README.md +++ b/README.md @@ -166,11 +166,11 @@ Open a PR and we'll check it :) # Translations -If you want to translate shadPS4 to your language we use [**crowdin**](https://crowdin.com/project/shadps4-emulator). +If you want to translate shadPS4 to your language we use [**Crowdin**](https://crowdin.com/project/shadps4-emulator). # Contributors - + From e11ffe242bcc7fe41dc6ee817a8acfb614b176a6 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 18 Apr 2025 23:47:59 +0300 Subject: [PATCH 155/194] Update team member on readme --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ba0fe225d..e24d57807 100644 --- a/README.md +++ b/README.md @@ -146,16 +146,13 @@ The following firmware modules are supported and must be placed in shadPS4's `us # Main team - [**georgemoralis**](https://github.com/georgemoralis) -- [**raphaelthegreat**](https://github.com/raphaelthegreat) - [**psucien**](https://github.com/psucien) -- [**skmp**](https://github.com/skmp) -- [**wheremyfoodat**](https://github.com/wheremyfoodat) -- [**raziel1000**](https://github.com/raziel1000) - [**viniciuslrangel**](https://github.com/viniciuslrangel) - [**roamic**](https://github.com/vladmikhalin) -- [**poly**](https://github.com/polybiusproxy) - [**squidbus**](https://github.com/squidbus) - [**frodo**](https://github.com/baggins183) +- [**Stephen Miller**](https://github.com/StevenMiller123) +- [**kalaposfos13**](https://github.com/kalaposfos13) Logo is done by [**Xphalnos**](https://github.com/Xphalnos) From d0dbb737d9a87e2ea745e0d0b282c795beb62104 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 19 Apr 2025 17:51:43 +0300 Subject: [PATCH 156/194] New translations en_us.ts (Arabic) (#2810) --- src/qt_gui/translations/ar_SA.ts | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index 7ef3c6171..e434b3259 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -66,7 +66,7 @@ You can delete the cheats you don't want after downloading them. - يمكنك حذف الشفرات التي لا تريدها بعد تنزيلها. + يمكنك حذف الشفرات التي لا 'تريدها بعد تنزيلها. Do you want to delete the selected file?\n%1 @@ -74,11 +74,11 @@ Select Patch File: - إختر ملف الباتش: + اختر مِلَف التصحيح: Download Patches - تحميل الباتشات + تحميل ملفات التصحيح Save @@ -98,15 +98,15 @@ No patch selected. - لم يتم اختيار أي تصحيح. + لم يتم تحديد أي مِلَف تصحيح. Unable to open files.json for reading. - تعذر فتح files.json للقراءة. + تعذّر فتح مِلَف files.json للقراءة. No patch file found for the current serial. - لم يتم العثور على مِلَفّ باتش للسيريال الحالي. + لم يتم العثور على مِلَف تصحيح للسيريال الحالي. Unable to open the file for reading. @@ -126,11 +126,11 @@ Options saved successfully. - تم حفظ الخيارات بنجاح. + تم حفظ الإعدادات. Invalid Source - مصدر غير صالح + المصدر غير صالح The selected source is invalid. @@ -138,11 +138,11 @@ File Exists - الملف موجود + المِلَف موجود مسبقًا File already exists. Do you want to replace it? - يوجد ملف بنفس الاسم. هل ترغب في استبداله؟ + المِلَف موجود مسبقًا. هل ترغب في استبداله؟ Failed to save file: @@ -158,7 +158,7 @@ No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. - لم يتم العثور على شفرات لهذه اللعبة في هذه النسخة من المستودع المحدد. حاول استخدام مستودع آخر أو نسخة مختلفة من اللعبة. + لم يتم العثور على شفرات لهذه اللعبة في هذا الإصدار من المستودع المحدد. جرّب مستودعًا آخر أو إصدارًا مختلفًا من اللعبة. Cheats Downloaded Successfully @@ -182,7 +182,7 @@ Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. - تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات لجميع الألعاب، ولا داعي لتنزيلها بشكل فردي لكل لعبة كما هو الحال مع الغش. إذا لم يظهر التحديث، قد يكون السبب أنه غير متوفر للإصدار وسيريال اللعبة المحدد. + تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات المتوفرة لجميع الألعاب، ولا حاجة إلى تنزيلها بشكل فردي لكل لعبة كما هو الحال مع الشفرات. إذا لم يظهر التصحيح، فقد لا يكون متوفرًا للسيريال أو الإصدار المحدد من اللعبة. Failed to parse JSON data from HTML. @@ -190,15 +190,15 @@ Failed to retrieve HTML page. - .HTML فشل في استرجاع صفحة + فشل في جلب صفحة HTML. The game is in version: %1 - النسخة الحالية للعبة هي: %1 + إصدار اللعبة الحالي: %1 The downloaded patch only works on version: %1 - الباتش الذي تم تنزيله يعمل فقط على الإصدار: %1 + التصحيح الذي تم تنزيله يعمل فقط مع الإصدار:%1 You may need to update your game. @@ -206,7 +206,7 @@ Incompatibility Notice - إشعار عدم التوافق + إشعار بعدم التوافق Failed to open file: @@ -238,7 +238,7 @@ Can't apply cheats before the game is started - لا يمكن تطبيق الغش قبل بدء اللعبة. + لا 'يمكن تطبيق الشفرات قبل بَدْء اللعبة Close @@ -249,7 +249,7 @@ CheckUpdate Auto Updater - محدث تلقائي + التحديثات التلقائية Error @@ -261,7 +261,7 @@ The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. - يتيح التحديث التلقائي ما يصل إلى 60 عملية تحقق من التحديث في الساعة.\nلقد وصلت إلى هذا الحد. الرجاء المحاولة مرة أخرى لاحقًا. + تسمح التحديثات التلقائية بـ 60 عملية تحقق من التحديث في الساعة.\nلقد وصلت إلى الحد المسموح به. الرجاء المحاولة لاحقًا. Failed to parse update information. From d188510e59aeef73aa7ca7076cf53e5a6e5d2ab6 Mon Sep 17 00:00:00 2001 From: Missake212 Date: Sat, 19 Apr 2025 15:51:59 +0100 Subject: [PATCH 157/194] Discord logo fix on the README. (#2811) * Fix the broken Discord logo on the README * Update README.md change the size to match better --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e24d57807..985bba586 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ SPDX-License-Identifier: GPL-2.0-or-later

- + From 111ff2bedd2cc8797d52a8be786245eada9ed2ad Mon Sep 17 00:00:00 2001 From: bigqy <52437374+bigQY@users.noreply.github.com> Date: Sat, 19 Apr 2025 22:52:43 +0800 Subject: [PATCH 158/194] =?UTF-8?q?Fixe:=20Updated=20the=20timing=20of=20s?= =?UTF-8?q?etting=20text=20translation=20for=20the=20=E2=80=9CShow=20label?= =?UTF-8?q?=20under=20icon=E2=80=9D=20action=20in=20the=20toolbar=20to=20s?= =?UTF-8?q?et=20the=20text=20after=20retranslateUi.=20(#2806)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/qt_gui/main_window_ui.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 2c4d4480b..518f860b0 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -106,8 +106,6 @@ public: toggleLabelsAct = new QAction(MainWindow); toggleLabelsAct->setObjectName("toggleLabelsAct"); - toggleLabelsAct->setText( - QCoreApplication::translate("MainWindow", "Show Labels Under Icons")); toggleLabelsAct->setCheckable(true); toggleLabelsAct->setChecked(Config::getShowLabelsUnderIcons()); @@ -413,6 +411,7 @@ public: setThemeTokyoNight->setText("Tokyo Night"); setThemeOled->setText("OLED"); toolBar->setWindowTitle(QCoreApplication::translate("MainWindow", "toolBar", nullptr)); + toggleLabelsAct->setText(QCoreApplication::translate("MainWindow", "Show Labels Under Icons")); } // retranslateUi }; From 69777e2ffac74477f97febe0f2705e4cf989cff3 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sat, 19 Apr 2025 17:19:40 +0200 Subject: [PATCH 159/194] hotfix: clang-format --- src/qt_gui/main_window_ui.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 518f860b0..4d3481c07 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -411,7 +411,8 @@ public: setThemeTokyoNight->setText("Tokyo Night"); setThemeOled->setText("OLED"); toolBar->setWindowTitle(QCoreApplication::translate("MainWindow", "toolBar", nullptr)); - toggleLabelsAct->setText(QCoreApplication::translate("MainWindow", "Show Labels Under Icons")); + toggleLabelsAct->setText( + QCoreApplication::translate("MainWindow", "Show Labels Under Icons")); } // retranslateUi }; From 5be726ca3b311a3fd780efb8d9bd2e7b06349430 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:26:52 -0500 Subject: [PATCH 160/194] Revert libSceGnmDriver initialization emulation (#2816) Under normal circumstances, this mapping should only occur when libSceGnmDriver initializes. From what I can tell, this can be after game code starts running. Until there's a better way to accurately handle this, allocating this memory breaks some games. This revert fixes the regression in games using the GFD engine. --- src/core/linker.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 69deb464f..0f86376af 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -112,18 +112,6 @@ void Linker::Execute(const std::vector args) { 0, "SceKernelInternalMemory"); ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping"); - // Simulate libSceGnmDriver initialization, which maps a chunk of direct memory. - // Some games fail without accurately emulating this behavior. - s64 phys_addr{}; - ret = Libraries::Kernel::sceKernelAllocateDirectMemory( - 0, Libraries::Kernel::sceKernelGetDirectMemorySize(), 0x10000, 0x10000, 3, &phys_addr); - if (ret == 0) { - void* addr{reinterpret_cast(0xfe0000000)}; - ret = Libraries::Kernel::sceKernelMapNamedDirectMemory(&addr, 0x10000, 0x13, 0, phys_addr, - 0x10000, "SceGnmDriver"); - } - ASSERT_MSG(ret == 0, "Unable to emulate libSceGnmDriver initialization"); - main_thread.Run([this, module, args](std::stop_token) { Common::SetCurrentThreadName("GAME_MainThread"); LoadSharedLibraries(); From 0c86c54d48533e3505e56e639059d28331fe4bc3 Mon Sep 17 00:00:00 2001 From: Fire Cube Date: Mon, 21 Apr 2025 23:25:15 +0200 Subject: [PATCH 161/194] Implement SET_PC_B64 instruction (#2823) * basic impl * minor improvements * clang * more clang * improvements requested by squidbus --- .../frontend/control_flow_graph.cpp | 63 ++++++++++++++++++- .../frontend/instruction.cpp | 2 +- .../frontend/translate/scalar_flow.cpp | 1 + 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index cf1882b8c..b53db9e94 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -4,6 +4,7 @@ #include #include #include "common/assert.h" +#include "common/logging/log.h" #include "shader_recompiler/frontend/control_flow_graph.h" namespace Shader::Gcn { @@ -67,6 +68,39 @@ static bool IgnoresExecMask(const GcnInst& inst) { return false; } +static std::optional ResolveSetPcTarget(std::span list, u32 setpc_index, + std::span pc_map) { + if (setpc_index < 3) { + return std::nullopt; + } + + const auto& getpc = list[setpc_index - 3]; + const auto& arith = list[setpc_index - 2]; + const auto& setpc = list[setpc_index]; + + if (getpc.opcode != Opcode::S_GETPC_B64 || + !(arith.opcode == Opcode::S_ADD_U32 || arith.opcode == Opcode::S_SUB_U32) || + setpc.opcode != Opcode::S_SETPC_B64) + return std::nullopt; + + if (getpc.dst[0].code != setpc.src[0].code || arith.dst[0].code != setpc.src[0].code) + return std::nullopt; + + if (arith.src_count < 2 || arith.src[1].field != OperandField::LiteralConst) + return std::nullopt; + + const u32 imm = arith.src[1].code; + + const s32 signed_offset = + (arith.opcode == Opcode::S_ADD_U32) ? static_cast(imm) : -static_cast(imm); + + const u32 base_pc = pc_map[setpc_index - 3] + getpc.length; + + const u32 result_pc = static_cast(static_cast(base_pc) + signed_offset); + LOG_DEBUG(Render_Recompiler, "SetPC target: {} + {} = {}", base_pc, signed_offset, result_pc); + return result_pc & ~0x3u; +} + static constexpr size_t LabelReserveSize = 32; CFG::CFG(Common::ObjectPool& block_pool_, std::span inst_list_) @@ -89,9 +123,20 @@ void CFG::EmitLabels() { index_to_pc[i] = pc; const GcnInst inst = inst_list[i]; if (inst.IsUnconditionalBranch()) { - const u32 target = inst.BranchTarget(pc); + u32 target = inst.BranchTarget(pc); + if (inst.opcode == Opcode::S_SETPC_B64) { + if (auto t = ResolveSetPcTarget(inst_list, i, index_to_pc)) { + target = *t; + } else { + ASSERT_MSG( + false, + "S_SETPC_B64 without a resolvable offset at PC {:#x} (Index {}): Involved " + "instructions not recognized or invalid pattern", + pc, i); + } + } AddLabel(target); - // Emit this label so that the block ends with s_branch instruction + // Emit this label so that the block ends with the branching instruction AddLabel(pc + inst.length); } else if (inst.IsConditionalBranch()) { const u32 true_label = inst.BranchTarget(pc); @@ -102,6 +147,7 @@ void CFG::EmitLabels() { const u32 next_label = pc + inst.length; AddLabel(next_label); } + pc += inst.length; } index_to_pc[inst_list.size()] = pc; @@ -280,7 +326,18 @@ void CFG::LinkBlocks() { // Find the branch targets from the instruction and link the blocks. // Note: Block end address is one instruction after end_inst. const u32 branch_pc = block.end - end_inst.length; - const u32 target_pc = end_inst.BranchTarget(branch_pc); + u32 target_pc = 0; + if (end_inst.opcode == Opcode::S_SETPC_B64) { + auto tgt = ResolveSetPcTarget(inst_list, block.end_index, index_to_pc); + ASSERT_MSG(tgt, + "S_SETPC_B64 without a resolvable offset at PC {:#x} (Index {}): Involved " + "instructions not recognized or invalid pattern", + branch_pc, block.end_index); + target_pc = *tgt; + } else { + target_pc = end_inst.BranchTarget(branch_pc); + } + if (end_inst.IsUnconditionalBranch()) { auto* target_block = get_block(target_pc); ++target_block->num_predecessors; diff --git a/src/shader_recompiler/frontend/instruction.cpp b/src/shader_recompiler/frontend/instruction.cpp index a0c132053..246c2b85a 100644 --- a/src/shader_recompiler/frontend/instruction.cpp +++ b/src/shader_recompiler/frontend/instruction.cpp @@ -18,7 +18,7 @@ bool GcnInst::IsTerminateInstruction() const { } bool GcnInst::IsUnconditionalBranch() const { - return opcode == Opcode::S_BRANCH; + return opcode == Opcode::S_BRANCH || opcode == Opcode::S_SETPC_B64; } bool GcnInst::IsFork() const { diff --git a/src/shader_recompiler/frontend/translate/scalar_flow.cpp b/src/shader_recompiler/frontend/translate/scalar_flow.cpp index 0e02b77a2..cd1cf51f0 100644 --- a/src/shader_recompiler/frontend/translate/scalar_flow.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_flow.cpp @@ -18,6 +18,7 @@ void Translator::EmitFlowControl(u32 pc, const GcnInst& inst) { return; case Opcode::S_GETPC_B64: return S_GETPC_B64(pc, inst); + case Opcode::S_SETPC_B64: case Opcode::S_WAITCNT: case Opcode::S_NOP: case Opcode::S_ENDPGM: From cda421434bb8d040f47cfc73bafa731afba39aee Mon Sep 17 00:00:00 2001 From: Dmugetsu <168934208+diegolix29@users.noreply.github.com> Date: Tue, 22 Apr 2025 13:49:29 -0600 Subject: [PATCH 162/194] Adding missmatch info to Texture cache assert (#2828) --- src/video_core/texture_cache/texture_cache.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index d41ee57cc..545152ec8 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -326,9 +326,13 @@ ImageId TextureCache::FindImage(BaseDesc& desc, FindFlags flags) { info.pixel_format != cache_image.info.pixel_format) { continue; } - ASSERT((cache_image.info.type == info.type || info.size == Extent3D{1, 1, 1} || - True(flags & FindFlags::RelaxFmt))); - image_id = cache_id; + if (!(cache_image.info.type == info.type || info.size == Extent3D{1, 1, 1} || + static_cast(flags & FindFlags::RelaxFmt))) { + ASSERT_MSG(false, "Image cache type mismatch: cache={}, info={}", + int(cache_image.info.type), int(info.type)); + image_id = cache_id; + break; + } } if (True(flags & FindFlags::NoCreate) && !image_id) { From 5b0205bc59f5057203a860f991fef3cb54dc8ed0 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:00:08 -0700 Subject: [PATCH 163/194] fix: Texture cache image type assert. --- src/video_core/texture_cache/texture_cache.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 545152ec8..7023dda55 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -326,13 +326,11 @@ ImageId TextureCache::FindImage(BaseDesc& desc, FindFlags flags) { info.pixel_format != cache_image.info.pixel_format) { continue; } - if (!(cache_image.info.type == info.type || info.size == Extent3D{1, 1, 1} || - static_cast(flags & FindFlags::RelaxFmt))) { - ASSERT_MSG(false, "Image cache type mismatch: cache={}, info={}", - int(cache_image.info.type), int(info.type)); - image_id = cache_id; - break; - } + ASSERT_MSG((cache_image.info.type == info.type || info.size == Extent3D{1, 1, 1} || + True(flags & FindFlags::RelaxFmt)), + "Image cache type mismatch: cache={}, info={}", + vk::to_string(cache_image.info.type), vk::to_string(info.type)); + image_id = cache_id; } if (True(flags & FindFlags::NoCreate) && !image_id) { From 0297aee3f4871704da9eef42f81ecf5ead8e7a3b Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:03:43 -0700 Subject: [PATCH 164/194] texture_cache: Relax mismatched image type from assert to cache miss. (#2830) --- src/video_core/texture_cache/texture_cache.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 7023dda55..047bb3dfe 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -319,17 +319,14 @@ ImageId TextureCache::FindImage(BaseDesc& desc, FindFlags flags) { continue; } if (False(flags & FindFlags::RelaxFmt) && - !IsVulkanFormatCompatible(info.pixel_format, cache_image.info.pixel_format)) { + (!IsVulkanFormatCompatible(info.pixel_format, cache_image.info.pixel_format) || + (cache_image.info.type != info.type && info.size != Extent3D{1, 1, 1}))) { continue; } if (True(flags & FindFlags::ExactFmt) && info.pixel_format != cache_image.info.pixel_format) { continue; } - ASSERT_MSG((cache_image.info.type == info.type || info.size == Extent3D{1, 1, 1} || - True(flags & FindFlags::RelaxFmt)), - "Image cache type mismatch: cache={}, info={}", - vk::to_string(cache_image.info.type), vk::to_string(info.type)); image_id = cache_id; } From 96bee58d0f048c44bbbf76a2fa1434c469df753e Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 22 Apr 2025 21:41:20 -0700 Subject: [PATCH 165/194] ci: Fix macOS SDL build. (#2831) --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86ca81c38..9393380e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -206,11 +206,11 @@ jobs: mkdir upload mv ${{github.workspace}}/build/shadps4 upload cp ${{github.workspace}}/build/externals/MoltenVK/libMoltenVK.dylib upload - tar cf shadps4-macos-sdl.tar.gz -C upload . + install_name_tool -add_rpath "@executable_path" upload/shadps4 - uses: actions/upload-artifact@v4 with: name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} - path: shadps4-macos-sdl.tar.gz + path: upload/ macos-qt: runs-on: macos-15 From b1ebb2fec56c25dc304d74618e2aca04671eddb1 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:05:32 -0700 Subject: [PATCH 166/194] fix: Only change macOS working directory for app bundle. --- src/common/path_util.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 702d0fabc..2ff1ddaf7 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -60,7 +60,7 @@ static CFURLRef UntranslocateBundlePath(const CFURLRef bundle_path) { return nullptr; } -static std::filesystem::path GetBundleParentDirectory() { +static std::optional GetBundleParentDirectory() { if (CFBundleRef bundle_ref = CFBundleGetMainBundle()) { if (CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref)) { SCOPE_EXIT { @@ -83,14 +83,16 @@ static std::filesystem::path GetBundleParentDirectory() { } } } - return std::filesystem::current_path(); + return std::nullopt; } #endif static auto UserPaths = [] { #ifdef __APPLE__ // Set the current path to the directory containing the app bundle. - std::filesystem::current_path(GetBundleParentDirectory()); + if (const auto bundle_dir = GetBundleParentDirectory()) { + std::filesystem::current_path(*bundle_dir); + } #endif // Try the portable user directory first. From ee7fe305c9cb69ad3c33b1d2bb0d5b2fa918e982 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:18:39 -0700 Subject: [PATCH 167/194] fix: Make sure right MoltenVK is loaded for macOS SDL. --- src/video_core/renderer_vulkan/vk_platform.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index e656369b2..6e9d825a3 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -223,6 +223,7 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e LOG_INFO(Render_Vulkan, "Creating vulkan instance"); #ifdef __APPLE__ +#ifdef ENABLE_QT_GUI // If the Vulkan loader exists in /usr/local/lib, give it priority. The Vulkan SDK // installs it here by default but it is not in the default library search path. // The loader has a clause to check for it, but at a lower priority than the bundled @@ -231,6 +232,10 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e static vk::detail::DynamicLoader dl = std::filesystem::exists(usr_local_path) ? vk::detail::DynamicLoader(usr_local_path) : vk::detail::DynamicLoader(); +#else + // TODO: Support layer loading in SDL build. For now just make sure we load the right MoltenVK. + static vk::detail::DynamicLoader dl("libMoltenVK.dylib"); +#endif #else static vk::detail::DynamicLoader dl; #endif From 53ca64f6ffba393cef12b6d794ba32570a610bf0 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 23 Apr 2025 01:10:03 -0700 Subject: [PATCH 168/194] renderer_vulkan: Support loading Vulkan layers on macOS SDL build. (#2832) --- .github/workflows/build.yml | 4 +- .gitmodules | 2 +- CMakeLists.txt | 61 +++++++++++-------- externals/MoltenVK/MoltenVK | 2 +- externals/MoltenVK/MoltenVK_icd.json | 8 --- externals/MoltenVK/SPIRV-Cross | 2 +- src/common/path_util.cpp | 2 +- .../renderer_vulkan/vk_platform.cpp | 22 +++++-- 8 files changed, 58 insertions(+), 45 deletions(-) delete mode 100644 externals/MoltenVK/MoltenVK_icd.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9393380e2..55549ab4c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -205,8 +205,8 @@ jobs: run: | mkdir upload mv ${{github.workspace}}/build/shadps4 upload - cp ${{github.workspace}}/build/externals/MoltenVK/libMoltenVK.dylib upload - install_name_tool -add_rpath "@executable_path" upload/shadps4 + mv ${{github.workspace}}/build/MoltenVK_icd.json upload + mv ${{github.workspace}}/build/libMoltenVK.dylib upload - uses: actions/upload-artifact@v4 with: name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} diff --git a/.gitmodules b/.gitmodules index 9daefe305..065a4570f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -97,7 +97,7 @@ shallow = true [submodule "externals/MoltenVK/SPIRV-Cross"] path = externals/MoltenVK/SPIRV-Cross - url = https://github.com/billhollings/SPIRV-Cross + url = https://github.com/KhronosGroup/SPIRV-Cross shallow = true [submodule "externals/MoltenVK/MoltenVK"] path = externals/MoltenVK/MoltenVK diff --git a/CMakeLists.txt b/CMakeLists.txt index beea8a1e0..c48d00046 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1083,34 +1083,45 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND ENABLE_USERFAULTFD) endif() if (APPLE) - if (ENABLE_QT_GUI) - # Include MoltenVK in the app bundle, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers. - set(MVK_ICD ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK_icd.json) - target_sources(shadps4 PRIVATE ${MVK_ICD}) - set_source_files_properties(${MVK_ICD} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/vulkan/icd.d) + # Include MoltenVK, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers. + set(MVK_DYLIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/externals/MoltenVK/libMoltenVK.dylib) - set(MVK_DYLIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/externals/MoltenVK/libMoltenVK.dylib) - set(MVK_DYLIB_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/Frameworks/libMoltenVK.dylib) - add_custom_command( - OUTPUT ${MVK_DYLIB_DST} - DEPENDS ${MVK_DYLIB_SRC} - COMMAND cmake -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST}) - add_custom_target(CopyMoltenVK DEPENDS ${MVK_DYLIB_DST}) - add_dependencies(CopyMoltenVK MoltenVK) - add_dependencies(shadps4 CopyMoltenVK) - set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../Frameworks") - else() - # For non-bundled SDL build, just do a normal library link. - target_link_libraries(shadps4 PRIVATE MoltenVK) - endif() + if (ENABLE_QT_GUI) + set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../Frameworks") + set(MVK_ICD_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json) + set(MVK_DYLIB_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/Frameworks/libMoltenVK.dylib) + set(MVK_DYLIB_ICD_PATH "../../../Frameworks/libMoltenVK.dylib") + else() + set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path") + set(MVK_ICD_DST ${CMAKE_CURRENT_BINARY_DIR}/MoltenVK_icd.json) + set(MVK_DYLIB_DST ${CMAKE_CURRENT_BINARY_DIR}/libMoltenVK.dylib) + set(MVK_DYLIB_ICD_PATH "./libMoltenVK.dylib") + endif() - if (ARCHITECTURE STREQUAL "x86_64") - # Reserve system-managed memory space. - target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000) - endif() + cmake_path(GET MVK_ICD_DST PARENT_PATH MVK_ICD_DST_PARENT) + cmake_path(GET MVK_DYLIB_DST PARENT_PATH MVK_DYLIB_DST_PARENT) - # Replacement for std::chrono::time_zone - target_link_libraries(shadps4 PRIVATE date::date-tz) + set(MVK_ICD "\\\{ \\\"file_format_version\\\": \\\"1.0.0\\\", \\\"ICD\\\": \\\{ \\\"library_path\\\": \\\"${MVK_DYLIB_ICD_PATH}\\\", \\\"api_version\\\": \\\"1.2.0\\\", \\\"is_portability_driver\\\": true \\\} \\\}") + add_custom_command( + OUTPUT ${MVK_ICD_DST} + COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_ICD_DST_PARENT} && ${CMAKE_COMMAND} -E echo ${MVK_ICD} > ${MVK_ICD_DST}) + + add_custom_command( + OUTPUT ${MVK_DYLIB_DST} + DEPENDS ${MVK_DYLIB_SRC} + COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DYLIB_DST_PARENT} && ${CMAKE_COMMAND} -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST}) + + add_custom_target(CopyMoltenVK DEPENDS ${MVK_ICD_DST} ${MVK_DYLIB_DST}) + add_dependencies(CopyMoltenVK MoltenVK) + add_dependencies(shadps4 CopyMoltenVK) + + if (ARCHITECTURE STREQUAL "x86_64") + # Reserve system-managed memory space. + target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000) + endif() + + # Replacement for std::chrono::time_zone + target_link_libraries(shadps4 PRIVATE date::date-tz) endif() if (NOT ENABLE_QT_GUI) diff --git a/externals/MoltenVK/MoltenVK b/externals/MoltenVK/MoltenVK index 067fc6c85..4cf8f9468 160000 --- a/externals/MoltenVK/MoltenVK +++ b/externals/MoltenVK/MoltenVK @@ -1 +1 @@ -Subproject commit 067fc6c85b02f37dfda58eeda49d8458e093ed60 +Subproject commit 4cf8f94684c53e581eb9cc694dd3305d1f7d9959 diff --git a/externals/MoltenVK/MoltenVK_icd.json b/externals/MoltenVK/MoltenVK_icd.json deleted file mode 100644 index 2c3319263..000000000 --- a/externals/MoltenVK/MoltenVK_icd.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "file_format_version": "1.0.0", - "ICD": { - "library_path": "../../../Frameworks/libMoltenVK.dylib", - "api_version": "1.2.0", - "is_portability_driver": true - } -} diff --git a/externals/MoltenVK/SPIRV-Cross b/externals/MoltenVK/SPIRV-Cross index 185833a61..2275d0efc 160000 --- a/externals/MoltenVK/SPIRV-Cross +++ b/externals/MoltenVK/SPIRV-Cross @@ -1 +1 @@ -Subproject commit 185833a61cbe29ce3bfb5a499ffb3dfeaee3bbe7 +Subproject commit 2275d0efc4f2fa46851035d9d3c67c105bc8b99e diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 2ff1ddaf7..1a6ff9ec8 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -88,7 +88,7 @@ static std::optional GetBundleParentDirectory() { #endif static auto UserPaths = [] { -#ifdef __APPLE__ +#if defined(__APPLE__) && defined(ENABLE_QT_GUI) // Set the current path to the directory containing the app bundle. if (const auto bundle_dir = GetBundleParentDirectory()) { std::filesystem::current_path(*bundle_dir); diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index 6e9d825a3..c49e62f4e 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -22,6 +22,10 @@ #include "sdl_window.h" #include "video_core/renderer_vulkan/vk_platform.h" +#ifdef __APPLE__ +#include +#endif + namespace Vulkan { static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; @@ -223,19 +227,25 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e LOG_INFO(Render_Vulkan, "Creating vulkan instance"); #ifdef __APPLE__ -#ifdef ENABLE_QT_GUI +#ifndef ENABLE_QT_GUI + // Initialize the environment with the path to the MoltenVK ICD, so that the loader will + // find it. + static const auto icd_path = [] { + char path[PATH_MAX]; + u32 length = PATH_MAX; + _NSGetExecutablePath(path, &length); + return std::filesystem::path(path).parent_path() / "MoltenVK_icd.json"; + }(); + setenv("VK_DRIVER_FILES", icd_path.c_str(), true); +#endif // If the Vulkan loader exists in /usr/local/lib, give it priority. The Vulkan SDK - // installs it here by default but it is not in the default library search path. + // installs it here by default, but it is not in the default library search path. // The loader has a clause to check for it, but at a lower priority than the bundled // libMoltenVK.dylib, so we need to handle it ourselves to give it priority. static const std::string usr_local_path = "/usr/local/lib/libvulkan.dylib"; static vk::detail::DynamicLoader dl = std::filesystem::exists(usr_local_path) ? vk::detail::DynamicLoader(usr_local_path) : vk::detail::DynamicLoader(); -#else - // TODO: Support layer loading in SDL build. For now just make sure we load the right MoltenVK. - static vk::detail::DynamicLoader dl("libMoltenVK.dylib"); -#endif #else static vk::detail::DynamicLoader dl; #endif From 354a2e6946319e8c6855146829659efa107ab32f Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 23 Apr 2025 13:05:36 +0300 Subject: [PATCH 169/194] tagged v0.8.0 release --- CMakeLists.txt | 8 ++++---- dist/net.shadps4.shadPS4.metainfo.xml | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c48d00046..e91939949 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,13 +202,13 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") -set(EMULATOR_VERSION_MINOR "7") -set(EMULATOR_VERSION_PATCH "1") +set(EMULATOR_VERSION_MINOR "8") +set(EMULATOR_VERSION_PATCH "0") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") -set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH} WIP") -set(APP_IS_RELEASE false) +set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") +set(APP_IS_RELEASE true) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml index 99f9e070d..9f7b4f9c5 100644 --- a/dist/net.shadps4.shadPS4.metainfo.xml +++ b/dist/net.shadps4.shadPS4.metainfo.xml @@ -37,6 +37,9 @@ Game + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.8.0 + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.7.0 From 5db162cbcd7ba73a93576566dabb84de3ac48293 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 23 Apr 2025 13:24:57 +0300 Subject: [PATCH 170/194] started 0.8.1 WIP --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e91939949..967229d6a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,12 +203,12 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") set(EMULATOR_VERSION_MINOR "8") -set(EMULATOR_VERSION_PATCH "0") +set(EMULATOR_VERSION_PATCH "1") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") -set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") -set(APP_IS_RELEASE true) +set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH} WIP") +set(APP_IS_RELEASE false) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") From aeee7706eeb6afd82ea00bc152c2665c5113c84f Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 23 Apr 2025 03:28:31 -0700 Subject: [PATCH 171/194] renderer_vulkan: Restore Vulkan version to 1.3 (#2827) Co-authored-by: georgemoralis --- .../backend/spirv/emit_spirv.cpp | 3 +- .../renderer_vulkan/host_passes/pp_pass.cpp | 2 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 24 ++++---- .../renderer_vulkan/vk_instance.cpp | 55 ++++++------------- .../renderer_vulkan/vk_pipeline_cache.cpp | 4 +- src/video_core/renderer_vulkan/vk_platform.h | 2 +- .../renderer_vulkan/vk_scheduler.cpp | 40 +++++++------- 7 files changed, 55 insertions(+), 75 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 036df24d8..936f82cd6 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -335,8 +335,7 @@ void DefineEntryPoint(const Info& info, EmitContext& ctx, Id main) { ctx.AddExecutionMode(main, spv::ExecutionMode::OriginUpperLeft); } if (info.has_discard) { - ctx.AddExtension("SPV_EXT_demote_to_helper_invocation"); - ctx.AddCapability(spv::Capability::DemoteToHelperInvocationEXT); + ctx.AddCapability(spv::Capability::DemoteToHelperInvocation); } if (info.stores.GetAny(IR::Attribute::Depth)) { ctx.AddExecutionMode(main, spv::ExecutionMode::DepthReplacing); diff --git a/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp b/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp index c854e124f..0c40ffd7a 100644 --- a/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp +++ b/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp @@ -78,7 +78,7 @@ void PostProcessingPass::Create(vk::Device device) { const std::array pp_color_formats{ vk::Format::eB8G8R8A8Unorm, // swapchain.GetSurfaceFormat().format, }; - const vk::PipelineRenderingCreateInfoKHR pipeline_rendering_ci{ + const vk::PipelineRenderingCreateInfo pipeline_rendering_ci{ .colorAttachmentCount = pp_color_formats.size(), .pColorAttachmentFormats = pp_color_formats.data(), }; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 354e22331..7c020a012 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -122,21 +122,21 @@ GraphicsPipeline::GraphicsPipeline( }; boost::container::static_vector dynamic_states = { - vk::DynamicState::eViewportWithCountEXT, vk::DynamicState::eScissorWithCountEXT, - vk::DynamicState::eBlendConstants, vk::DynamicState::eDepthTestEnableEXT, - vk::DynamicState::eDepthWriteEnableEXT, vk::DynamicState::eDepthCompareOpEXT, - vk::DynamicState::eDepthBiasEnableEXT, vk::DynamicState::eDepthBias, - vk::DynamicState::eStencilTestEnableEXT, vk::DynamicState::eStencilReference, - vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask, - vk::DynamicState::eStencilOpEXT, vk::DynamicState::eCullModeEXT, - vk::DynamicState::eFrontFaceEXT, + vk::DynamicState::eViewportWithCount, vk::DynamicState::eScissorWithCount, + vk::DynamicState::eBlendConstants, vk::DynamicState::eDepthTestEnable, + vk::DynamicState::eDepthWriteEnable, vk::DynamicState::eDepthCompareOp, + vk::DynamicState::eDepthBiasEnable, vk::DynamicState::eDepthBias, + vk::DynamicState::eStencilTestEnable, vk::DynamicState::eStencilReference, + vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask, + vk::DynamicState::eStencilOp, vk::DynamicState::eCullMode, + vk::DynamicState::eFrontFace, }; if (instance.IsPrimitiveRestartDisableSupported()) { - dynamic_states.push_back(vk::DynamicState::ePrimitiveRestartEnableEXT); + dynamic_states.push_back(vk::DynamicState::ePrimitiveRestartEnable); } if (instance.IsDepthBoundsSupported()) { - dynamic_states.push_back(vk::DynamicState::eDepthBoundsTestEnableEXT); + dynamic_states.push_back(vk::DynamicState::eDepthBoundsTestEnable); dynamic_states.push_back(vk::DynamicState::eDepthBounds); } if (instance.IsDynamicColorWriteMaskSupported()) { @@ -145,7 +145,7 @@ GraphicsPipeline::GraphicsPipeline( if (instance.IsVertexInputDynamicState()) { dynamic_states.push_back(vk::DynamicState::eVertexInputEXT); } else if (!vertex_bindings.empty()) { - dynamic_states.push_back(vk::DynamicState::eVertexInputBindingStrideEXT); + dynamic_states.push_back(vk::DynamicState::eVertexInputBindingStride); } const vk::PipelineDynamicStateCreateInfo dynamic_info = { @@ -212,7 +212,7 @@ GraphicsPipeline::GraphicsPipeline( }); } - const vk::PipelineRenderingCreateInfoKHR pipeline_rendering_ci = { + const vk::PipelineRenderingCreateInfo pipeline_rendering_ci = { .colorAttachmentCount = key.num_color_attachments, .pColorAttachmentFormats = key.color_formats.data(), .depthAttachmentFormat = key.depth_format, diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 0df020116..089c0f00d 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -203,12 +203,14 @@ std::string Instance::GetDriverVersionName() { } bool Instance::CreateDevice() { - const vk::StructureChain feature_chain = physical_device.getFeatures2< - vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan11Features, - vk::PhysicalDeviceVulkan12Features, vk::PhysicalDeviceRobustness2FeaturesEXT, - vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT, - vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT, - vk::PhysicalDevicePortabilitySubsetFeaturesKHR>(); + const vk::StructureChain feature_chain = + physical_device + .getFeatures2(); features = feature_chain.get().features; const vk::StructureChain properties_chain = physical_device.getProperties2< @@ -240,18 +242,6 @@ bool Instance::CreateDevice() { return false; }; - // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 - // with extensions. - ASSERT(add_extension(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME)); - ASSERT(add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)); - ASSERT(add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME)); - ASSERT(add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME)); - ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)); - ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME)); - ASSERT(add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME) || - driver_id == vk::DriverId::eIntelProprietaryWindows); - ASSERT(add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME)); - // Required ASSERT(add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME)); ASSERT(add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME)); @@ -324,6 +314,7 @@ bool Instance::CreateDevice() { feature_chain.get(); const auto vk11_features = feature_chain.get(); const auto vk12_features = feature_chain.get(); + const auto vk13_features = feature_chain.get(); vk::StructureChain device_chain = { vk::DeviceCreateInfo{ .queueCreateInfoCount = 1u, @@ -372,26 +363,14 @@ bool Instance::CreateDevice() { .hostQueryReset = vk12_features.hostQueryReset, .timelineSemaphore = vk12_features.timelineSemaphore, }, - // Vulkan 1.3 promoted extensions - vk::PhysicalDeviceDynamicRenderingFeaturesKHR{ - .dynamicRendering = true, + vk::PhysicalDeviceVulkan13Features{ + .robustImageAccess = vk13_features.robustImageAccess, + .shaderDemoteToHelperInvocation = vk13_features.shaderDemoteToHelperInvocation, + .synchronization2 = vk13_features.synchronization2, + .dynamicRendering = vk13_features.dynamicRendering, + .maintenance4 = vk13_features.maintenance4, }, - vk::PhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT{ - .shaderDemoteToHelperInvocation = true, - }, - vk::PhysicalDeviceSynchronization2Features{ - .synchronization2 = true, - }, - vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT{ - .extendedDynamicState = true, - }, - vk::PhysicalDeviceExtendedDynamicState2FeaturesEXT{ - .extendedDynamicState2 = true, - }, - vk::PhysicalDeviceMaintenance4FeaturesKHR{ - .maintenance4 = true, - }, - // Other extensions + // Extensions vk::PhysicalDeviceCustomBorderColorFeaturesEXT{ .customBorderColors = true, .customBorderColorWithoutFormat = true, @@ -547,7 +526,7 @@ void Instance::CollectToolingInfo() { // Currently causes issues with Reshade on AMD proprietary, disabled until fix released. return; } - const auto [tools_result, tools] = physical_device.getToolPropertiesEXT(); + const auto [tools_result, tools] = physical_device.getToolProperties(); if (tools_result != vk::Result::eSuccess) { LOG_ERROR(Render_Vulkan, "Could not get Vulkan tool properties: {}", vk::to_string(tools_result)); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index bad2a549c..efb1966ba 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -26,6 +26,8 @@ using Shader::LogicalStage; using Shader::Stage; using Shader::VsOutput; +constexpr static auto SpirvVersion1_6 = 0x00010600U; + constexpr static std::array DescriptorHeapSizes = { vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 8192}, vk::DescriptorPoolSize{vk::DescriptorType::eStorageBuffer, 1024}, @@ -192,7 +194,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} { const auto& vk12_props = instance.GetVk12Properties(); profile = Shader::Profile{ - .supported_spirv = instance.ApiVersion() >= VK_API_VERSION_1_3 ? 0x00010600U : 0x00010500U, + .supported_spirv = SpirvVersion1_6, .subgroup_size = instance.SubgroupSize(), .support_fp32_denorm_preserve = bool(vk12_props.shaderDenormPreserveFloat32), .support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32), diff --git a/src/video_core/renderer_vulkan/vk_platform.h b/src/video_core/renderer_vulkan/vk_platform.h index 6a6ebeb15..b8f5f9f11 100644 --- a/src/video_core/renderer_vulkan/vk_platform.h +++ b/src/video_core/renderer_vulkan/vk_platform.h @@ -18,7 +18,7 @@ class WindowSDL; namespace Vulkan { -constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_2; +constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_3; vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::WindowSDL& emu_window); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index a48d93dee..8d4188a22 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -170,29 +170,29 @@ void Scheduler::SubmitExecution(SubmitInfo& info) { void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmdbuf) { if (dirty_state.viewports) { dirty_state.viewports = false; - cmdbuf.setViewportWithCountEXT(viewports); + cmdbuf.setViewportWithCount(viewports); } if (dirty_state.scissors) { dirty_state.scissors = false; - cmdbuf.setScissorWithCountEXT(scissors); + cmdbuf.setScissorWithCount(scissors); } if (dirty_state.depth_test_enabled) { dirty_state.depth_test_enabled = false; - cmdbuf.setDepthTestEnableEXT(depth_test_enabled); + cmdbuf.setDepthTestEnable(depth_test_enabled); } if (dirty_state.depth_write_enabled) { dirty_state.depth_write_enabled = false; // Note that this must be set in a command buffer even if depth test is disabled. - cmdbuf.setDepthWriteEnableEXT(depth_write_enabled); + cmdbuf.setDepthWriteEnable(depth_write_enabled); } if (depth_test_enabled && dirty_state.depth_compare_op) { dirty_state.depth_compare_op = false; - cmdbuf.setDepthCompareOpEXT(depth_compare_op); + cmdbuf.setDepthCompareOp(depth_compare_op); } if (dirty_state.depth_bounds_test_enabled) { dirty_state.depth_bounds_test_enabled = false; if (instance.IsDepthBoundsSupported()) { - cmdbuf.setDepthBoundsTestEnableEXT(depth_bounds_test_enabled); + cmdbuf.setDepthBoundsTestEnable(depth_bounds_test_enabled); } } if (depth_bounds_test_enabled && dirty_state.depth_bounds) { @@ -203,7 +203,7 @@ void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmd } if (dirty_state.depth_bias_enabled) { dirty_state.depth_bias_enabled = false; - cmdbuf.setDepthBiasEnableEXT(depth_bias_enabled); + cmdbuf.setDepthBiasEnable(depth_bias_enabled); } if (depth_bias_enabled && dirty_state.depth_bias) { dirty_state.depth_bias = false; @@ -211,28 +211,28 @@ void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmd } if (dirty_state.stencil_test_enabled) { dirty_state.stencil_test_enabled = false; - cmdbuf.setStencilTestEnableEXT(stencil_test_enabled); + cmdbuf.setStencilTestEnable(stencil_test_enabled); } if (stencil_test_enabled) { if (dirty_state.stencil_front_ops && dirty_state.stencil_back_ops && stencil_front_ops == stencil_back_ops) { dirty_state.stencil_front_ops = false; dirty_state.stencil_back_ops = false; - cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFrontAndBack, - stencil_front_ops.fail_op, stencil_front_ops.pass_op, - stencil_front_ops.depth_fail_op, stencil_front_ops.compare_op); + cmdbuf.setStencilOp(vk::StencilFaceFlagBits::eFrontAndBack, stencil_front_ops.fail_op, + stencil_front_ops.pass_op, stencil_front_ops.depth_fail_op, + stencil_front_ops.compare_op); } else { if (dirty_state.stencil_front_ops) { dirty_state.stencil_front_ops = false; - cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFront, stencil_front_ops.fail_op, - stencil_front_ops.pass_op, stencil_front_ops.depth_fail_op, - stencil_front_ops.compare_op); + cmdbuf.setStencilOp(vk::StencilFaceFlagBits::eFront, stencil_front_ops.fail_op, + stencil_front_ops.pass_op, stencil_front_ops.depth_fail_op, + stencil_front_ops.compare_op); } if (dirty_state.stencil_back_ops) { dirty_state.stencil_back_ops = false; - cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eBack, stencil_back_ops.fail_op, - stencil_back_ops.pass_op, stencil_back_ops.depth_fail_op, - stencil_back_ops.compare_op); + cmdbuf.setStencilOp(vk::StencilFaceFlagBits::eBack, stencil_back_ops.fail_op, + stencil_back_ops.pass_op, stencil_back_ops.depth_fail_op, + stencil_back_ops.compare_op); } } if (dirty_state.stencil_front_reference && dirty_state.stencil_back_reference && @@ -291,16 +291,16 @@ void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmd if (dirty_state.primitive_restart_enable) { dirty_state.primitive_restart_enable = false; if (instance.IsPrimitiveRestartDisableSupported()) { - cmdbuf.setPrimitiveRestartEnableEXT(primitive_restart_enable); + cmdbuf.setPrimitiveRestartEnable(primitive_restart_enable); } } if (dirty_state.cull_mode) { dirty_state.cull_mode = false; - cmdbuf.setCullModeEXT(cull_mode); + cmdbuf.setCullMode(cull_mode); } if (dirty_state.front_face) { dirty_state.front_face = false; - cmdbuf.setFrontFaceEXT(front_face); + cmdbuf.setFrontFace(front_face); } if (dirty_state.blend_constants) { dirty_state.blend_constants = false; From 3f4249084cfc3b94db7d8241647ff496f49d4b90 Mon Sep 17 00:00:00 2001 From: tlarok <116431383+tlarok@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:41:14 +0200 Subject: [PATCH 172/194] multikey for kbm_gui (#2778) * Update kbm_gui.cpp * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * lunix test * linux test * Update kbm_gui.h * Update kbm_gui.cpp * Update kbm_gui.cpp * Update kbm_gui.cpp * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * kbm_gui.cpp's names fix just cleaning my code * name fix * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * Update src/qt_gui/kbm_gui.cpp Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> * clean up from main * bruh, welp here we go again --------- Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> --- src/qt_gui/kbm_gui.cpp | 273 +++++++++++++++++++++++------------------ src/qt_gui/kbm_gui.h | 17 +++ 2 files changed, 169 insertions(+), 121 deletions(-) diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 2e1f6ddce..15e9008ab 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -608,21 +608,28 @@ void KBMSettings::CheckMapping(QPushButton*& button) { MappingTimer -= 1; button->setText(tr("Press a key") + " [" + QString::number(MappingTimer) + "]"); + if (pressedKeys.size() > 0) { + QStringList keyStrings; + + for (const QString& buttonAction : pressedKeys) { + keyStrings << buttonAction; + } + + QString combo = keyStrings.join(","); + SetMapping(combo); + MappingCompleted = true; + EnableMapping = false; + + MappingButton->setText(combo); + pressedKeys.clear(); + timer->stop(); + } if (MappingCompleted) { EnableMapping = false; EnableMappingButtons(); timer->stop(); - if (mapping == "lshift" || mapping == "lalt" || mapping == "lctrl" || mapping == "lmeta" || - mapping == "lwin") { - modifier = ""; - } - - if (modifier != "") { - button->setText(modifier + ", " + mapping); - } else { - button->setText(mapping); - } + button->setText(mapping); } if (MappingTimer <= 0) { @@ -647,322 +654,346 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { } if (EnableMapping) { - if (Qt::ShiftModifier & QApplication::keyboardModifiers()) { - modifier = "lshift"; - } else if (Qt::AltModifier & QApplication::keyboardModifiers()) { - modifier = "lalt"; - } else if (Qt::ControlModifier & QApplication::keyboardModifiers()) { - modifier = "lctrl"; - } else if (Qt::MetaModifier & QApplication::keyboardModifiers()) { -#ifdef _WIN32 - modifier = "lwin"; -#else - modifier = "lmeta"; -#endif - } - if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->isAutoRepeat()) + return true; + + if (pressedKeys.size() >= 3) { + return true; + } + switch (keyEvent->key()) { case Qt::Key_Space: - SetMapping("space"); + pressedKeys.insert("space"); break; case Qt::Key_Comma: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kpcomma"); + pressedKeys.insert("kpcomma"); } else { - SetMapping("comma"); + pressedKeys.insert("comma"); } break; case Qt::Key_Period: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kpperiod"); + pressedKeys.insert("kpperiod"); } else { - SetMapping("period"); + pressedKeys.insert("period"); } break; case Qt::Key_Slash: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) - SetMapping("kpdivide"); + pressedKeys.insert("kpdivide"); break; case Qt::Key_Asterisk: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) - SetMapping("kpmultiply"); + pressedKeys.insert("kpmultiply"); break; case Qt::Key_Question: - SetMapping("question"); + pressedKeys.insert("question"); break; case Qt::Key_Semicolon: - SetMapping("semicolon"); + pressedKeys.insert("semicolon"); break; case Qt::Key_Minus: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kpminus"); + pressedKeys.insert("kpminus"); } else { - SetMapping("minus"); + pressedKeys.insert("minus"); } break; case Qt::Key_Plus: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kpplus"); + pressedKeys.insert("kpplus"); } else { - SetMapping("plus"); + pressedKeys.insert("plus"); } break; case Qt::Key_ParenLeft: - SetMapping("lparenthesis"); + pressedKeys.insert("lparenthesis"); break; case Qt::Key_ParenRight: - SetMapping("rparenthesis"); + pressedKeys.insert("rparenthesis"); break; case Qt::Key_BracketLeft: - SetMapping("lbracket"); + pressedKeys.insert("lbracket"); break; case Qt::Key_BracketRight: - SetMapping("rbracket"); + pressedKeys.insert("rbracket"); break; case Qt::Key_BraceLeft: - SetMapping("lbrace"); + pressedKeys.insert("lbrace"); break; case Qt::Key_BraceRight: - SetMapping("rbrace"); + pressedKeys.insert("rbrace"); break; case Qt::Key_Backslash: - SetMapping("backslash"); + pressedKeys.insert("backslash"); break; case Qt::Key_Tab: - SetMapping("tab"); + pressedKeys.insert("tab"); break; case Qt::Key_Backspace: - SetMapping("backspace"); + pressedKeys.insert("backspace"); break; case Qt::Key_Return: - SetMapping("enter"); + pressedKeys.insert("enter"); break; case Qt::Key_Enter: - SetMapping("kpenter"); + pressedKeys.insert("kpenter"); + break; + case Qt::Key_Home: + pressedKeys.insert("home"); + break; + case Qt::Key_End: + pressedKeys.insert("end"); + break; + case Qt::Key_PageDown: + pressedKeys.insert("pgdown"); + break; + case Qt::Key_PageUp: + pressedKeys.insert("pgup"); + break; + case Qt::Key_CapsLock: + pressedKeys.insert("capslock"); break; case Qt::Key_Escape: - SetMapping("unmapped"); + pressedKeys.insert("unmapped"); break; case Qt::Key_Shift: - SetMapping("lshift"); + if (keyEvent->nativeScanCode() == rshift) { + pressedKeys.insert("rshift"); + } else { + pressedKeys.insert("lshift"); + } break; case Qt::Key_Alt: - SetMapping("lalt"); + if (keyEvent->nativeScanCode() == ralt) { + pressedKeys.insert("ralt"); + } else { + pressedKeys.insert("lalt"); + } break; case Qt::Key_Control: - SetMapping("lctrl"); + if (keyEvent->nativeScanCode() == rctrl) { + pressedKeys.insert("rctrl"); + } else { + pressedKeys.insert("lctrl"); + } break; case Qt::Key_Meta: activateWindow(); #ifdef _WIN32 - SetMapping("lwin"); + pressedKeys.insert("lwin"); #else - SetMapping("lmeta"); + pressedKeys.insert("lmeta"); #endif case Qt::Key_1: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp1"); + pressedKeys.insert("kp1"); } else { - SetMapping("1"); + pressedKeys.insert("1"); } break; case Qt::Key_2: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp2"); + pressedKeys.insert("kp2"); } else { - SetMapping("2"); + pressedKeys.insert("2"); } break; case Qt::Key_3: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp3"); + pressedKeys.insert("kp3"); } else { - SetMapping("3"); + pressedKeys.insert("3"); } break; case Qt::Key_4: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp4"); + pressedKeys.insert("kp4"); } else { - SetMapping("4"); + pressedKeys.insert("4"); } break; case Qt::Key_5: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp5"); + pressedKeys.insert("kp5"); } else { - SetMapping("5"); + pressedKeys.insert("5"); } break; case Qt::Key_6: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp6"); + pressedKeys.insert("kp6"); } else { - SetMapping("6"); + pressedKeys.insert("6"); } break; case Qt::Key_7: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp7"); + pressedKeys.insert("kp7"); } else { - SetMapping("7"); + pressedKeys.insert("7"); } break; case Qt::Key_8: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp8"); + pressedKeys.insert("kp8"); } else { - SetMapping("8"); + pressedKeys.insert("8"); } break; case Qt::Key_9: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp9"); + pressedKeys.insert("kp9"); } else { - SetMapping("9"); + pressedKeys.insert("9"); } break; case Qt::Key_0: if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - SetMapping("kp0"); + pressedKeys.insert("kp0"); } else { - SetMapping("0"); + pressedKeys.insert("0"); } break; case Qt::Key_Up: activateWindow(); - SetMapping("up"); + pressedKeys.insert("up"); break; case Qt::Key_Down: - SetMapping("down"); + pressedKeys.insert("down"); break; case Qt::Key_Left: - SetMapping("left"); + pressedKeys.insert("left"); break; case Qt::Key_Right: - SetMapping("right"); + pressedKeys.insert("right"); break; case Qt::Key_A: - SetMapping("a"); + pressedKeys.insert("a"); break; case Qt::Key_B: - SetMapping("b"); + pressedKeys.insert("b"); break; case Qt::Key_C: - SetMapping("c"); + pressedKeys.insert("c"); break; case Qt::Key_D: - SetMapping("d"); + pressedKeys.insert("d"); break; case Qt::Key_E: - SetMapping("e"); + pressedKeys.insert("e"); break; case Qt::Key_F: - SetMapping("f"); + pressedKeys.insert("f"); break; case Qt::Key_G: - SetMapping("g"); + pressedKeys.insert("g"); break; case Qt::Key_H: - SetMapping("h"); + pressedKeys.insert("h"); break; case Qt::Key_I: - SetMapping("i"); + pressedKeys.insert("i"); break; case Qt::Key_J: - SetMapping("j"); + pressedKeys.insert("j"); break; case Qt::Key_K: - SetMapping("k"); + pressedKeys.insert("k"); break; case Qt::Key_L: - SetMapping("l"); + pressedKeys.insert("l"); break; case Qt::Key_M: - SetMapping("m"); + pressedKeys.insert("m"); break; case Qt::Key_N: - SetMapping("n"); + pressedKeys.insert("n"); break; case Qt::Key_O: - SetMapping("o"); + pressedKeys.insert("o"); break; case Qt::Key_P: - SetMapping("p"); + pressedKeys.insert("p"); break; case Qt::Key_Q: - SetMapping("q"); + pressedKeys.insert("q"); break; case Qt::Key_R: - SetMapping("r"); + pressedKeys.insert("r"); break; case Qt::Key_S: - SetMapping("s"); + pressedKeys.insert("s"); break; case Qt::Key_T: - SetMapping("t"); + pressedKeys.insert("t"); break; case Qt::Key_U: - SetMapping("u"); + pressedKeys.insert("u"); break; case Qt::Key_V: - SetMapping("v"); + pressedKeys.insert("v"); break; case Qt::Key_W: - SetMapping("w"); + pressedKeys.insert("w"); break; case Qt::Key_X: - SetMapping("x"); + pressedKeys.insert("x"); break; case Qt::Key_Y: - SetMapping("Y"); + pressedKeys.insert("Y"); break; case Qt::Key_Z: - SetMapping("z"); + pressedKeys.insert("z"); break; default: break; } return true; } + } - if (event->type() == QEvent::MouseButtonPress) { - QMouseEvent* mouseEvent = static_cast(event); + if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent* mouseEvent = static_cast(event); + if (pressedKeys.size() < 3) { switch (mouseEvent->button()) { case Qt::LeftButton: - SetMapping("leftbutton"); + pressedKeys.insert("leftbutton"); break; case Qt::RightButton: - SetMapping("rightbutton"); + pressedKeys.insert("rightbutton"); break; case Qt::MiddleButton: - SetMapping("middlebutton"); + pressedKeys.insert("middlebutton"); break; default: break; } return true; } + } - const QList AxisList = { - ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton, - ui->RStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->RStickRightButton}; + const QList AxisList = { + ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton, + ui->RStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->RStickRightButton}; - if (event->type() == QEvent::Wheel) { - QWheelEvent* wheelEvent = static_cast(event); + if (event->type() == QEvent::Wheel) { + QWheelEvent* wheelEvent = static_cast(event); + if (pressedKeys.size() < 3) { if (wheelEvent->angleDelta().y() > 5) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { - SetMapping("mousewheelup"); + pressedKeys.insert("mousewheelup"); } else { QMessageBox::information(this, tr("Cannot set mapping"), tr("Mousewheel cannot be mapped to stick outputs")); } } else if (wheelEvent->angleDelta().y() < -5) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { - SetMapping("mousewheeldown"); + pressedKeys.insert("mousewheeldown"); } else { QMessageBox::information(this, tr("Cannot set mapping"), tr("Mousewheel cannot be mapped to stick outputs")); @@ -972,9 +1003,9 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { // QT changes scrolling to horizontal for all widgets with the alt modifier if (Qt::AltModifier & QApplication::keyboardModifiers()) { - SetMapping("mousewheelup"); + pressedKeys.insert("mousewheelup"); } else { - SetMapping("mousewheelright"); + pressedKeys.insert("mousewheelright"); } } else { QMessageBox::information(this, tr("Cannot set mapping"), @@ -983,18 +1014,18 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { } else if (wheelEvent->angleDelta().x() < -5) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { if (Qt::AltModifier & QApplication::keyboardModifiers()) { - SetMapping("mousewheeldown"); + pressedKeys.insert("mousewheeldown"); } else { - SetMapping("mousewheelleft"); + pressedKeys.insert("mousewheelleft"); } } else { QMessageBox::information(this, tr("Cannot set mapping"), tr("Mousewheel cannot be mapped to stick outputs")); } } - return true; } } + return QDialog::eventFilter(obj, event); } diff --git a/src/qt_gui/kbm_gui.h b/src/qt_gui/kbm_gui.h index 06e58eef6..bfeed2b01 100644 --- a/src/qt_gui/kbm_gui.h +++ b/src/qt_gui/kbm_gui.h @@ -25,6 +25,22 @@ private: std::unique_ptr ui; std::shared_ptr m_game_info; +#ifdef _WIN32 + const int lctrl = 29; + const int rctrl = 57373; + const int lalt = 56; + const int ralt = 57400; + const int lshift = 42; + const int rshift = 54; +#else + const int lctrl = 37; + const int rctrl = 105; + const int lalt = 64; + const int ralt = 108; + const int lshift = 50; + const int rshift = 62; +#endif + bool eventFilter(QObject* obj, QEvent* event) override; void ButtonConnects(); void SetUIValuestoMappings(std::string config_id); @@ -33,6 +49,7 @@ private: void EnableMappingButtons(); void SetMapping(QString input); + QSet pressedKeys; bool EnableMapping = false; bool MappingCompleted = false; bool HelpWindowOpen = false; From 4ecdcf77d13dcac5562676f03638a28c71f272d2 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 23 Apr 2025 09:58:00 -0700 Subject: [PATCH 173/194] build: Update MoltenVK ICD API version. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 967229d6a..3561c2c3a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1101,7 +1101,7 @@ if (APPLE) cmake_path(GET MVK_ICD_DST PARENT_PATH MVK_ICD_DST_PARENT) cmake_path(GET MVK_DYLIB_DST PARENT_PATH MVK_DYLIB_DST_PARENT) - set(MVK_ICD "\\\{ \\\"file_format_version\\\": \\\"1.0.0\\\", \\\"ICD\\\": \\\{ \\\"library_path\\\": \\\"${MVK_DYLIB_ICD_PATH}\\\", \\\"api_version\\\": \\\"1.2.0\\\", \\\"is_portability_driver\\\": true \\\} \\\}") + set(MVK_ICD "\\\{ \\\"file_format_version\\\": \\\"1.0.0\\\", \\\"ICD\\\": \\\{ \\\"library_path\\\": \\\"${MVK_DYLIB_ICD_PATH}\\\", \\\"api_version\\\": \\\"1.3.0\\\", \\\"is_portability_driver\\\": true \\\} \\\}") add_custom_command( OUTPUT ${MVK_ICD_DST} COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_ICD_DST_PARENT} && ${CMAKE_COMMAND} -E echo ${MVK_ICD} > ${MVK_ICD_DST}) From ce3aded3e527c92f7a96a32dd5957354e311e225 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:09:04 -0700 Subject: [PATCH 174/194] fix: Intel crash on startup. --- src/video_core/renderer_vulkan/vk_instance.cpp | 8 +++++--- src/video_core/renderer_vulkan/vk_instance.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 089c0f00d..d33a1607b 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -521,9 +521,11 @@ void Instance::CollectDeviceParameters() { LOG_INFO(Render_Vulkan, "GPU_Vulkan_Extensions: {}", extensions); } -void Instance::CollectToolingInfo() { - if (GetDriverID() == vk::DriverId::eAmdProprietary) { - // Currently causes issues with Reshade on AMD proprietary, disabled until fix released. +void Instance::CollectToolingInfo() const { + if (driver_id == vk::DriverId::eAmdProprietary || + driver_id == vk::DriverId::eIntelProprietaryWindows) { + // AMD: Causes issues with Reshade. + // Intel: Causes crash on start. return; } const auto [tools_result, tools] = physical_device.getToolProperties(); diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index a9de01f84..b3f3e60b6 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -311,7 +311,7 @@ private: /// Collects telemetry information from the device. void CollectDeviceParameters(); - void CollectToolingInfo(); + void CollectToolingInfo() const; /// Gets the supported feature flags for a format. [[nodiscard]] vk::FormatFeatureFlags2 GetFormatFeatureFlags(vk::Format format) const; From a12d447bd65270cf447532b93263009cedd43970 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:21:14 -0500 Subject: [PATCH 175/194] sceKernelAllocateDirectMemory hotfixes (#2838) * Update memory.cpp * Clean logic FindDmemArea guarantees that the first dmem area we check contains search_start. Any dmem areas beyond the first one will be entirely past search_start, so checking against it in the loop is unnecessary. --- src/core/memory.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index cb80d6be4..494ffa70c 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -142,7 +142,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, auto mapping_start = search_start > dmem_area->second.base ? Common::AlignUp(search_start, alignment) : Common::AlignUp(dmem_area->second.base, alignment); - auto mapping_end = Common::AlignUp(mapping_start + size, alignment); + auto mapping_end = mapping_start + size; // Find the first free, large enough dmem area in the range. while ((!dmem_area->second.is_free || dmem_area->second.GetEnd() < mapping_end) && @@ -151,10 +151,8 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, dmem_area++; // Update local variables based on the new dmem_area - mapping_start = search_start > dmem_area->second.base - ? Common::AlignUp(search_start, alignment) - : Common::AlignUp(dmem_area->second.base, alignment); - mapping_end = Common::AlignUp(mapping_start + size, alignment); + mapping_start = Common::AlignUp(dmem_area->second.base, alignment); + mapping_end = mapping_start + size; } if (dmem_area == dmem_map.end()) { From c01590175a8f04e90a5daa1d785a4dbb48e4b012 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Thu, 24 Apr 2025 22:13:44 +0200 Subject: [PATCH 176/194] Implement sceImeDialogGetPanelSize (#2839) * Implement sceImeDialogGetPanelSize * Fix header * Clang * Adjust values that are different from Ime * Add original sizes as comments * clang * At this point half of the PR is from squidbus, and I'm just typing out what they say --------- Co-authored-by: squidbus <175574877+squidbus@users.noreply.github.com> --- src/core/libraries/ime/ime_dialog.cpp | 32 ++++++++++++++++++++++++--- src/core/libraries/ime/ime_dialog.h | 3 ++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/ime/ime_dialog.cpp b/src/core/libraries/ime/ime_dialog.cpp index 9151aa64e..bee185787 100644 --- a/src/core/libraries/ime/ime_dialog.cpp +++ b/src/core/libraries/ime/ime_dialog.cpp @@ -83,9 +83,35 @@ int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeDialogGetPanelSize() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u32* width, + u32* height) { + LOG_INFO(Lib_ImeDialog, "called"); + + if (!width || !height) { + return Error::INVALID_ADDRESS; + } + switch (param->type) { + case OrbisImeType::Default: + case OrbisImeType::BasicLatin: + case OrbisImeType::Url: + case OrbisImeType::Mail: + *width = 500; // original: 793 + if (True(param->option & OrbisImeDialogOption::Multiline)) { + *height = 300; // original: 576 + } else { + *height = 150; // original: 476 + } + break; + case OrbisImeType::Number: + *width = 370; + *height = 470; + break; + default: + LOG_ERROR(Lib_ImeDialog, "Unknown OrbisImeType: {}", (u32)param->type); + return Error::INVALID_PARAM; + } + + return Error::OK; } int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended() { diff --git a/src/core/libraries/ime/ime_dialog.h b/src/core/libraries/ime/ime_dialog.h index c8b228498..33abc7ecd 100644 --- a/src/core/libraries/ime/ime_dialog.h +++ b/src/core/libraries/ime/ime_dialog.h @@ -155,7 +155,8 @@ Error PS4_SYSV_ABI sceImeDialogForceClose(); Error PS4_SYSV_ABI sceImeDialogForTestFunction(); int PS4_SYSV_ABI sceImeDialogGetCurrentStarState(); int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm(); -int PS4_SYSV_ABI sceImeDialogGetPanelSize(); +Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u32* width, + u32* height); int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(); Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result); OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus(); From d370ea32f4d0e7a00e87ef104d2bde22eaa9c343 Mon Sep 17 00:00:00 2001 From: Dmugetsu <168934208+diegolix29@users.noreply.github.com> Date: Thu, 24 Apr 2025 15:46:22 -0600 Subject: [PATCH 177/194] Sysmodules (#2826) * Some sysmodules inconsistencies fixed. Based on Visual studio flags if they are irrelevant lmk * Suggestions - info passed to sceKernelGetModuleInfoForUnwind and if name field matches it gets zeroed * Final suggestions * reverting OrbisModuleInfoForUnwind and modifing header. --- src/core/libraries/system/sysmodule.cpp | 40 +++++++++++++++++++++---- src/core/libraries/system/sysmodule.h | 4 ++- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/core/libraries/system/sysmodule.cpp b/src/core/libraries/system/sysmodule.cpp index 6c73764f2..d9e78e4ab 100644 --- a/src/core/libraries/system/sysmodule.cpp +++ b/src/core/libraries/system/sysmodule.cpp @@ -19,11 +19,40 @@ int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, void* info) { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - Kernel::OrbisModuleInfoForUnwind module_info; - module_info.st_size = 0x130; - s32 res = Kernel::sceKernelGetModuleInfoForUnwind(addr, flags, &module_info); +s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, + Kernel::OrbisModuleInfoForUnwind* info) { + LOG_TRACE(Lib_SysModule, "sceSysmoduleGetModuleInfoForUnwind(addr=0x{:X}, flags=0x{:X})", addr, + flags); + + s32 res = Kernel::sceKernelGetModuleInfoForUnwind(addr, flags, info); + if (res != 0) { + return res; + } + + static constexpr std::array modules_to_hide = { + "libc.prx", + "libc.sprx", + "libSceAudioLatencyEstimation.prx", + "libSceFace.prx", + "libSceFaceTracker.prx", + "libSceFios2.prx", + "libSceFios2.sprx", + "libSceFontGsm.prx", + "libSceHand.prx", + "libSceHandTracker.prx", + "libSceHeadTracker.prx", + "libSceJobManager.prx", + "libSceNpCppWebApi.prx", + "libSceNpToolkit.prx", + "libSceNpToolkit2.prx", + "libSceS3DConversion.prx", + "libSceSmart.prx", + }; + + const std::string_view module_name = info->name.data(); + if (std::ranges::find(modules_to_hide, module_name) != modules_to_hide.end()) { + std::ranges::fill(info->name, '\0'); + } return res; } @@ -56,7 +85,6 @@ int PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id) { } int PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id) { - auto color_name = magic_enum::enum_name(id); LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {}", magic_enum::enum_name(id)); return ORBIS_OK; } diff --git a/src/core/libraries/system/sysmodule.h b/src/core/libraries/system/sysmodule.h index dfbdca162..9a5fe9513 100644 --- a/src/core/libraries/system/sysmodule.h +++ b/src/core/libraries/system/sysmodule.h @@ -4,6 +4,7 @@ #pragma once #include "common/types.h" +#include "core/libraries/kernel/process.h" namespace Core::Loader { class SymbolsResolver; @@ -152,7 +153,8 @@ enum class OrbisSysModuleInternal : u32 { }; int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal(); -s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, void* info); +s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, + Kernel::OrbisModuleInfoForUnwind* info); int PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule(); int PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded(); int PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id); From 15d6a45dcdf49bbd232e0ef275373c9b1018a839 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 24 Apr 2025 19:40:50 -0700 Subject: [PATCH 178/194] externals: Simplify MoltenVK bundling. (#2842) --- CMakeLists.txt | 32 +++++++++++++++----------------- externals/MoltenVK/MoltenVK | 2 +- externals/MoltenVK/SPIRV-Cross | 2 +- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3561c2c3a..96cce0b10 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1084,33 +1084,31 @@ endif() if (APPLE) # Include MoltenVK, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers. - set(MVK_DYLIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/externals/MoltenVK/libMoltenVK.dylib) - if (ENABLE_QT_GUI) - set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../Frameworks") - set(MVK_ICD_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json) - set(MVK_DYLIB_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/Frameworks/libMoltenVK.dylib) - set(MVK_DYLIB_ICD_PATH "../../../Frameworks/libMoltenVK.dylib") + set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d") + set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}") + set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/${MVK_BUNDLE_PATH}) else() set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path") - set(MVK_ICD_DST ${CMAKE_CURRENT_BINARY_DIR}/MoltenVK_icd.json) - set(MVK_DYLIB_DST ${CMAKE_CURRENT_BINARY_DIR}/libMoltenVK.dylib) - set(MVK_DYLIB_ICD_PATH "./libMoltenVK.dylib") + set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}) endif() - cmake_path(GET MVK_ICD_DST PARENT_PATH MVK_ICD_DST_PARENT) - cmake_path(GET MVK_DYLIB_DST PARENT_PATH MVK_DYLIB_DST_PARENT) + set(MVK_DYLIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/externals/MoltenVK/libMoltenVK.dylib) + set(MVK_DYLIB_DST ${MVK_DST}/libMoltenVK.dylib) + set(MVK_ICD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK/MoltenVK/icd/MoltenVK_icd.json) + set(MVK_ICD_DST ${MVK_DST}/MoltenVK_icd.json) - set(MVK_ICD "\\\{ \\\"file_format_version\\\": \\\"1.0.0\\\", \\\"ICD\\\": \\\{ \\\"library_path\\\": \\\"${MVK_DYLIB_ICD_PATH}\\\", \\\"api_version\\\": \\\"1.3.0\\\", \\\"is_portability_driver\\\": true \\\} \\\}") + add_custom_command( + OUTPUT ${MVK_DST} + COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST}) add_custom_command( OUTPUT ${MVK_ICD_DST} - COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_ICD_DST_PARENT} && ${CMAKE_COMMAND} -E echo ${MVK_ICD} > ${MVK_ICD_DST}) - + DEPENDS ${MVK_ICD_SRC} ${MVK_DST} + COMMAND ${CMAKE_COMMAND} -E copy ${MVK_ICD_SRC} ${MVK_ICD_DST}) add_custom_command( OUTPUT ${MVK_DYLIB_DST} - DEPENDS ${MVK_DYLIB_SRC} - COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DYLIB_DST_PARENT} && ${CMAKE_COMMAND} -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST}) - + DEPENDS ${MVK_DYLIB_SRC} ${MVK_DST} + COMMAND ${CMAKE_COMMAND} -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST}) add_custom_target(CopyMoltenVK DEPENDS ${MVK_ICD_DST} ${MVK_DYLIB_DST}) add_dependencies(CopyMoltenVK MoltenVK) add_dependencies(shadps4 CopyMoltenVK) diff --git a/externals/MoltenVK/MoltenVK b/externals/MoltenVK/MoltenVK index 4cf8f9468..87a8e8b13 160000 --- a/externals/MoltenVK/MoltenVK +++ b/externals/MoltenVK/MoltenVK @@ -1 +1 @@ -Subproject commit 4cf8f94684c53e581eb9cc694dd3305d1f7d9959 +Subproject commit 87a8e8b13d4ad8835367fea1ebad1896d0460946 diff --git a/externals/MoltenVK/SPIRV-Cross b/externals/MoltenVK/SPIRV-Cross index 2275d0efc..791877574 160000 --- a/externals/MoltenVK/SPIRV-Cross +++ b/externals/MoltenVK/SPIRV-Cross @@ -1 +1 @@ -Subproject commit 2275d0efc4f2fa46851035d9d3c67c105bc8b99e +Subproject commit 7918775748c5e2f5c40d9918ce68825035b5a1e1 From 632ed99ee77de8e1c7513eed0bd2319fffca9ff8 Mon Sep 17 00:00:00 2001 From: MajorP93 Date: Sat, 26 Apr 2025 00:06:51 +0200 Subject: [PATCH 179/194] ci: Bump Clang to 19 for Linux builds, align LLVM repository with runner version (#2844) * ci: Bump Clang to 19 for Linux builds * PR #2434 was intended to bump Clang to 19. In reality it only made sure that clang-format-19 is being used and that the shadPS4 codebase can be compiled with Clang 19. This PR makes sure that Clang 19 is actually being used for Linux builds which makes sense since we use Clang 19 for Windows builds already (Since Visual Studio 17.13 Clang 19 is being shipped). * ci: Use noble variant of LLVM repository * shadPS4 has been using Ubuntu 24.04 runners for some time now. This commit makes sure the correct LLVM repository is being used. --- .github/workflows/build.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 55549ab4c..787aba251 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: - name: Install run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - - sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main' + sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main' sudo apt update sudo apt install clang-format-19 - name: Build @@ -281,8 +281,13 @@ jobs: with: submodules: recursive + - name: Add LLVM repository + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main' + - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -304,7 +309,7 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) @@ -337,8 +342,13 @@ jobs: with: submodules: recursive + - name: Add LLVM repository + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main' + - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -360,7 +370,7 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) From e816bc4b99ec1859467f7e69fce68b46009d821b Mon Sep 17 00:00:00 2001 From: baggins183 Date: Fri, 25 Apr 2025 19:44:03 -0700 Subject: [PATCH 180/194] Use GetSrc in VALU insts instead of assuming vector reg (was vcc_lo) (#2845) * Use GetSrc in v_add_i32 instead of assuming vector reg (was vcc_lo) * some other cases --- src/shader_recompiler/frontend/translate/vector_alu.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 787cf6ad3..3ce86c131 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -513,13 +513,13 @@ void Translator::V_LSHLREV_B32(const GcnInst& inst) { void Translator::V_AND_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; + const IR::U32 src1{GetSrc(inst.src[1])}; SetDst(inst.dst[0], ir.BitwiseAnd(src0, src1)); } void Translator::V_OR_B32(bool is_xor, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; + const IR::U32 src1{GetSrc(inst.src[1])}; SetDst(inst.dst[0], is_xor ? ir.BitwiseXor(src0, src1) : IR::U32(ir.BitwiseOr(src0, src1))); } @@ -579,7 +579,7 @@ void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { void Translator::V_ADD_I32(const GcnInst& inst) { // Signed or unsigned components const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; + const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 result{ir.IAdd(src0, src1)}; SetDst(inst.dst[0], result); From c09fff2da6f4a879711d915f51797eebe315a60a Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 27 Apr 2025 01:04:17 -0500 Subject: [PATCH 181/194] VideoOut event cleanup (#2849) * Readable VideoOutEvent data packing Inspired by the work of former shadPS4 devs and mostly based on red_prig's current code. * Apply DceData struct to sceVideoOutGetEventCount Makes the code easier to read * Update equeue.h * Update main.cpp * Update equeue.h * Proper struct names * Fix hint mask Thanks to red_prig for catching my mistake here. * Clang * Fix header discrepancy --- src/core/libraries/kernel/equeue.h | 33 +++++++++++++++-------- src/core/libraries/videoout/video_out.cpp | 5 ++-- src/core/libraries/videoout/video_out.h | 10 +++++-- src/main.cpp | 2 +- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index 11c09bb37..2bd7ef510 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -61,6 +61,18 @@ struct SceKernelEvent { void* udata = nullptr; /* opaque user data identifier */ }; +struct OrbisVideoOutEventHint { + u64 event_id : 8; + u64 video_id : 8; + u64 flip_arg : 48; +}; + +struct OrbisVideoOutEventData { + u64 time : 12; + u64 count : 4; + u64 flip_arg : 48; +}; + struct EqueueEvent { SceKernelEvent event; void* data = nullptr; @@ -84,19 +96,18 @@ struct EqueueEvent { 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) { + if (data != nullptr) { + auto event_data = static_cast(event.data); + auto event_hint_raw = reinterpret_cast(data); + auto event_hint = static_cast(event_hint_raw); + if (event_hint.event_id == event.ident && event.ident != 0xfe) { auto time = Common::FencedRDTSC(); - auto mask = 0xF000; - if ((static_cast(event.data) & 0xF000) != 0xF000) { - mask = (static_cast(event.data) + 0x1000) & 0xF000; + auto counter = event_data.count; + if (counter != 0xf) { + counter++; } - event.data = (mask | static_cast(static_cast(time) & 0xFFF) | - (hint & 0xFFFFFFFFFFFF0000)); + event.data = + (time & 0xfff) | (counter << 0xc) | (event_hint_raw & 0xffffffffffff0000); } } } diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index 3c839dadd..c5208b6dd 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -220,7 +220,7 @@ s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* if (ev->ident != static_cast(OrbisVideoOutInternalEventId::Flip) || ev->data == 0) { *data = event_data; } else { - *data = event_data | 0xFFFF000000000000; + *data = event_data | 0xffff000000000000; } return ORBIS_OK; } @@ -233,7 +233,8 @@ s32 PS4_SYSV_ABI sceVideoOutGetEventCount(const Kernel::SceKernelEvent* ev) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; } - return (ev->data >> 0xc) & 0xf; + auto event_data = static_cast(ev->data); + return event_data.count; } s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status) { diff --git a/src/core/libraries/videoout/video_out.h b/src/core/libraries/videoout/video_out.h index f3e661de4..7db09530b 100644 --- a/src/core/libraries/videoout/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -111,6 +111,12 @@ struct SceVideoOutColorSettings { u32 reserved[3]; }; +struct OrbisVideoOutEventData { + u64 time : 12; + u64 count : 4; + u64 flip_arg : 48; +}; + void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat, u32 tilingMode, u32 aspectRatio, u32 width, u32 height, u32 pitchInPixel); @@ -128,8 +134,8 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index, const void* param); s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle); -int PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev); -int PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, int64_t* data); +s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev); +s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* data); s32 PS4_SYSV_ABI sceVideoOutColorSettingsSetGamma(SceVideoOutColorSettings* settings, float gamma); s32 PS4_SYSV_ABI sceVideoOutAdjustColor(s32 handle, const SceVideoOutColorSettings* settings); diff --git a/src/main.cpp b/src/main.cpp index 6b334e446..85581774b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -154,7 +154,7 @@ int main(int argc, char* argv[]) { // If no game directory is set and no command line argument, prompt for it if (Config::getGameInstallDirs().empty()) { std::cout << "Warning: No game folder set, please set it by calling shadps4" - " with the --add-game-folder argument"; + " with the --add-game-folder argument\n"; } if (!has_game_argument) { From 410313ca87840de8b8bd06a18e74863863b60db6 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 27 Apr 2025 01:32:01 -0500 Subject: [PATCH 182/194] Implement sceKernelGetModuleInfo, sceKernelGetModuleInfoInternal, and sceKernelGetModuleList (#2850) * Fix GetModule exception Simple mistake * Prevent OOB writes in add_segment Due to mistakes in our linker logic, OpenOrbis' libSceFios2 causes OOB writes here. While the ideal solution would be to fix the erroneous behavior, the best I'm capable of right now is just preventing the OOB writes. * Implement sceKernelGetModuleInfo, sceKernelGetModuleInfoInternal, sceKernelGetModuleList These are implemented based on hardware observations and a homebrew sample made by red_prig. I've yet to test what error cases can show up. * Clang * Accurate error returns If there are more modules than provided space, then return kernel ENOMEM. If either handles or out_count are null, return kernel EFAULT. * Accurate error checks in ModuleInfo functions * Clang --- src/core/libraries/kernel/process.cpp | 59 +++++++++++++++++++++++++++ src/core/linker.h | 2 +- src/core/module.cpp | 12 ++++-- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index 02f8a538d..8a37e78d5 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -127,6 +127,62 @@ int PS4_SYSV_ABI sceKernelGetModuleInfoFromAddr(VAddr addr, int flags, return ORBIS_OK; } +s32 PS4_SYSV_ABI sceKernelGetModuleInfo(s32 handle, Core::OrbisKernelModuleInfo* info) { + if (info == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (info->st_size != sizeof(Core::OrbisKernelModuleInfo)) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + + auto* linker = Common::Singleton::Instance(); + auto* module = linker->GetModule(handle); + if (module == nullptr) { + return ORBIS_KERNEL_ERROR_ESRCH; + } + *info = module->GetModuleInfo(); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceKernelGetModuleInfoInternal(s32 handle, Core::OrbisKernelModuleInfoEx* info) { + if (info == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (info->st_size != sizeof(Core::OrbisKernelModuleInfoEx)) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + + auto* linker = Common::Singleton::Instance(); + auto* module = linker->GetModule(handle); + if (module == nullptr) { + return ORBIS_KERNEL_ERROR_ESRCH; + } + *info = module->GetModuleInfoEx(); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceKernelGetModuleList(s32* handles, u64 num_array, u64* out_count) { + if (handles == nullptr || out_count == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + + auto* linker = Common::Singleton::Instance(); + u64 count = 0; + auto* module = linker->GetModule(count); + while (module != nullptr && count < num_array) { + handles[count] = count; + count++; + module = linker->GetModule(count); + } + + if (count == num_array && module != nullptr) { + return ORBIS_KERNEL_ERROR_ENOMEM; + } + + *out_count = count; + return ORBIS_OK; +} + s32 PS4_SYSV_ABI exit(s32 status) { UNREACHABLE_MSG("Exiting with status code {}", status); return 0; @@ -141,6 +197,9 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("LwG8g3niqwA", "libkernel", 1, "libkernel", 1, 1, sceKernelDlsym); LIB_FUNCTION("RpQJJVKTiFM", "libkernel", 1, "libkernel", 1, 1, sceKernelGetModuleInfoForUnwind); LIB_FUNCTION("f7KBOafysXo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetModuleInfoFromAddr); + LIB_FUNCTION("kUpgrXIrz7Q", "libkernel", 1, "libkernel", 1, 1, sceKernelGetModuleInfo); + LIB_FUNCTION("HZO7xOos4xc", "libkernel", 1, "libkernel", 1, 1, sceKernelGetModuleInfoInternal); + LIB_FUNCTION("IuxnUuXk6Bg", "libkernel", 1, "libkernel", 1, 1, sceKernelGetModuleList); LIB_FUNCTION("6Z83sYWFlA8", "libkernel", 1, "libkernel", 1, 1, exit); } diff --git a/src/core/linker.h b/src/core/linker.h index 63dfc37e8..028e18ead 100644 --- a/src/core/linker.h +++ b/src/core/linker.h @@ -83,7 +83,7 @@ public: } Module* GetModule(s32 index) const { - if (index >= 0 || index < m_modules.size()) { + if (index >= 0 && index < m_modules.size()) { return m_modules.at(index).get(); } return nullptr; diff --git a/src/core/module.cpp b/src/core/module.cpp index 1004f4404..cbe44457c 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -135,10 +135,14 @@ void Module::LoadModuleToMemory(u32& max_tls_index) { if (do_map) { elf.LoadSegment(segment_addr, phdr.p_offset, phdr.p_filesz); } - auto& segment = info.segments[info.num_segments++]; - segment.address = segment_addr; - segment.prot = phdr.p_flags; - segment.size = GetAlignedSize(phdr); + if (info.num_segments < 4) { + auto& segment = info.segments[info.num_segments++]; + segment.address = segment_addr; + segment.prot = phdr.p_flags; + segment.size = GetAlignedSize(phdr); + } else { + LOG_ERROR(Core_Linker, "Attempting to add too many segments!"); + } }; for (u16 i = 0; i < elf_header.e_phnum; i++) { From cef795b80b032fd623485bf41efb3309b3d7ee9d Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sun, 27 Apr 2025 13:32:29 -0300 Subject: [PATCH 183/194] devtools: persist fsr configs (#2852) Saves FSR config to imgui.ini so it won't reset every startup --- src/core/devtools/layer.cpp | 2 +- src/core/devtools/options.cpp | 22 +++++++++++++++++++++- src/imgui/imgui_texture.h | 3 +++ src/imgui/renderer/imgui_core.cpp | 2 ++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 94b39e801..a93178de5 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "SDL3/SDL_log.h" #include "layer.h" #include +#include "SDL3/SDL_log.h" #include "common/config.h" #include "common/singleton.h" #include "common/types.h" diff --git a/src/core/devtools/options.cpp b/src/core/devtools/options.cpp index 2def42071..f4b0ceb9a 100644 --- a/src/core/devtools/options.cpp +++ b/src/core/devtools/options.cpp @@ -1,9 +1,14 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "options.h" + +#include #include -#include "options.h" +#include "video_core/renderer_vulkan/vk_presenter.h" + +extern std::unique_ptr presenter; namespace Core::Devtools { @@ -12,6 +17,7 @@ TOptions Options; void LoadOptionsConfig(const char* line) { char str[512]; int i; + float f; if (sscanf(line, "disassembler_cli_isa=%511[^\n]", str) == 1) { Options.disassembler_cli_isa = str; return; @@ -24,12 +30,26 @@ void LoadOptionsConfig(const char* line) { Options.frame_dump_render_on_collapse = i != 0; return; } + if (sscanf(line, "fsr_enabled=%d", &i) == 1) { + presenter->GetFsrSettingsRef().enable = i != 0; + return; + } + if (sscanf(line, "fsr_rcas_enabled=%d", &i) == 1) { + presenter->GetFsrSettingsRef().use_rcas = i != 0; + return; + } + if (sscanf(line, "fsr_rcas_attenuation=%f", &f) == 1) { + presenter->GetFsrSettingsRef().rcas_attenuation = f; + } } void SerializeOptionsConfig(ImGuiTextBuffer* buf) { buf->appendf("disassembler_cli_isa=%s\n", Options.disassembler_cli_isa.c_str()); buf->appendf("disassembler_cli_spv=%s\n", Options.disassembler_cli_spv.c_str()); buf->appendf("frame_dump_render_on_collapse=%d\n", Options.frame_dump_render_on_collapse); + buf->appendf("fsr_enabled=%d\n", presenter->GetFsrSettingsRef().enable); + buf->appendf("fsr_rcas_enabled=%d\n", presenter->GetFsrSettingsRef().use_rcas); + buf->appendf("fsr_rcas_attenuation=%f\n", presenter->GetFsrSettingsRef().rcas_attenuation); } } // namespace Core::Devtools diff --git a/src/imgui/imgui_texture.h b/src/imgui/imgui_texture.h index 1a38066d0..d84eda6b7 100644 --- a/src/imgui/imgui_texture.h +++ b/src/imgui/imgui_texture.h @@ -4,8 +4,11 @@ #pragma once #include +#include #include +#include "common/types.h" + namespace ImGui { namespace Core::TextureManager { diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 50ce41ebf..d143232dc 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -112,6 +112,8 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w if (const auto dpi = SDL_GetWindowDisplayScale(window.GetSDLWindow()); dpi > 0.0f) { GetIO().FontGlobalScale = dpi; } + + std::at_quick_exit([] { SaveIniSettingsToDisk(GetIO().IniFilename); }); } void OnResize() { From 254375ef0c2807f7c7a68ecfa3bb87fe82cbab1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Bogd=C4=81ns?= Date: Sun, 27 Apr 2025 20:57:20 +0300 Subject: [PATCH 184/194] Update ime_dialog.h (#2853) Fix the incorrect ORBIS_IME_DIALOG_MAX_TEXT_LENGTH; a larger value is required for at least the game Undertale --- src/core/libraries/ime/ime_dialog.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/ime/ime_dialog.h b/src/core/libraries/ime/ime_dialog.h index 33abc7ecd..526e5f022 100644 --- a/src/core/libraries/ime/ime_dialog.h +++ b/src/core/libraries/ime/ime_dialog.h @@ -13,7 +13,7 @@ class SymbolsResolver; namespace Libraries::ImeDialog { -constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 0x78; +constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 2048; enum class Error : u32 { OK = 0x0, From b505829e1603fa6c638572203dd846690cd2f080 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 27 Apr 2025 16:52:52 -0700 Subject: [PATCH 185/194] lower_buffer_format_to_raw: Fix handling of format remapping. (#2857) --- .../ir/passes/lower_buffer_format_to_raw.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp b/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp index 3fdc6f0cd..658a495bc 100644 --- a/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp +++ b/src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp @@ -196,13 +196,18 @@ static void LowerBufferFormatInst(IR::Block& block, IR::Inst& inst, Info& info) const auto buffer{desc.GetSharp(info)}; const auto is_inst_typed = flags.inst_data_fmt != AmdGpu::DataFormat::FormatInvalid; - const auto data_format = is_inst_typed ? flags.inst_data_fmt.Value() : buffer.GetDataFmt(); - const auto num_format = is_inst_typed ? flags.inst_num_fmt.Value() : buffer.GetNumberFmt(); + const auto data_format = + is_inst_typed ? AmdGpu::RemapDataFormat(flags.inst_data_fmt.Value()) : buffer.GetDataFmt(); const auto format_info = FormatInfo{ .data_format = data_format, - .num_format = num_format, - .swizzle = is_inst_typed ? AmdGpu::IdentityMapping : buffer.DstSelect(), - .num_conversion = AmdGpu::MapNumberConversion(num_format), + .num_format = is_inst_typed + ? AmdGpu::RemapNumberFormat(flags.inst_num_fmt.Value(), data_format) + : buffer.GetNumberFmt(), + .swizzle = is_inst_typed + ? AmdGpu::RemapSwizzle(flags.inst_data_fmt.Value(), AmdGpu::IdentityMapping) + : buffer.DstSelect(), + .num_conversion = is_inst_typed ? AmdGpu::MapNumberConversion(flags.inst_num_fmt.Value()) + : buffer.GetNumberConversion(), .num_components = AmdGpu::NumComponents(data_format), }; From ff984d3cde34ff0c725b6ce379540f48fc163b05 Mon Sep 17 00:00:00 2001 From: MajorP93 Date: Mon, 28 Apr 2025 05:34:59 +0200 Subject: [PATCH 186/194] ci: Use mold linker for Linux builds (#2847) * The default linker which happens to be BFD in Ubuntu 24.04 does not support Clang's ThinLTO which CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON tries to enable. * Using mold linker fixes this and reduces build time a bit. * For consistency reasons we enable mold linker for GCC builds aswell. --- .github/workflows/build.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 787aba251..ceb915f6a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -287,7 +287,7 @@ jobs: sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main' - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -309,7 +309,7 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) @@ -348,7 +348,7 @@ jobs: sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main' - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -370,7 +370,7 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) @@ -395,7 +395,7 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -417,7 +417,7 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) @@ -431,7 +431,7 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -453,7 +453,7 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) From 81ad31ce319e47fb94c9303145a123afbdaddfa1 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:56:17 -0700 Subject: [PATCH 187/194] pp_pass: Use correct surface format. (#2860) --- src/video_core/renderer_vulkan/host_passes/pp_pass.cpp | 4 ++-- src/video_core/renderer_vulkan/host_passes/pp_pass.h | 2 +- src/video_core/renderer_vulkan/vk_presenter.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp b/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp index 0c40ffd7a..73dd3a7b5 100644 --- a/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp +++ b/src/video_core/renderer_vulkan/host_passes/pp_pass.cpp @@ -14,7 +14,7 @@ namespace Vulkan::HostPasses { -void PostProcessingPass::Create(vk::Device device) { +void PostProcessingPass::Create(vk::Device device, const vk::Format surface_format) { static const std::array pp_shaders{ HostShaders::FS_TRI_VERT, HostShaders::POST_PROCESS_FRAG, @@ -76,7 +76,7 @@ void PostProcessingPass::Create(vk::Device device) { Check<"create pp pipeline layout">(device.createPipelineLayoutUnique(layout_info)); const std::array pp_color_formats{ - vk::Format::eB8G8R8A8Unorm, // swapchain.GetSurfaceFormat().format, + surface_format, }; const vk::PipelineRenderingCreateInfo pipeline_rendering_ci{ .colorAttachmentCount = pp_color_formats.size(), diff --git a/src/video_core/renderer_vulkan/host_passes/pp_pass.h b/src/video_core/renderer_vulkan/host_passes/pp_pass.h index 6127bb5c1..f95c02e8d 100644 --- a/src/video_core/renderer_vulkan/host_passes/pp_pass.h +++ b/src/video_core/renderer_vulkan/host_passes/pp_pass.h @@ -19,7 +19,7 @@ public: u32 hdr = 0; }; - void Create(vk::Device device); + void Create(vk::Device device, vk::Format surface_format); void Render(vk::CommandBuffer cmdbuf, vk::ImageView input, vk::Extent2D input_size, Frame& output, Settings settings); diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 4a6a5c7c2..6bd4b26fa 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -130,7 +130,7 @@ Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_ } fsr_pass.Create(device, instance.GetAllocator(), num_images); - pp_pass.Create(device); + pp_pass.Create(device, swapchain.GetSurfaceFormat().format); ImGui::Layer::AddLayer(Common::Singleton::Instance()); } From 83fd0683fa71944a9af1150a541f117b9997ffa8 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:57:04 -0700 Subject: [PATCH 188/194] fix: Properly enable depthBounds feature. --- src/video_core/renderer_vulkan/vk_instance.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index d33a1607b..14c72836e 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -331,6 +331,7 @@ bool Instance::CreateDevice() { .tessellationShader = features.tessellationShader, .logicOp = features.logicOp, .depthBiasClamp = features.depthBiasClamp, + .depthBounds = features.depthBounds, .fillModeNonSolid = features.fillModeNonSolid, .multiViewport = features.multiViewport, .samplerAnisotropy = features.samplerAnisotropy, From 59d060bc164581e98ce606eda5821ff3a27c76d8 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 27 Apr 2025 21:06:10 -0700 Subject: [PATCH 189/194] fix: gcc compile --- src/video_core/renderer_vulkan/vk_instance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 14c72836e..072807124 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -331,8 +331,8 @@ bool Instance::CreateDevice() { .tessellationShader = features.tessellationShader, .logicOp = features.logicOp, .depthBiasClamp = features.depthBiasClamp, - .depthBounds = features.depthBounds, .fillModeNonSolid = features.fillModeNonSolid, + .depthBounds = features.depthBounds, .multiViewport = features.multiViewport, .samplerAnisotropy = features.samplerAnisotropy, .vertexPipelineStoresAndAtomics = features.vertexPipelineStoresAndAtomics, From 385c5a4507cca22891c6f4e267b7bf22fcb2e0b8 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 27 Apr 2025 21:53:36 -0700 Subject: [PATCH 190/194] fix: Add missing OpSelectionMerge in bounds check. --- src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index c6ec65606..211899714 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -38,6 +38,7 @@ Id BufferAtomicU32BoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto e const Id ib_label = ctx.OpLabel(); const Id oob_label = ctx.OpLabel(); const Id end_label = ctx.OpLabel(); + ctx.OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone); ctx.OpBranchConditional(in_bounds, ib_label, oob_label); ctx.AddLabel(ib_label); const Id ib_result = emit_func(); From 81fa9b7fff603a188fc235373b2933962292ae9a Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 28 Apr 2025 00:04:16 -0700 Subject: [PATCH 191/194] shader_recompiler: Add lowering pass for when 64-bit float is unsupported. (#2858) * shader_recompiler: Add lowering pass for when 64-bit float is unsupported. * shader_recompiler: Fix PackDouble2x32/UnpackDouble2x32 type. * shader_recompiler: Remove extra bit cast implementations. --- CMakeLists.txt | 1 + .../spirv/emit_spirv_bitwise_conversion.cpp | 14 +- .../backend/spirv/emit_spirv_instructions.h | 5 +- .../frontend/translate/translate.cpp | 7 +- src/shader_recompiler/ir/ir_emitter.cpp | 18 +- src/shader_recompiler/ir/ir_emitter.h | 3 +- src/shader_recompiler/ir/opcodes.inc | 5 +- src/shader_recompiler/ir/passes/ir_passes.h | 1 + .../ir/passes/lower_fp64_to_fp32.cpp | 186 ++++++++++++++++++ .../ir/passes/shader_info_collection_pass.cpp | 3 +- src/shader_recompiler/profile.h | 1 + src/shader_recompiler/recompiler.cpp | 3 + src/video_core/renderer_vulkan/vk_instance.h | 5 + .../renderer_vulkan/vk_pipeline_cache.cpp | 1 + 14 files changed, 220 insertions(+), 33 deletions(-) create mode 100644 src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 96cce0b10..e36c1f280 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -840,6 +840,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h src/shader_recompiler/ir/passes/identity_removal_pass.cpp src/shader_recompiler/ir/passes/ir_passes.h src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp + src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp src/shader_recompiler/ir/passes/resource_tracking_pass.cpp src/shader_recompiler/ir/passes/ring_access_elimination.cpp diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp index 56a6abc05..43655ba3f 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp @@ -64,10 +64,6 @@ Id EmitBitCastU32F32(EmitContext& ctx, Id value) { return ctx.OpBitcast(ctx.U32[1], value); } -Id EmitBitCastU64F64(EmitContext& ctx, Id value) { - return ctx.OpBitcast(ctx.U64, value); -} - Id EmitBitCastF16U16(EmitContext& ctx, Id value) { return ctx.OpBitcast(ctx.F16[1], value); } @@ -76,10 +72,6 @@ Id EmitBitCastF32U32(EmitContext& ctx, Id value) { return ctx.OpBitcast(ctx.F32[1], value); } -void EmitBitCastF64U64(EmitContext&) { - UNREACHABLE_MSG("SPIR-V Instruction"); -} - Id EmitPackUint2x32(EmitContext& ctx, Id value) { return ctx.OpBitcast(ctx.U64, value); } @@ -88,10 +80,14 @@ Id EmitUnpackUint2x32(EmitContext& ctx, Id value) { return ctx.OpBitcast(ctx.U32[2], value); } -Id EmitPackFloat2x32(EmitContext& ctx, Id value) { +Id EmitPackDouble2x32(EmitContext& ctx, Id value) { return ctx.OpBitcast(ctx.F64[1], value); } +Id EmitUnpackDouble2x32(EmitContext& ctx, Id value) { + return ctx.OpBitcast(ctx.U32[2], value); +} + Id EmitPackUnorm2x16(EmitContext& ctx, Id value) { return ctx.OpPackUnorm2x16(ctx.U32[1], value); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 9b7528be8..079f1005d 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -202,13 +202,12 @@ Id EmitSelectF32(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitSelectF64(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitBitCastU16F16(EmitContext& ctx, Id value); Id EmitBitCastU32F32(EmitContext& ctx, Id value); -Id EmitBitCastU64F64(EmitContext& ctx, Id value); Id EmitBitCastF16U16(EmitContext& ctx, Id value); Id EmitBitCastF32U32(EmitContext& ctx, Id value); -void EmitBitCastF64U64(EmitContext& ctx); Id EmitPackUint2x32(EmitContext& ctx, Id value); Id EmitUnpackUint2x32(EmitContext& ctx, Id value); -Id EmitPackFloat2x32(EmitContext& ctx, Id value); +Id EmitPackDouble2x32(EmitContext& ctx, Id value); +Id EmitUnpackDouble2x32(EmitContext& ctx, Id value); Id EmitPackUnorm2x16(EmitContext& ctx, Id value); Id EmitUnpackUnorm2x16(EmitContext& ctx, Id value); Id EmitPackSnorm2x16(EmitContext& ctx, Id value); diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index 230f3917f..c5a5814a4 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -336,7 +336,7 @@ T Translator::GetSrc64(const InstOperand& operand) { const auto value_lo = ir.GetVectorReg(IR::VectorReg(operand.code)); const auto value_hi = ir.GetVectorReg(IR::VectorReg(operand.code + 1)); if constexpr (is_float) { - value = ir.PackFloat2x32(ir.CompositeConstruct(value_lo, value_hi)); + value = ir.PackDouble2x32(ir.CompositeConstruct(value_lo, value_hi)); } else { value = ir.PackUint2x32(ir.CompositeConstruct(value_lo, value_hi)); } @@ -444,10 +444,9 @@ void Translator::SetDst64(const InstOperand& operand, const IR::U64F64& value_ra value_untyped = ir.FPSaturate(value_raw); } } - const IR::U64 value = - is_float ? ir.BitCast(IR::F64{value_untyped}) : IR::U64{value_untyped}; - const IR::Value unpacked{ir.UnpackUint2x32(value)}; + const IR::Value unpacked{is_float ? ir.UnpackDouble2x32(IR::F64{value_untyped}) + : ir.UnpackUint2x32(IR::U64{value_untyped})}; const IR::U32 lo{ir.CompositeExtract(unpacked, 0U)}; const IR::U32 hi{ir.CompositeExtract(unpacked, 1U)}; switch (operand.field) { diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index e8836bb4c..e1ebf2206 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -84,16 +84,6 @@ IR::F16 IREmitter::BitCast(const IR::U16& value) { return Inst(Opcode::BitCastF16U16, value); } -template <> -IR::U64 IREmitter::BitCast(const IR::F64& value) { - return Inst(Opcode::BitCastU64F64, value); -} - -template <> -IR::F64 IREmitter::BitCast(const IR::U64& value) { - return Inst(Opcode::BitCastF64U64, value); -} - U1 IREmitter::ConditionRef(const U1& value) { return Inst(Opcode::ConditionRef, value); } @@ -841,8 +831,12 @@ Value IREmitter::UnpackUint2x32(const U64& value) { return Inst(Opcode::UnpackUint2x32, value); } -F64 IREmitter::PackFloat2x32(const Value& vector) { - return Inst(Opcode::PackFloat2x32, vector); +F64 IREmitter::PackDouble2x32(const Value& vector) { + return Inst(Opcode::PackDouble2x32, vector); +} + +Value IREmitter::UnpackDouble2x32(const F64& value) { + return Inst(Opcode::UnpackDouble2x32, value); } U32 IREmitter::Pack2x16(const AmdGpu::NumberFormat number_format, const Value& vector) { diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 186d83a07..d978b3b4f 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -181,7 +181,8 @@ public: [[nodiscard]] U64 PackUint2x32(const Value& vector); [[nodiscard]] Value UnpackUint2x32(const U64& value); - [[nodiscard]] F64 PackFloat2x32(const Value& vector); + [[nodiscard]] F64 PackDouble2x32(const Value& vector); + [[nodiscard]] Value UnpackDouble2x32(const F64& value); [[nodiscard]] U32 Pack2x16(AmdGpu::NumberFormat number_format, const Value& vector); [[nodiscard]] Value Unpack2x16(AmdGpu::NumberFormat number_format, const U32& value); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 4932ff9a0..6f186808c 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -191,14 +191,13 @@ OPCODE(SelectF64, F64, U1, // Bitwise conversions OPCODE(BitCastU16F16, U16, F16, ) OPCODE(BitCastU32F32, U32, F32, ) -OPCODE(BitCastU64F64, U64, F64, ) OPCODE(BitCastF16U16, F16, U16, ) OPCODE(BitCastF32U32, F32, U32, ) -OPCODE(BitCastF64U64, F64, U64, ) OPCODE(PackUint2x32, U64, U32x2, ) OPCODE(UnpackUint2x32, U32x2, U64, ) -OPCODE(PackFloat2x32, F64, F32x2, ) +OPCODE(PackDouble2x32, F64, U32x2, ) +OPCODE(UnpackDouble2x32, U32x2, F64, ) OPCODE(PackUnorm2x16, U32, F32x2, ) OPCODE(UnpackUnorm2x16, F32x2, U32, ) diff --git a/src/shader_recompiler/ir/passes/ir_passes.h b/src/shader_recompiler/ir/passes/ir_passes.h index 760dbb112..06e4ac850 100644 --- a/src/shader_recompiler/ir/passes/ir_passes.h +++ b/src/shader_recompiler/ir/passes/ir_passes.h @@ -21,6 +21,7 @@ void ReadLaneEliminationPass(IR::Program& program); void ResourceTrackingPass(IR::Program& program); void CollectShaderInfoPass(IR::Program& program); void LowerBufferFormatToRaw(IR::Program& program); +void LowerFp64ToFp32(IR::Program& program); void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info); void TessellationPreprocess(IR::Program& program, RuntimeInfo& runtime_info); void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info); diff --git a/src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp b/src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp new file mode 100644 index 000000000..3c30e75b4 --- /dev/null +++ b/src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp @@ -0,0 +1,186 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "shader_recompiler/info.h" +#include "shader_recompiler/ir/basic_block.h" +#include "shader_recompiler/ir/ir_emitter.h" +#include "shader_recompiler/ir/program.h" + +namespace Shader::Optimization { + +constexpr s32 F64ToF32Exp = +1023 - 127; +constexpr s32 F32ToF64Exp = +127 - 1023; + +static IR::F32 PackedF64ToF32(IR::IREmitter& ir, const IR::Value& packed) { + const IR::U32 lo{ir.CompositeExtract(packed, 0)}; + const IR::U32 hi{ir.CompositeExtract(packed, 1)}; + const IR::U32 sign{ir.BitFieldExtract(hi, ir.Imm32(31), ir.Imm32(1))}; + const IR::U32 exp{ir.BitFieldExtract(hi, ir.Imm32(20), ir.Imm32(11))}; + const IR::U32 mantissa_hi{ir.BitFieldExtract(hi, ir.Imm32(0), ir.Imm32(20))}; + const IR::U32 mantissa_lo{ir.BitFieldExtract(lo, ir.Imm32(29), ir.Imm32(3))}; + const IR::U32 mantissa{ + ir.BitwiseOr(ir.ShiftLeftLogical(mantissa_hi, ir.Imm32(3)), mantissa_lo)}; + const IR::U32 exp_if_subnorm{ + ir.Select(ir.IEqual(exp, ir.Imm32(0)), ir.Imm32(0), ir.IAdd(exp, ir.Imm32(F64ToF32Exp)))}; + const IR::U32 exp_if_infnan{ + ir.Select(ir.IEqual(exp, ir.Imm32(0x7ff)), ir.Imm32(0xff), exp_if_subnorm)}; + const IR::U32 result{ + ir.BitwiseOr(ir.ShiftLeftLogical(sign, ir.Imm32(31)), + ir.BitwiseOr(ir.ShiftLeftLogical(exp_if_infnan, ir.Imm32(23)), mantissa))}; + return ir.BitCast(result); +} + +IR::Value F32ToPackedF64(IR::IREmitter& ir, const IR::Value& raw) { + const IR::U32 value{ir.BitCast(IR::F32(raw))}; + const IR::U32 sign{ir.BitFieldExtract(value, ir.Imm32(31), ir.Imm32(1))}; + const IR::U32 exp{ir.BitFieldExtract(value, ir.Imm32(23), ir.Imm32(8))}; + const IR::U32 mantissa{ir.BitFieldExtract(value, ir.Imm32(0), ir.Imm32(23))}; + const IR::U32 mantissa_hi{ir.BitFieldExtract(mantissa, ir.Imm32(3), ir.Imm32(20))}; + const IR::U32 mantissa_lo{ir.BitFieldExtract(mantissa, ir.Imm32(0), ir.Imm32(3))}; + const IR::U32 exp_if_subnorm{ + ir.Select(ir.IEqual(exp, ir.Imm32(0)), ir.Imm32(0), ir.IAdd(exp, ir.Imm32(F32ToF64Exp)))}; + const IR::U32 exp_if_infnan{ + ir.Select(ir.IEqual(exp, ir.Imm32(0xff)), ir.Imm32(0x7ff), exp_if_subnorm)}; + const IR::U32 lo{ir.ShiftLeftLogical(mantissa_lo, ir.Imm32(29))}; + const IR::U32 hi{ + ir.BitwiseOr(ir.ShiftLeftLogical(sign, ir.Imm32(31)), + ir.BitwiseOr(ir.ShiftLeftLogical(exp_if_infnan, ir.Imm32(20)), mantissa_hi))}; + return ir.CompositeConstruct(lo, hi); +} + +static IR::Opcode Replace(IR::Opcode op) { + switch (op) { + case IR::Opcode::CompositeConstructF64x2: + return IR::Opcode::CompositeConstructF32x2; + case IR::Opcode::CompositeConstructF64x3: + return IR::Opcode::CompositeConstructF32x3; + case IR::Opcode::CompositeConstructF64x4: + return IR::Opcode::CompositeConstructF32x4; + case IR::Opcode::CompositeExtractF64x2: + return IR::Opcode::CompositeExtractF32x2; + case IR::Opcode::CompositeExtractF64x3: + return IR::Opcode::CompositeExtractF32x3; + case IR::Opcode::CompositeExtractF64x4: + return IR::Opcode::CompositeExtractF32x4; + case IR::Opcode::CompositeInsertF64x2: + return IR::Opcode::CompositeInsertF32x2; + case IR::Opcode::CompositeInsertF64x3: + return IR::Opcode::CompositeInsertF32x3; + case IR::Opcode::CompositeInsertF64x4: + return IR::Opcode::CompositeInsertF32x4; + case IR::Opcode::CompositeShuffleF64x2: + return IR::Opcode::CompositeShuffleF32x2; + case IR::Opcode::CompositeShuffleF64x3: + return IR::Opcode::CompositeShuffleF32x3; + case IR::Opcode::CompositeShuffleF64x4: + return IR::Opcode::CompositeShuffleF32x4; + case IR::Opcode::SelectF64: + return IR::Opcode::SelectF64; + case IR::Opcode::FPAbs64: + return IR::Opcode::FPAbs32; + case IR::Opcode::FPAdd64: + return IR::Opcode::FPAdd32; + case IR::Opcode::FPFma64: + return IR::Opcode::FPFma32; + case IR::Opcode::FPMax64: + return IR::Opcode::FPMax32; + case IR::Opcode::FPMin64: + return IR::Opcode::FPMin32; + case IR::Opcode::FPMul64: + return IR::Opcode::FPMul32; + case IR::Opcode::FPDiv64: + return IR::Opcode::FPDiv32; + case IR::Opcode::FPNeg64: + return IR::Opcode::FPNeg32; + case IR::Opcode::FPRecip64: + return IR::Opcode::FPRecip32; + case IR::Opcode::FPRecipSqrt64: + return IR::Opcode::FPRecipSqrt32; + case IR::Opcode::FPSaturate64: + return IR::Opcode::FPSaturate32; + case IR::Opcode::FPClamp64: + return IR::Opcode::FPClamp32; + case IR::Opcode::FPRoundEven64: + return IR::Opcode::FPRoundEven32; + case IR::Opcode::FPFloor64: + return IR::Opcode::FPFloor32; + case IR::Opcode::FPCeil64: + return IR::Opcode::FPCeil32; + case IR::Opcode::FPTrunc64: + return IR::Opcode::FPTrunc32; + case IR::Opcode::FPFract64: + return IR::Opcode::FPFract32; + case IR::Opcode::FPFrexpSig64: + return IR::Opcode::FPFrexpSig32; + case IR::Opcode::FPFrexpExp64: + return IR::Opcode::FPFrexpExp32; + case IR::Opcode::FPOrdEqual64: + return IR::Opcode::FPOrdEqual32; + case IR::Opcode::FPUnordEqual64: + return IR::Opcode::FPUnordEqual32; + case IR::Opcode::FPOrdNotEqual64: + return IR::Opcode::FPOrdNotEqual32; + case IR::Opcode::FPUnordNotEqual64: + return IR::Opcode::FPUnordNotEqual32; + case IR::Opcode::FPOrdLessThan64: + return IR::Opcode::FPOrdLessThan32; + case IR::Opcode::FPUnordLessThan64: + return IR::Opcode::FPUnordLessThan32; + case IR::Opcode::FPOrdGreaterThan64: + return IR::Opcode::FPOrdGreaterThan32; + case IR::Opcode::FPUnordGreaterThan64: + return IR::Opcode::FPUnordGreaterThan32; + case IR::Opcode::FPOrdLessThanEqual64: + return IR::Opcode::FPOrdLessThanEqual32; + case IR::Opcode::FPUnordLessThanEqual64: + return IR::Opcode::FPUnordLessThanEqual32; + case IR::Opcode::FPOrdGreaterThanEqual64: + return IR::Opcode::FPOrdGreaterThanEqual32; + case IR::Opcode::FPUnordGreaterThanEqual64: + return IR::Opcode::FPUnordGreaterThanEqual32; + case IR::Opcode::FPIsNan64: + return IR::Opcode::FPIsNan32; + case IR::Opcode::FPIsInf64: + return IR::Opcode::FPIsInf32; + case IR::Opcode::ConvertS32F64: + return IR::Opcode::ConvertS32F32; + case IR::Opcode::ConvertF32F64: + return IR::Opcode::Identity; + case IR::Opcode::ConvertF64F32: + return IR::Opcode::Identity; + case IR::Opcode::ConvertF64S32: + return IR::Opcode::ConvertF32S32; + case IR::Opcode::ConvertF64U32: + return IR::Opcode::ConvertF32U32; + default: + return op; + } +} + +static void Lower(IR::Block& block, IR::Inst& inst) { + switch (inst.GetOpcode()) { + case IR::Opcode::PackDouble2x32: { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + inst.ReplaceUsesWith(PackedF64ToF32(ir, inst.Arg(0))); + break; + } + case IR::Opcode::UnpackDouble2x32: { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + inst.ReplaceUsesWith(F32ToPackedF64(ir, inst.Arg(0))); + break; + } + default: + inst.ReplaceOpcode(Replace(inst.GetOpcode())); + break; + } +} + +void LowerFp64ToFp32(IR::Program& program) { + for (IR::Block* const block : program.blocks) { + for (IR::Inst& inst : block->Instructions()) { + Lower(*block, inst); + } + } +} + +} // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp index 219378a6c..d739b2da5 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -44,7 +44,8 @@ void Visit(Info& info, const IR::Inst& inst) { case IR::Opcode::BitCastF16U16: info.uses_fp16 = true; break; - case IR::Opcode::BitCastU64F64: + case IR::Opcode::PackDouble2x32: + case IR::Opcode::UnpackDouble2x32: info.uses_fp64 = true; break; case IR::Opcode::ImageWrite: diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 3b2854d59..9aac6230a 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -15,6 +15,7 @@ struct Profile { bool support_int8{}; bool support_int16{}; bool support_int64{}; + bool support_float64{}; bool support_vertex_instance_id{}; bool support_float_controls{}; bool support_separate_denorm_behavior{}; diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index 5004e0beb..3e0bd98d2 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -60,6 +60,9 @@ IR::Program TranslateProgram(std::span code, Pools& pools, Info& info program.post_order_blocks = Shader::IR::PostOrder(program.syntax_list.front()); // Run optimization passes + if (!profile.support_float64) { + Shader::Optimization::LowerFp64ToFp32(program); + } Shader::Optimization::SsaRewritePass(program.post_order_blocks); Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); Shader::Optimization::IdentityRemovalPass(program.blocks); diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index b3f3e60b6..bf9af1f24 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -89,6 +89,11 @@ public: return features.depthBounds; } + /// Returns true if 64-bit floats are supported in shaders + bool IsShaderFloat64Supported() const { + return features.shaderFloat64; + } + /// Returns true when VK_EXT_custom_border_color is supported bool IsCustomBorderColorSupported() const { return custom_border_color; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index efb1966ba..0b991cda0 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -196,6 +196,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, profile = Shader::Profile{ .supported_spirv = SpirvVersion1_6, .subgroup_size = instance.SubgroupSize(), + .support_float64 = instance.IsShaderFloat64Supported(), .support_fp32_denorm_preserve = bool(vk12_props.shaderDenormPreserveFloat32), .support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32), .support_fp32_round_to_zero = bool(vk12_props.shaderRoundingModeRTZFloat32), From beb9c86749aa756abb0b16c5f29312d46bccd9af Mon Sep 17 00:00:00 2001 From: lcjh <120989324@qq.com> Date: Tue, 29 Apr 2025 02:27:25 +0800 Subject: [PATCH 192/194] Code Review: SuspiciousPriority (#2854) Priority of the '&&' operation is higher than that of the '||' operation.It's possible that parentheses should be used in the expression. --- src/common/memory_patcher.cpp | 3 ++- src/qt_gui/cheats_patches.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/memory_patcher.cpp b/src/common/memory_patcher.cpp index 2a8b26acb..bb2d23c45 100644 --- a/src/common/memory_patcher.cpp +++ b/src/common/memory_patcher.cpp @@ -169,7 +169,8 @@ void OnGameLoaded() { if (type == "mask_jump32") patchMask = MemoryPatcher::PatchMask::Mask_Jump32; - if (type == "mask" || type == "mask_jump32" && !maskOffsetStr.empty()) { + if ((type == "mask" || type == "mask_jump32") && + !maskOffsetStr.empty()) { maskOffsetValue = std::stoi(maskOffsetStr, 0, 10); } diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index 7239affd5..67f616f87 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -1387,7 +1387,7 @@ void CheatsPatches::applyPatch(const QString& patchName, bool enabled) { if (type == "mask_jump32") patchMask = MemoryPatcher::PatchMask::Mask_Jump32; - if (type == "mask" || type == "mask_jump32" && !maskOffsetStr.toStdString().empty()) { + if ((type == "mask" || type == "mask_jump32") && !maskOffsetStr.toStdString().empty()) { maskOffsetValue = std::stoi(maskOffsetStr.toStdString(), 0, 10); } From 5e3157a82c4b3c2fccb4a83ecb491925369b0083 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 29 Apr 2025 16:29:35 -0700 Subject: [PATCH 193/194] vk_rasterizer: Fix updating wrong color attachment when skipped by mask. (#2859) --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index b04b4a07e..4caa781b9 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -696,14 +696,19 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline, RenderState& state) { int cb_index = 0; - for (auto& [image_id, desc] : cb_descs) { + for (auto attach_idx = 0u; attach_idx < state.num_color_attachments; ++attach_idx) { + if (state.color_attachments[attach_idx].imageView == VK_NULL_HANDLE) { + continue; + } + + auto& [image_id, desc] = cb_descs[cb_index++]; if (auto& old_img = texture_cache.GetImage(image_id); old_img.binding.needs_rebind) { auto& view = texture_cache.FindRenderTarget(desc); ASSERT(view.image_id != image_id); image_id = bound_images.emplace_back(view.image_id); auto& image = texture_cache.GetImage(view.image_id); - state.color_attachments[cb_index].imageView = *view.image_view; - state.color_attachments[cb_index].imageLayout = image.last_state.layout; + state.color_attachments[attach_idx].imageView = *view.image_view; + state.color_attachments[attach_idx].imageLayout = image.last_state.layout; const auto mip = view.info.range.base.level; state.width = std::min(state.width, std::max(image.info.size.width >> mip, 1u)); @@ -722,8 +727,7 @@ void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline, RenderState& s desc.view_info.range); } image.usage.render_target = 1u; - state.color_attachments[cb_index].imageLayout = image.last_state.layout; - ++cb_index; + state.color_attachments[attach_idx].imageLayout = image.last_state.layout; } if (db_desc) { From fa9f58446f85d478689c6048eac0c65ef87635be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Wed, 30 Apr 2025 11:09:40 +0200 Subject: [PATCH 194/194] Implement sceKernelPwritev (#2865) --- src/core/devices/base_device.h | 4 ++++ src/core/libraries/kernel/file_system.cpp | 27 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/core/devices/base_device.h b/src/core/devices/base_device.h index 36614b8f4..0cbbd3a00 100644 --- a/src/core/devices/base_device.h +++ b/src/core/devices/base_device.h @@ -40,6 +40,10 @@ public: return ORBIS_KERNEL_ERROR_EBADF; } + virtual size_t pwritev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { + return ORBIS_KERNEL_ERROR_EBADF; + } + virtual s64 lseek(s64 offset, int whence) { return ORBIS_KERNEL_ERROR_EBADF; } diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index bc34dff98..bcfa15a62 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -877,7 +877,7 @@ s32 PS4_SYSV_ABI sceKernelGetdirentries(s32 fd, char* buf, s32 nbytes, s64* base return result; } -s64 PS4_SYSV_ABI posix_pwrite(s32 fd, void* buf, size_t nbytes, s64 offset) { +s64 PS4_SYSV_ABI posix_pwritev(s32 fd, const SceKernelIovec* iov, s32 iovcnt, s64 offset) { if (offset < 0) { *__Error() = POSIX_EINVAL; return -1; @@ -893,7 +893,7 @@ s64 PS4_SYSV_ABI posix_pwrite(s32 fd, void* buf, size_t nbytes, s64 offset) { std::scoped_lock lk{file->m_mutex}; if (file->type == Core::FileSys::FileType::Device) { - s64 result = file->device->pwrite(buf, nbytes, offset); + s64 result = file->device->pwritev(iov, iovcnt, offset); if (result < 0) { ErrSceToPosix(result); return -1; @@ -908,7 +908,16 @@ s64 PS4_SYSV_ABI posix_pwrite(s32 fd, void* buf, size_t nbytes, s64 offset) { *__Error() = POSIX_EIO; return -1; } - return file->f.WriteRaw(buf, nbytes); + size_t total_written = 0; + for (int i = 0; i < iovcnt; i++) { + total_written += file->f.WriteRaw(iov[i].iov_base, iov[i].iov_len); + } + return total_written; +} + +s64 PS4_SYSV_ABI posix_pwrite(s32 fd, void* buf, size_t nbytes, s64 offset) { + SceKernelIovec iovec{buf, nbytes}; + return posix_pwritev(fd, &iovec, 1, offset); } s64 PS4_SYSV_ABI sceKernelPwrite(s32 fd, void* buf, size_t nbytes, s64 offset) { @@ -920,6 +929,15 @@ s64 PS4_SYSV_ABI sceKernelPwrite(s32 fd, void* buf, size_t nbytes, s64 offset) { return result; } +s64 PS4_SYSV_ABI sceKernelPwritev(s32 fd, const SceKernelIovec* iov, s32 iovcnt, s64 offset) { + s64 result = posix_pwritev(fd, iov, iovcnt, offset); + if (result < 0) { + LOG_ERROR(Kernel_Fs, "error = {}", *__Error()); + return ErrnoToSceKernelError(*__Error()); + } + return result; +} + s32 PS4_SYSV_ABI posix_unlink(const char* path) { if (path == nullptr) { *__Error() = POSIX_EINVAL; @@ -1017,7 +1035,10 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("sfKygSjIbI8", "libkernel", 1, "libkernel", 1, 1, getdirentries); LIB_FUNCTION("taRWhTJFTgE", "libkernel", 1, "libkernel", 1, 1, sceKernelGetdirentries); LIB_FUNCTION("C2kJ-byS5rM", "libkernel", 1, "libkernel", 1, 1, posix_pwrite); + LIB_FUNCTION("FCcmRZhWtOk", "libScePosix", 1, "libkernel", 1, 1, posix_pwritev); + LIB_FUNCTION("FCcmRZhWtOk", "libkernel", 1, "libkernel", 1, 1, posix_pwritev); LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPwrite); + LIB_FUNCTION("mBd4AfLP+u8", "libkernel", 1, "libkernel", 1, 1, sceKernelPwritev); LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", 1, 1, sceKernelUnlink); }

DZ>IxByd|!@9y1 zyT~s8ZSZj$k&iUZjkIgxEI7LVO&XCr6bwDSYb3V|Zy>~3(v^&V^eS@WKm`_lwy3n973WMu5f|;cO_a6DdMyk z%1U{y9n4ufDpt|;bGznDt6YKDa1meRQL0vxh?f|pXjX-LWrV~pav5a_TZSdIhBW7Q z0yZM5ROK+6Oo|%+J^eWWJIOor(TY-3c9A1gD6~@!#lq%WJ6FPHU^E6^C_Bg=vyhe=D3O1QeCY`$vs65VrQEAMn`{<2cO1v}Fm8vqgUg z<_eO^|9f2GldRDCE~FOS$+s|Iof9m$%WQ_qqkXTc(CRKMXR;9`o$)y)3K-AdDg^>z zQ}z?%%)_zy4Bd%aW;CHPPl*MX_Eb#~-AB|5!&LlQHWIAEk(I zS;b#}?#>Yu@AR|R;p4z0LrPKQx}P;;od+MviTEJ`&5(AsFctsfk}h^#pJrc(l0yFM zbylXW0JyqWZ|%4><2wyH&b%%Xw4J27BVe^paAW>ruEBLUa*^?e9W_FkXZa|2`@8y> z59H$i0I@hD!LcdGfC650x_Xx!<{oBNl+MrjTkOuf9GfVzMVVg((viQ|yPyehmD3qO zHG@%xj3-{L1OoXhR}E>R9K9N^%)Dg@0NErTcP(Y1Yigf|;+!ps4B({u3m|`y6p?sT zTI}}=tqA)A+LZLU9Ly%mJ9Rqn%Y8?hX!=cZcp_VNBal&t4rT9OA1-GRx+vP5nql1J zsjx~i;w2<(Gi7sT8f4~RJ`Erf^BqJr)mLhBm=yQ&5{EI8K6WbW+-ht@3guHdb%=Vn zFcoN7oHI=QPW^(y_$HLGhCTFyT+zodX~bsQhs~M&FvCUAFrs`hyFXV?edI07%Oqoz zFIlq8v0J8+A3*BNh_{XZG7GV#WBo)E(?!A=31*zTFpfwwkQpD*JQA%Ft#wceh{0BA z6x+0Q9=Jxl6yRvhxDGFkkHbp~FBoewre-22u<=1~Wt%lq9q0_u^>xV3IpzY#O>R&$ z%2Nq0snvFpGR=*8Q92DEm&p;JR$g&k@&luT|A9PZXvJ(JX<3u-^rI@`HOxZMmoAx7 zil)<;7MYnC;;A;$X|J{{l;rC!Vr|Kf0A-5DuOqYzq(?_gN5^*{Hkv1}j@#;|M^I(5 z5evc`WZL(7XWpPG%AZHUY(10fyD(o`;n+7hQ84L+n7h&3bb8or+`;y5ub<96380iR zeaB29OXP4;Mw%(;Tv=O6v_7^lU>1-_7wRQrsFEb+o@yK_;C3cqR150tm4*o`3SPeF zDbmS!2cr=E;NVUm*ns{d+!<8{GEu}c#hR5=$&n6t&phFx&PI!pWl=~Bi&#~}PBBz4 z>1ptS-t~sOBv09IZ3n%_$A3OTB^87)D5`RyJB`7JFD4SFwigA1Q3P|J;qtd31Zagv zWU*1y@=X9hD|m9`5<{IQO8xY!*AZXY)rv;P4H=@%-n9i^vNMT)E*{0nrcCJ2V%e5F zNF9Q_Ou0Gn=c!X!pihiW*4#%M7q80>xc$s?# z3f9zSTB{j>cfFCPO@l6PjbJ&!c;V$o_al}J)7ysx^V(5@`}keNR%5oKOp*^;WjKUA z6P=Q51T<*qN{Joa(nQI|_CPv1k}60&Pk+a6^@Pg$Bpv3d{W7b!$ZVvB0CqXlRz3akAJ1^t zG0!poTBU%R!On*k-$Y$Uzb{TYX#5w4g!XBoysUOE=vv#tmVmtgD|KJMAKQ&}V*91S z3v$(Q-%VvbMHLl~^bob4e%wQ5b>YiEu<-zIm30YEu>3?eF@qrelCEg*TJX&J9`1xK zy4@mSxRUKsXhUgX>tbU`17?AO_gC{Kn1f8xOrFV)M z+8C0_)#RarI{}%^0p{i{9IBy3LO+cbIWcdemFUPsuEGWj%1E26Ak1~-)2Jw*BiYhY zMOu+Kh2nAbNpti=Y8zE#EEN#c8H*WhyheXK;VI)qhU3xU=(^oTB-8q>sV9zbz{Wy5 zZfZwqJoPU6Lg}hA^Pusfy&}4^vz^J3X*bz1*$6~0x|DfPh!4rBgdMZjx2NN1e?RsA zN5`jIcTi(@8+}Kg<-6>Zl8CTJ1+E9B+4t=1+kg!31CGeuv_S6h)|;52 z2M#8aU8!8O9Vk(3dRic)?FDyDNgo$MBBEWW9`yANepvrZU*9>+z(Dq(anjeDlw(Hq zA*z9URD%;6mdY4IhXA5HujSmPIk$fLfr7o!1_a@`J19UMQx|ECy0dUUelK}@%zuJl zy*K-0BNrQ4Z45|3kz5%HwVqpBC?~|H!3lQ<9ih!i)Jj4)zk~TBc4B*kBH=M&J@pai zi$v0q?&a3LH8qT|lWkC-Ug57z-P|fieF$-2R~t`#>?dA=0K%4h7JP9f1d<@ojU18} zcTs;DyYb5bIowZZt)o)gXM+vhMOH-8tEPGDXGTWdV67~Nm((DGE}lBQy?$YK-xe&| zEZsz&k^ARn5C8<5eWJ!wUM zg#Lu}Ps7Fi(aH=m=-Uw=lBp-?B$kQKA;``Zpd`NOBB+SI+2M&mD0Z6fF~URbx-z^v z`w!)XOs;421M1y_?wGuf>Rc;IJg9}m%lk|=3>&*~X8MT~7R1Zl*bRpnwwWU5=6=Ja3D z;Z6_I?0JcC#a$4+j3ZO}XUXhI1^}d)j|*9m7FJk=Sf5|z_kX5}G8Vgv@KY5~5XaAN z^WT^1i?WY`Wr%)H^`L(#B+RkJOlL)rS#NY<^b*x<3~s35L?W$%3b|42SMRa-xh6YTyJ7C0?aS;Hd>pOlX_Bp|bW|H?BFya%pjm{N`MOnJh zt(3WaQYaquYVr>`oEM(`PLzM~O@UrMiYKB-O zHl+Q+<+a0;b`kfm+y6rdt3{boz`Cii!RIL*RKNcK!A^6BW9!cG2_V`kvE;AOOmq^; z8w4}l!fw&OwFsnf(d6jZq*FzBhAP6jsy@KgPq!l3lT?{d)=Q-_f5hZyz|AwcxmXP$ zIv;s{aE4nhE|Ee1^fWOw+pnPEAf+f(i!GGd$qyb}b$@?F@9#(-ot5#{aZ{1VlK_2$ z9ofjcJ69WpUOYMxi3lJA*Vv;=>2e^?x`@%k4XeXT5iU^$T^kx`dWI(w3)mq7?g2sI z?d+<8e>&XS z=QJPMq&yzqOFc;rZrqyCnlq7!xC*_or&zS*N?FOqTM`W8`{Cs z-6cn)0cGx~kw4$NR(9xInEUml>-@~KevyVUbGUG2Xm+DG`^pa$9!xehzt&Yb|1xCOyF1UIZt2r;aX$NuM(m_;ERIlCi>+xEvKs=T zhW;H2@%dD7G&zFn#EBDbF1n$!^?#k7cypFJJn&fl_2ulq(+&_j8<{lIxQ8w;&-W5{ z^#DSvi$9E>c)!d8(qb_~(R zjBEXnco^R3GFNffp9!&$!2q>W-Q=vStV-R&T$bM6-l?IN8fm7lU#{9^N3zy2KuIQG zGeo?Ws5` zODutFhuPoXLqEP&D}xN|+_mfSv1hzn7oB~4sxhZ>zem<9T*d3h62c^hb}su z2mTy>abd=+(zRZ5Jk{vb=PGx(##>*WoZRQt&cj_+_B9!T3=$@_q7xBk-kW6jh)z%M zcbPTkIQSHFF8m_>nRs6}-zutj?CF*JITzm?!okhm<&MghIx}jh09X4Co$4)O1+8G+ z=zn(ZtNL}l)l+VqCDRT}S*!<6fNAKs2RYK;{cJ67%IwQejWk5qL|EvA{2qVOrRUjG zV4~<+m-*wv{o_!73Gegfh*(=L4xH>%^XbL&RWHx>r#m>bo~s^e?Q?XS!8itA zl9t2h?PX&pd!5(tFIAOuNZ^*}981y932OAWb8vEMM-};1Jo{}8znm0SQ_AVXFE7jp z>0D6My||d?Sm}xi?z?yIiZ<^E?r|2n8j-+f7KQ9Vb<|6hEA+DvpZWhZLjKQ^??^+o zmEOO&e}5+bq(rU90_|Q#=i%k=?M!0Zat`_6yQD)+at zER@}Xe?4m3>+za}iCg@zukRjC5jE2eu{Uqu{`-@=^=`c5hORsx`&)7vRRnxF%agTe zhy)&~MXrbbZrs`|YS@T!OHWUq`*Je(k^iWnYv?FW{L(MgS+-QXJ<-yVYpgYSZ2X4R z#8hF*M5+;&lyq}M)=0!(mlq+kzgY{M!jMca4gth{zp7?paps-+I zOWk*{U4L_)8vyM*?pJ?84hj#`AiXDZBh+{0hVOlFyqasMJlOL=O-<$vE4|zOu9x1c z@F@r)QR+b0svm^KV1XHsFup`%P+;!%jtL4@2+ow! zu=FHouqGhu;&kca%-8<%4gI`=f*4TExPY-%;RKHp`+NHDSAs_)tAz0g`Egu*mbiV_ zc|fFieP(OT15{7Zts$#6aPs`t#@{t<1EN_#SXkKMEX}lhqszU|&mG^Hb|wE;?BC%_ zDPUhj;yivx(ZW4}p4Zr~=&dQY41TcEfB)ltt}oA8KA^)Xsy_Gan(~7F)*pR6aY>@Q zHD0a!xv|<)F-xIGi}Wo`z*# znyRPu0q|JK9XEE}$jR{)3Q)x6M6UYyzMN5?Wl1e!6j`sV`NjsMHE?y!iH#CFH}vAB z`niFXnyhapI8=s&xRQ?Nir3tuN<<4B%nyCB@J6BI?c04O2@V)i+*?+;&(7v zq$U5d_?xTz3ZEZ&ULG~I;pE)xcL~{{F%rJ7If1hfsQPz{N4YZkERu9SLYJcV=@@r@3Pr@0}Xm0 zAlZ?kzp2@{HZ)tuX+!bQTyYEgelrO~u$fzbFgKdj9y2sn#?J2x@u! zZd3nI69MnLT7k0Chpww#VSIeDOB@8hZEIs>E8ypY>SF-d{yTe5uz`Uow7qZ1waMxY z-VH2cR~y1-6&P(B&0F|6N>9!{*b#Fh$zcDeW8*5nmX;P%n(3A*=Q7Jwf2G99tN)D|q{!3y+!ePIy*rvTLF!zwR_dNrA4H{N~f_;%7t2-1(^OJy?04J>T>8EGeHrp6s3jsuRUVIZ2^CM}9 zMD||cIRpOuhOC9ha9!BpZ0){sXNt`y6G4~PKPwwM!2`eFc1Ub==$w=Tpa4PcNuDxR z7y3!fF8!?o)k7CVvvoo) zxZTB%>Ood!JJB zN%5RZzqKs2zVm6UnD2dmXaAo)NZ!Z*y-r(l;usd zZj6k0{P=N+TcaL0@jR1j0#AL1?mT@OwG~>qKDlw^$dUO6fq$|?hV%3C$Pm+Cm>qr( z-l*aE)vH%O*9Pv2kB>+A`1nE*+rg3}g*9Mvv{t7IOI=<*kH*(Rl;r>E=t<$Y({Grm z9aR0~S6CWhkk#3jqs#~5wA~QE;1v|!s@PD$V27^FRM&pLbv$&TV;3iW98GIJS4p8j zfjbSF>W*%RSfkv&O}uzf+sDVp%gZYj{*kR644`jTtDCG%NN#=KdbqAiPo*m`b!#U?ov^aw6JgxW0E+mVb;viR=*0*&QsIeK2oN`Ib%c zC7{06Y~7!-@cmY!cGlKZh%q{z^}D{kXK|MUZvaM7MC}v-LLA^?!O58%Q1Bq zW(HvE`hf-0>5rv<9^~ul>JDuj*ex$F4=L9Aaonpz5)rS_KcJKHN=n6$cL6J0BmbR% z@cAu(`48f2C3fC-OZ{$LEAwMu!;|%&*t}bw`RdbGb~9wF-3?hJWW40Mad~QbtZyq-ggN*^M3kMi41&s0*(|wdNV8<>p#t>~Z1P89(#o z$z_mA)!=D6*YKXZH@J*}eCGz9=3n~zJIntSD-aHHNUf>7*s1RPX2+C)@Gz5+^32gc z-wT=CZZSf*>+$`~O%PEuii6lb^xyZ4qmaerhA$-InlO?2L!o2I5Sz4q-LOUTnvjtQ z4M*3oV)v4JE-dRE5Ed`n+tOsv;NeJQ?Xi~^9yB_!UeOa!iC7JKSigMCFJkzzR3pUe zSwX$Tj}MM#TwI#&0h4k^&ByTAu>r@fVIA8298}caydh7y;1nDmqDe&|h#Uly=@67I zlt+1M&26CWQr0rd)YWy^F{DNAS?#tw-q&n2Mw%PAw<={{Y@`o|86Qf-j$OGy41|bt zp{S?iJ}`%X*A0Bby*l%?dUElD)qQYnA4_d711~;++)Z)6c&g^t)gOrpI!OK1t8AdV zPLDirkYe61DvE2|+L%Q%r~PL>6$PoH-oy9B583Ow%bzP}0;TE~d(E>#za=u|8_yl5 z=_fZ~Nq!d@lo`Gp%r{m&H=rhKafk22P(8n;iHXU(yBaBw;oX8%62H{H`u*NTG)eUv zY4AF6q6fmq%3=AOoSfhMrY(aREeI8yK-*+)lqFCdO=U)3#X}yt^T)nS?cm*D+6zF8 zb9hz-G_ZEqFPviBbv}@%>5oT6KS?%798cVpLjq^Qls~&v8MSe3?f1(RQQsOV`G%aj zx&s&g{(cYHg@uEIK(eUDJ;>6yR?SvCNZ{=ik3UZWoBr5$NPBa+x)B5|m#?`S1hHl` zL3HZNllvikQTj~8ZT(L|CdDY$0KL}4-VD;^|nLZ1t#ps?8PG`Py+~mwr#Yx*!;`$bD|QF zD^e-C%R;jaYrdzJ=O7pc3w;xkgBOCy%VkwnsS1^K6hK}$fw$#0^)Q6|D|;#t2Ef`I z$^&BO+ohQDBOXI7XLGf`ap9-imi8!95;&aIrJrq9kqg^J_MbMs%(Vlp3XvU`Ztg9n zX0*_WeM{-MQ~kyL-1_S(Z!?swYIJZAIJDV2x|mFMW)CUau0d8O?)mHUBVhLYZvQM* zCnWsC391Vy68P#E`N(><#DJbMWaoq8KJQ@L`Cl4efBSCeV>>(4^i3q0iXM6*)z|fnQnDp zo)Oa3y(}`6x6v;_)M)1K6V;6_i^lW5pBrrs1D6}YKrZtFHlhiD8${$#g^|Cv^$^8= z@ketU>vlBHhX|(#%r)1V4ZnFbsNSbaw=Tf+=<~C}+`5a_&gyGf2g3H+-aDKRIlMTF zeFs{WphgedmB%e$dUWdbA0a#|1Nt&m`Wrv2fS)~I3xf&6QCktdwciCc*C+KIzmD9# zd-p8_^g`-qg&-WvhY-M*L$>8JB$};YWzfPDs+mxndhooB>3m>d?VnB^VeO!j;wN0~ z*LUc&zkk1EftaZ^2y|l+a^V%%jSxpsnIhCTmt)^67t=oj*;6 za#{D2lU5FuuIGfz@H&aZxVh^%!s)erDosM^B{^Xe!jX%G62ft15TKl?8N05t{K6>E z)m0p1`~YyH0IVeh+qd7nJIs*I`xz9rb8WV<>+y-Vp(~@8Ve>t=FVmfjj5Z66q1TX( z{@zYEf!dU5eoPSj>tvvt8gc9kBiWH2X_>)aY4@oWc0Mhp~%DgU%RltHqz0&6Dj zoIwo|Peei^l%tX)^mD*1%g1K+F{jLYEI2N3Nz*MJ1;U4M% z(Lb@NHUw{PS(g_<`M~6a{F*F3n;31SOq_&8i?ZI$YW}U#$%a=dS+#48(8|0Eo>V9% z3#Wwew#rbEu1hj6hU+iAeES0SvOyd~VtBuhNwQB7Qw*og5=kK(EQm1#!JN=Vh&QXh zxHQ;on)sVSQim_18HPIkxa6-eEiFUIn$HlTA*eyUmP~KgPKyytus!#py`_AMZJS1S z8lLE`gg{L0%VQRl!MLvgM`m^^3j<1OFv3Y~VPWB?ej~$@=~Fhpu>rgJk(uagx^4Vr zTRXoGI6~!ouH(&{0(wfWQlpv4uY^$MsOpRB4w+BSe(B|b+UHbJad8^tFg$Fr2}RxU z-|Xa(7h`=<@ku(9W|SgpZV_Maia9tc3`eKPt9$mxU#0J;ylrn+VT?C9XEPoXu$vEw zAa~?^V7m)RB<0pEZysn?rH9{5GxR>MqH_$18#Kvz%LbAt0*!V`QTLn9PIX2t*m&Ki zN)7Guj86+e*H!)mx3cBWH*xr2cx(hOaw$}RI8RJy!DUawvma$iDIwo{Bvm^9IJ);0 zn+u-qzc6bCM6hJu5tJ$=m(+@TQiLP4hc3D03nUuXF%W?8n}3Bnj`m_DKV4_&T&}wb z&4kklmxSeCcXD}~v6E9X$F&ro7tmJ$wPmB!eZ@G!;T`N*WB+!e4T7?Qz?d&^wkd4M zjo9>c0;u)5HvdtUk`m%kD&hZdvJ?h|(2?IFh9F-R&tnL_g#Eyw6EH3aBS@$F%1*GH zZ+(hfNff6tc3H_>1Fvm_wvJAJiX(jjAy9GMfPoSUUh9l(7^Zt$=2VzG{7dF~Z?BD! z_1;XOcUb}ms|S|GB@M7Z*Vf@KK5X!SFdJDNFeJ9?qHh<)sj6n`Dj}yoGv?(_7SiG2 zMQbM<1|0=bM1MiTXLfRIz$Drjf$5;D^{h=Cr!m&C%#!_otl?U-0}%Zu7oKg`)#-c5AqRbp}BbJ4sxWoh__B>%g2s~IMk<%{nBI~;0Xf^;f9nq=(I}24QILmwW z_I?f=VHYWB@`g>%U~M2o=;|mXcT0rNMN7%`C9!%tvXdFqIs=KNr(3S_>oK7yPZp-= z?PV-3h|6`^UXQe6JLeDBlr4PGf`bQ#n@aeCEmfM2Az}pbg99n)W$J#zXjh5@wO0pC zOmGl)?`0`2aKNwNeyQ*wHfF~kdBoF^l2cUlf}|Y)JAtt_X~V=Gh)SPkV`HP!>)_F& z!hVe#&kf}4IoZ6ALP8MqeDXS<60&>uZu;GZaJQ(2uNq77 z3JP|{qTHy0!rv?@9Y+Yd_YC=b$TNC1M}xQS-^AVYj118@u($CL|8(=DPfs)Ndw`=x z@*xy5)XraL2f+~CDLw%Kkr*#bRaXL1f55G%1oAA99=b{5Yu+laY&gwh6LfEf2c{}S zf7v;$0hK&dLU>($d`c^kbl%YC7EiHxIF;5v#;Y&DH$!pE12t&*Xx9ori%l(PW!(1= z!pRlY)bC+CBZeRwID<+c?=ZxNoCd4aioCz}kc}0P&IPfwhI3Hs`ZZktRT7wP__ZP8 zrIab_fiYPdj0(%t&wo-8$Dtq!f#gF2V0s+tPocEPX}J2Lp{e2wW{wlgL?rcp@-I{I z`0-w-{CM)%V7p!<+91YYeI%A4ceJ&$^Fo1?g1q!9lpuxU7KdBgrit`~pEKv&I^POGz@tD>PI7Msc& zzWfYHH5`YOr>LUh9i0eaP%h^*c#M25mm=)yV(=M(7%&l14k2K!nNUkh3#1`kl124D z`QB0Gk0VJ7VyVkr;95#bN>o7X(|MrA81^l37l$ER6NmE+96^^<)aEvzkA~!M8nsZo zuP8F+gC$M_A=0r&HNUfw(~L(h%lYV|2@b?ubS#q zK1{^sZTGKhJFcw)`A+fEryo&jC4YYEvmFx6rv|3>f#E8myCN{OzNk_W^w`;<7k%hA zgJ4UlygGPeQw`Nc`;9RwgJX(2YTn<~__O@g4}IwBOGxh_mGtJZY4;ur6-0ESUByq9 zUOb$umkkYIRP6$uL5m0~5hr#z1;uA|>fZm!Lqgt8{ohW@(>Em}a$`C)B+-jkW#nM%T`- z2)6v;B7ZpW&}ON2WBIGFeq>!U#AsJvf|{I%9&c}X#@Z!w_d7So?2$zV?Mu$Z(*ez# z8vZ|uGi;!m*bLb2->Iyea#e$ENRA<;5ydAF@6vJSIAP<@-bx=HI+G%gGk2Xtf zb5}<8AlLtfn%QUExX}!i!}gv-5*Ejf-HAr61O@uckV46WA%>hA4!u7_Re&SDOy_lP z+4K!TDITpf@7Mx^h(^Z(kS}{f5hWa|TTDA=8qdS|ZC!c@ ztqCZ^aKgl@OkH2O<30%g7ykU=G#ZG?Ci6d9ee1a)K+YG}FP-kxTRSUzIFmM~3|?mJ ziu83F)X$gSBR1I1?ne!|D~*^C#T#X43lwVVx@f4W0z!H!{GT`3h)XLDZAlS|HgbEm zL|U{^jR!))sJYQQ+`*_~E&r|% z*Je9)X2+pe0v`^c*UR;l*C2^V^&kdQB`OMQtm#Tg$(hw`Z*Dxv^w0Mt_DRmpOFS#c+cba zU@dxJ2Th5#QN@R3VCn&)hL@=5LO$8GVNNM3x{;RF1bvoPh&v#|e->?e=`U{1Ie4r1 z(H%9WOLavNA@ePQ`!4))%3Xu7kyw7>?X7hv9>^+pRmYj{-*4MifJ||U{hp^RTR`vo zHed_Adz6xtV^D^x`TgQ2hvC4>=GZSUjP?_Agbx{Q z>GHPWe1xkXl3cP=kVU4*-}7k=BN;C@FZ~1yVzYRJ4E@C;c6P7nLbv*y33^h8`sOq2 zCwCwXt5yToem;D89i~xQ!R%77K;o>Z&3_I2gsaUJF4;Khytv?xHK=%Bfi$0fW3NW| zKRI&I=gxt?B^Cl>=E0>#dx9AoOq|^HYq+f!v6>3$k|c!UH;eAy|4?SH03lc_l%zNz z_V`52rsgngx|ZLwI>nRvBOtBeYfZqI=KSmJL@O%|5VE3K|84{3d9YMTbTihlXJ2M5 zw^K0uKtzKP#BViF`5PK?fKgJ3NWX<6i2Q|p)i7-Anc;Fl7;Ik+L>%}T6Uc$341vsebE&L|__<51>Nl3CF!k{bA zTWzv)+n%~P!(&fRxeEavYx_tXV(qn)IcIom_IC|~vrJ0bp>qq05}ht!jws8v&_h}n z@9<@Tp;psGW#~b%@2R0}JqekdKjLtCv#s%H`$1V&^8^`pq9g|UnnwVG38?*EU|(K+ z?D$%_`Ed-tLiOKI!)MO*IVhiVQ7G?osM=$nk9~X)6HPF4GLmF8B;)aTsW`88^)Hc8 z_+#e`9v`OMo(vzZbljRMuax~0p1a+1WaY92jUn_8ehmm#=N2n-r zA7rMLqw@?dAghcR1nE8)85#Kk)OCNrH{(~x9gZ_Ou^~q>DmO17;r7nXS!k~gTH4#- zN3#h!956|NcGXz1{yk!K^*Acu0&~MObZmQ}M9G4M_p-wyB7*${1ovXsM4uL z%w8IiX36v68lH$i25b>$qh+f8yDTl+ecta$RFV zgtimC3Mwjx(5rTkfFm!UzQu~2D!`KV)-qV2J!^|xJr`G3R}`FX%&S+g7KdgdGwcD! ziErnW5GFGbgWrT3;5yc(VP44q?g`49A8P{*`Mr}R)7SVGG>7TPxf#MY5Jbbb)WQ3mJiusjU-9CWuApV#O&~4JPYh=Y~$g{yS?uuFXQ39 zGOdee*vJSQlKFcC`s@7S;+FI)@KRC+s82AUe#A=VNwbjyPJb=MkY$Vy9XbPZ91F`6 zU2qdMdvUtY99nMD22|tW(=CEa4{mo<{T4p>5~OH9IENVc$lvJ0eXEm&-kD`BflHtG z5YG;N*_TkHAD^AIqQ51#d$-vL6!C#>r{|sP_v5HS=i^IZE-WAH=XOS5(A|Zlr9Nni zS@EbGxi&cswHHGf)wSqHzIF2lzsy{qzv5YVDCLt<^`Ie-itm(0*B2KD1fME@_(b(r zRLh?H1DPY=W#{JZhd!P;de6i!rR*LMht?M}&%gd3uYEh;zNHUjeV0A_u|P{l0U;}7 z9teAUR$Ub3VJP{^6_%l(4;GA=4>dtJs{J_Yc9TPge%?9rPzoh2_cjrcG8kjo#(4Nc zfz~DS|42Li>A2sCfBFExWi&X*9VK~ScxYF#lCBiNuvf+~yz~Iez`zPtff|b%4)C}N z22{N3`@oP4K7<_L;pxdQ(KW}6AaiO;>Kyz2Zx$drI{J=^JJx`z#)lA&NBp@vVrOj3 zNQW~I+7Jsuh#WEi{FgnHc?zZz7yka;MF&!iM1>zkBKthl47m>$L<+H_DXTv*-E{s+9+v zVHj;c42Z;#i3gwGM}$lwRg#B%`ympgTNv>@YHD^cog&SW>1B6`jjXv4>x1$c;@)j2 zpK++oQooxlNxG`ZhUD?1h>M7rp;971s{VR2HI=}VhvXs1CjVRXy$b1_I}O1d?PRe= zobwSO*+iFa(kC}_XEFM=;_J6>uZ?B9Q>YWQ;pOb)ZC=bDAg?fDVCJGL0()Xg%A_VC z)64ic8+mk`=>v!GUSqJlsFsnWAf)KmXqe{&ToY^&#F6ymOAr*E=>pjj7Z+FcW;-9k zhfwujx3wu~YGz6Tl!{l9a||&4-z^Y~=i%FXO+cF^QL?gMO-i!!^1>TX86ONYs@T{F z)i&V|Z=xtD01BWWyI3+ME5a;CzF{SAy(977eSZ(yVA1qD*TYiy5f#(3pr}F%F+>j! zkN;C00rjv2FGI4btr#0Z+Z(zGGEQAh;1J$<_x(XSp~xMtnw!7>{Ao@np#U#$0krRU zfw!9-)^OEl9&lFF)t%DPbkeFcb%TBywX@6CZ;QKpnT{#u*99FZDx=Y{V;~G38frpg zW#tcTz?Gm*jEbNmN3gy5K5$Jz0102?E`*dLOBU=Ql_x(xUyPZ>mcxgL;jyq(VZDJ$ zP#|hCKviV{-xo4C0-shB9%>HKz<$K&^UQ^o*Zxz&N!g%6%+bj>RtM>uv$XL`Ziaj;9u1VvAVkVTRdW8mh9aNs2} zy`V4l-yW4pP>)HFXNq*eP}vZnX5Z>3>nQE5`4E-Y{SOhEfsl|iXcZ_ZU6%6V;vgVl zWwJ&P%E1)z2QUppCvG(GQj-;gXq>_J^I3H?dO+168Vo!iD)%ZckSp-89RP$-55sNK zFu7zvo!%*rz|YA(dPIk&1a4CR4U{CE#dJIJ{>*nzT3HD-gd-~Hjg5^kZE49Cyb z`;#x@-@lun3PAIc%Y3!@9ZRk>a!&#tqQ6!LG5r=KCnTMPMOrtdXJ=+C&=0K%h?QY; z9X3xRjLHWE;sZZ%uu8N@xr}9+vcMqHIA-QQ74Z965IpgM&e!~=IxN|!$31Co4t_Ta z4&@=jCn$J`Ma4iF#6y1XUYq($Po~g2EJTeNz@(c1tLITEL=aDQviLX#{To?kY2`eo z`^Etho3gSp%0Q|hNC(r@KL8mlKtgr321*wKuNzmBklRrV&p~mTL>PLss;H`qos1CA zMzEsCG$CHgVnntxnz7B@;xiLanfdQCmBVr<5bz(suvEn=34haTo3_q$K;lv%2!{V| z34BvZG*YGmRHZJ8Ly|spcN0)hQ}O%N)p@Z=yOEd<`Zi+Deh<9UsW9|kAlmRB*}bf+ zj0MxmhYZobAtyTRV2cg;FJ~DoLk1{F);A5rk~dMkbsm6GcQsSkRluKG=F0 zTq?uiSK{agD+?!$E;k$Ss2CvsA9ngd>TVoaUyF*rdGlsjX{i*QnzHkMZ;c<~Qt5zk zT$pU7$1Y>rFl@)1L6jdXIb7yfd_+#lvPyV88ct?AE$6@ zk)6?^Yg5kgMOvfL#F%R&zu8gj6xNU&~w1}UJP z<>ch7;D`^v5g4?nHECS|%J7x-2R(!=B?33v%OydfxdWfk`7C0$?}C&3cfI&3Wx?l-w*rj)*1@Pdjp5 zla5YVULN80?c0o)8=CS62`!czqHqHFZ^Fb33$~RX$%+6lDBSXO8Y$Pq+k0co8~ZAjZXP08CJ6g7`G}_n;P? z4BaDJn<0keG#v8Nl~e?R^yY^b_hC3<(-WUKl6Fzyk!F7S#?)#&lk1 z0(#&ROI{uCH91c%4P6GkU$OO;m#cqI+zs*Nk$GduUSkx$P|nf z-9M#5tg{`Pq=bl#Bevhae=mLU;tm~_^Xn@ChU7#s;O?=bM{oa6%Bhlq%8yw@CIk?% zIIh4gtQZ#zEb@TWBTIpT*t>S}u^im}_ma(c zo>;c*yQwVL&TtR9$PV`WJ0Rd7vtAe1HDgif!4X&M5IGs{%(9Xa)BXGR(@_jaPELk@ zOTzn$Z*kPH1#-uQU!M(7K@xCucCLWo*cftRXH+dh0-JXW=GOk(I(DoqmYJKEF-dIG zZ4YAB&LCS}Q8A8(W5~7g@^agw!k6=!Ol2Sde*$4;NnRdV61Eh7!T27S2u2JW0xw+` z(|VPl0_y_RA+xTA2>iy4z;VnCet!NR>tV~UfO1u3tE(4XYN$UZRX%A!FlP>iAE@>M zO0UnNwjx0o$VDCtu86oWku|bE*==4*CmI{o%K}tUjlTvX0$vE}{jBQ>;`K34kg)%73L%L{pQXV0j zx4(Jg5r*=l1v*X%oQMGitU-8RXSNRAd@9nH<|=UnOf5AC#yG%P0a{WZw!8OCd<+zN z0P0nf1cgGnjWdBu4wjad0yW7|yOD(s+^GSX=U}wxVCR*UnO)|VfPhjl7d`dyqg}S{ z+gtED;^03=GXVPmw}9K27bOJe7U_zK=~p zQmGWl2?pbW1K(F=LosOaUurAB zy~MyADH-anJHR8e`OigdeO}5>kY9B1b~57RSU&iF`is&em-O z^i_bCRg+0kxoKFNIFFNuh+1Qn0enEVJg&DFp#77$R%g1Ysc9itPUP`hB&h7?{O)(K zep2RRD-2rVS1)RIpHr9oG2AkT(*Df78hpN+IE53!arbvrriwAc61xg8D8iUZJP9Yd zj8I&@JOgQ;+SBQ7t~3X6&y;XCn9CbEIsY=ifT@Y2gpwfRQkL;mdulHvS#&m97{+2Y zdeD?4tD$opx*>FqSAJjlvR~DjN|uvPd;alJ1PWoB$%B%W zM-P6Hp%rDNrq*?9VuIb1V0Lbzt}BDT)@4&B=H_&Pv{Sr>YqV+8`5ukSIA&k;_R%4? z^wd9=NPqT-pSxGeRq2_1-wK-!66I_pE9 zKL`HiQx92?hvAAdVJF0R>Ys3RN*<+6?+0XWzn9m)!c4&LKD!*JibNTv(#syz9n@z= znW0>_$3>hUgd;W}4Pwp@uv=~H(Q1Hy;k@)rLL2%rO&(lr>$YuovEImD z`L;_WVgtCA%LJ-;c@6nWX$eK1Eh^SKc91;AaNdjO&tJc|sD$VG=rCoPMA=+eG?-_T z>u?wt&%nytKHf-D&Ka#d%x6_DQ^{n zLEj_bU~g)A7F~bNPh#UPUc4E zlS`_8eW?Kgz=Z6hMeZnP_OSS|%>AGkegNBH=DmBmlw3zx86~8E8$jRPK>E z%H`|}0H99TlrEX zB9pg@_KPRi3!Oqvc!jIXd-3vRll1hc@o}n6d!MfdEk9K^TxsW>J!k%ZuWUpN3}j;M z>oDewuYI>D%Hi2ZKXN!l zSO;%_XZ?`=RlB@Kh{C#O&ISFc5sm*QBqC%J_4|E&IbRt}P8n)m?VFw3s6P6Rw7u~w z>@rBuiyydOeez^ej&r4xag@{&;i`?Cvg7!(IqsK{L4HFU+h+IfrLOp4;R7%%M}W3O z(f?s7sf6mWY#W6O>%puI#9riFEE5+)A7mXm)+cEp6{o+JmetH)es7*|sR~4+7f^EL zo46Kbf5vw@d~SQA?vBGpX~=QX4muqhv9`hDpbFRT-hCDD`D>p)?|Dc$=oTHTn2%UzpGmrx z!g1x;-d+!Xc@ozj)WA|6{(du0oTq-(#(z~~IS);3?wp|pGgU?KwPkP(m8D7A|xG+Udj5?nN+~qyw8b^y((ue zcOdN`78L@@gXm-{u3Q;oh;ewmVBtcLu2it1&#h!O|Fvv5-8_|guYI)Uh%n~;@Q0(u z3 zHfz;0Dfc7<5fy0lAjKd9{9jyB(oMwof3QcLHvQ{poIwkud}82m#JrM;Rs@PgbK7@Hr9Q=r%U|p3WT?tfrPb2u;WTOuIBgkrR86lptT$Uo*6JV;@ztPkO&mQ zK`VpPw34Zke4_ZzEG9%eFE%BOQ-yWcw`EI6N!>yn{HFpgnIDIkNDl<2khiqLEm`dB z_95yrq*XE;6PWekr?$u^8Wi1-IDW(eQ36uA%WE3vRU50II|~tk-C9 zxXNTwT$uaC>9=oNp(ERXFv{A>YMl(zb0U>}NHyCZ@rC_>V5j}LxylXb$zH>C$!IK2 z&(F6cT(YDE+_~G7=H*a+*0j!;!c*g1B_F`yONTv);=?QY@yom zZub@rf%d!D{qSTzCw1H5`R zxT^)IXi|P_Qw{zAk@08fok3^N789K=AKt3@3{exIFH=-*aqu4>B%+FF7Jc~efd|9) zZj-BPI_a7M&JwwjYI$*CxFdJWH8*8r$cssu&a1HLKVhE0GyZRfcFsjkI0EW}J$CI$ zT1kpocPny1et#_~z?~QO>);7yAcYG;;r*`rYuSgCZW=Y{|MQ$u{IK)8rDm|xM{FB59#*DQOcPZ+R{jzEriI89hNbcG znb~h8u%H9Wu>3OFYtbR3MT7cmZl^tLc5mqp2>CJ?R!5GMJc(MQ zBrPpX=JU$e;hx4IkS&0|)_+Lpyy|5YmH*;D^w{Q8-xMv7l?ocDJ-9tdT;K*+9DuXd z1D%+xG*@a-&6fkcQ3}){vlpM7XjgXQ&TR(CyWrCg6e>0!feGt32cH?$SB{*DjHiNJeS5Thz@ z@b`?!A{`@1!|ov4t)s4uK6)3aZvXjgoJ@PwOeK+yB0yW5zolxi@1hoMfeoK8Qr zlKE=nI-7UV$fILAqt)&7V<&;^fNT=c3&IcWg`?*1Cig7KrWpH|vhOQ`o!k^Rx8YIEm-S zc*$v{MOr>Tqo8gc0%@8R4|v39E*EDNfXOlc1Pj!N3Z@K%cu6c%bj)C5#7> z)9@bEgg(eI5aVHBb5a>E{tR=;I5+gO#sUn&bN>LMsjzaTC0;oWSC@n&#WAl@{*s6j ztkAcwD<5aas<<%|qj-2a`HH;W$}$#c`XqC4agim$O@`#^6!qt)JY&)QakqVQC~H>e zGdMj&A`qo8UjhMrTmf0EXC?uaigfn zhM>&dY7@b=)a9bRd%BRNvv=OBS1YLH)w?O?PSY)0T0nZ1Sh(=!vu7sQ(^b2qL?WPs za|_A|*O8hUQhyp0Yo^zhXHNf9D9rG zSe(UL)^q07*k1iBA^x*iS^m+qKP1k_F4*5$6@K>2nF}c9r(^aW;9*Bnr~jtfmjx=i zQUg8FtRmaHlpmQr;gURfY+&j)!n%SDCh-*G(3wt#29M_fh&RR|FV*x#@qSv2YLA;n z4iD9th58RvIoL2D;7~D=F%ZlMC?w<%tBj0gHecuct#rNjmn-YDXQ(u&^aKrPbWXuL zUIhhSY0VlNAl1p}y-R-du^s_?ZtCs_LQv=?&+5I-E)GM=~$l#erPNR*tTtj2z z+%ecfW-z?#Yt^v7W+cASIXobR-~X^;RO9z&>5fW@XUA~{VA#h1t3(g38KkRLQtj4^ zO-#HoJ(f4!fVAgrkMyAMPC31fx0OxV=Tz2gZ`eGb-KhdZi!2#Oq=r>3S( zav_;)D?gkU`nw?2qo*O0loQ3O3MR}Oq{~0e8xQ=rahy?hC0O)G7Lry}OUwZ-#H_^y z{qAER4hqbF@xl~&w<+Q6nN88^M#5MPWuN8+AN;uSa6+K{R@DK6&bQ*TXOFy8uX86L zi;)pJCPK$BH1s}L?RlVwZWD<(VMx_&m-UEYtJs~d&-zYLGMsM&lr>&dqW05gUR)io zMja_40}TQ;ionNn%f2r^_~XMBOZd_Yr%&_I>GYlp#*1Gg&UmIW&D@?+JqM@W@D9!^7jE zW|6%O&Y%$-zbx!}B6_2*ug}AcoF!T3plRjbTHj%4HaMW)!oy`-$H!1TIC9P}VarDv z%V+Oqs`+ASYF=?i=uQkIHnJ5c!{`keZrpeZputS(iB5idum;yoa5rl>H%#S*&T{e* zKW*N?V7wHdxY6O(8}BP913Zerbz~@NDVWj5aGNb4R|;_F7l|{hSW=FY>)-9=Wo~P5 zVRX%3$Q%K(+a11|tUfgB=ggn~d^h&oZc-Ojy*GHAxJpu05^yq{= z_P2_Lw}3#^$~8&mU{$b*sKsx7in29D$sQPJjj*W@y#Fi#mv{+A?7J`3CGCmrVhnZ3 zrOczq+E^^s@Xx2xH=Qd(&qhYZR8|^1K#9)=Q3n9ABcSl_2O73-K=e{i^;hvK<&t(G z<`nCW9PcU%s(_e$Xx~g<1)J06pY0#Kb|wt3@!?3b)A@X;VB5TK%wiN5$vke0n91Sr4pGhseHx#R+qgAoQzK=dAcCB3rU zIdQsc%{%|%ocXLZD{ZKdx7?z-X{ZT!!p!{Je#~}vFR$$&9NFBmP47+#wm^O2By{+v z92+(XF_qet;R^i(5z#gxBhH((6vFo~O(ecLd+yv_u#+dhYF2)nA4WOHFZVcJUZHT< zam&ILYq)U|3JUi3j@(|Zqy*Siu2V(OPj>_*jq+1ymDq!3130xl*)hwNmFbd{^Y7#D z+=nXbN_&Nv_gPVz^JtWx9V!ORKnLDLz`@JIBPM`Yu&Jd*NmFwN2vl#*sosC4IYp^- zXRsw1sLR{?TxavoUrX74x<5Y92&COr6s>N6fKOHg!fvysH*Zia*a>B{2GF-)24vUM z1`j&LuC7_+d}(di`ujEwHcv#D&7;L(LD z8(>odXS(quSDHqVY1u1B=j8HvlmTnl_({w@i-D^d84|N+FAWO|1A=fl&@+O9f zf6YgF8jXQ-!5>-0#l>b}J7BBf(b4m~3Q7pXJIsuu7n6L^PJ@}J5&YZhJp@L|#=hU& zTk!UXE~fkY_x)G2{MYxk_DSixkR2~Y}XbStDb z<)7|MMb)E_0N@Zwf1q&Igf`Db7VF`wdnLCXKQ;pEe|H(^xWKhNzkyEM=3~by5ZM&@ zcI1-L1*_*ISJj-aW@FNaL&H;m5sQm^WgDQ1xW5TTW%#^r2mltp50-0=fG@mAFK9aq zM>DG0irjt-m;^9W-kjI`a1CgpGF4UR7{vExeop(f>bOmyL0nYn@K+iv?`PM)z!0| zuF21#3gnbsy$)W1P(}}mQiU&huAySE?^UuDvZ$$F36<_JH1GB}*Z^+4IOy9FGEUG^ z>_Z&ov%06QuC4{`*1I8p1K@)(sewgrS@Bz`5}O$IQWl@u{jyCcX%mecGbhl^!XY|x z6Y?Gfveb^aOP8*J0`39G?SfT%1ok3Yxq}CUh=C|80)MDf>2GEo=d~re*Bg3!)9BK-cGThgsmEH3r+o3@G1e;97;;I zQ#!rhzsIGdu-vZx(?u?qJn}6_A{>U9b%9R42O+PJc5-Q zU`i+=&HJ+#%7&ZFpEjqaQQx%k7-MR?bB1ypO1HwHm#ekxM0*BG6Z=7JZ!7U%%1z~n zFItqtzxaqDN=Pjz7XXfU1Jik&##FlZ$7aV4B|3d0nrA@}Kxlm?%3M2smCdi$!yCMd z=662Pg2zxk7-h3z)YcdNIqyb15GtgkYPR%b{W2^>C=(@-eSPVYWc8CL*h-hWcxjS( z>FMiG@z>E3|al7^yZ z{BQ2T)i>pi)igR4W{0*-#Iq~azUp*@rMy4+t|f zrE+~yM})Jl$0#AemInk8H%Uiu_U6r-7tfy6!`G0!1o`MseW9wx_qkcBS&Ed-^FtcS zrH1lnqoSI@QChvfNnyA8wcOlhkiN*99M~h{@XDvy6Cn;Uf{1=J++Iow(Wu3lh*HU= zU%uRqIVQa-c-y`E_RSWdZniG*xFLVvZ;J}GIA)k{cis?~r8|h=9||D<)uv%np#n4p zP<1xeg_=UWE3SJGkJVX{w0QP{4 z?cW4I zUd@x1&O>c>Kd{745-41ut=Km;CFnZ|`BNT*tH7s`E&e`qydMsE$L7ud5v*!g*V3fg zWpLN)xd!Lly?b}Ls_G8h7DS!EzE(BTzeQxLW9;4h@*;&5RW{@9v)yN8cZris2 zIHxznF(Sf=S{WO6`SNvy?KZv@BXdu1`&9cBE5U!m8eEl?m6bpg1)>qE7lY^Yc&SSt zKUJSY2aDi-P$`1}+BIkH-2D)Z!;<-YVGUJsZ=c-ci&)hvA@!nlED;l--MWX)Lmjji z^e#4!dpv6Xr)JFi4*f)B+^g2D;5xP$bmz_@N1FpEEeZ!W!ltKw{rqW#4m6TeBTHrk zxgk;kpo&PE0ISi9ryykpm_yqqqEya-bz6_K8zm^mA4fI%`I#!KpomnPYTyjlyL!r> zy6$28nIw9Zqt&XTe!S7Uu~Z=8i(O;>`t@s4z=DDb`J1D`!RTz70MRND%1i<&c63)p z7}77|tkHwf@&9y^czew3P>w!T*fC>&&{W4&yDyD9li8E33CRScS(`7Dw_GkGV^|UV$7a{AjETv~N(FwP;azEq22-}8 zSdTF4O;eMChK4noS?HIbR8T@1w}@mu_)=-Rwdm(B1R~ftwt}@f;`ZCS*lBk`QgsCX z0#(A*`q7Ah;GKhmkgjSk7L0c*ZYNevaP~5Hkdc5{-vO|3TucnY$7R00zVNi?V+ENt z{|g58dwRY>97L+6C?t_|_d7fUW)2nZU*7su;Q4CS;yVvd*T3SXj4;@J9?dC)vBTCDAw@_{I`N1!b3UNt|^$O=-Od`5g#PsG?GY<^u65% za3$tptF2^vX;m4v%ytBOs2?NcfQpGZ`aEP5!lsBESSAXXfTJ;=KBcNE{<~Nh%~dsd zZ%GgBNp|oN$g&d0KrqNp43zo;bwCnaJ_&J3D0V%Sl&VSqBDnhTV|P>ZR zvO{e24L9QxV{HeQU5O1e*!||1))foycO_Qcb*VclLnoW^oDFsx zd<%2pLUG-goOkr7HaE``uveNOu@E@&W{3LU2rGJ3+PV{17HKH^UW3*WK$FL6FUD=4 zb$$qtt2Q~i?_OB~A`1^xv&bW(pl~ZKO=hcG&HrNm;a7ZzyU}BS>vYjsBAo z=0Qu#4u5}t*w;4*0>1?6%$HiZ31J#(OT+W6`Wx|+UIl4Ag6CO0gPZy@bd6Ynl#hjM zTgKJ-#RH0ZdX7Y5f~149W~Bp4ywr>dfT0p76F`u}Q7exlYe6w?U&>$pL^CGjUzcNG ziB$JSNvb6Juv~?E^^AgFMSXp|onJ`~9vgcxE^fFwA`JmcmnqZg>Lx%B*AW4a6I}6b z1#gFuV17Tn(8uMMXvlRQ!k8o!I7oJ0S|v%nd$jaUXYn+ucNS&~mjCAR3r4z+!bSxW z_%|q6lf(yc))R+HC~sjw?*Gvv)tF5egA8{9e@=WP>Ia-+6FjZ2Y0#S&^V}c4 z{C`tdyN;YGD;ChNq*H@l`{YIvR>%MBvrD$wlr<+3O*ux+;-#j8sF<%WOvgtOJCfg3}da6_@kj|PZt?zKC z{Qa+%-mWeP(`g|(@?S;iwn13UlTZX>(GPZ|ayq|!$wY=iwmqT?l$00MDSE^w+C*=o zY^&@T6?F4GUcp(Au1DhJ4sAAYR|+Ra#>U3>gB*b_^c%PYNor7wWKqQXzx7&!uJWk< zO89<~%M#cZF6tScm@-|g8+(72fnv`%i%YU2r3Y;&og;&3#DF{=dW-;=khZXrTP4I6 z!GOSE^r3QW8E*0q7Qc1V!X}UB?qsCRNj^{CIIjIxB0{6Frh_6FeW5f;$XY zeB}qEKR1zZx82!0Lwc%Hjp$Ypwibuzm$vBMy?agBTcihE(-A1mpet4K#FcSC z?u{j7+up4?>h|PbHzEm32+wjn+>efqh8BdNz92aeJl2yR5EzgqOT57;Za+=51GkZ3gE7AY0Ed0|7M9a0ZR*v2 zRsj9|(&y0%x6+X)0czR=?;|m1&fd4ZeQ$0iAdn;RhL29!c@Z!DjMFuu*#8e zYha3@K*aA#8P+&#Z+sGl+TqJXHZ1do?HTz|F!YFVJkVQ0kYC()5HdlEOP6jy7W^(S za5)S&q~)Au&6Z()K#V3>Wn*v7}aO%l52c=$(0}L+}QUjyXWY zDay!Hv05a-O~sCLhu=n`(4BR)k%+YbS*0>}?%XVNN2fbJR74otefTRq{c`iN=W>^& z1ZE!pXu18d-&|P*rIS2OFUqWURJS$Xu-#!-zqncD-0$orZl(QtY1US^wl1jV%@kU` z-d$^>m+h$kN?94M(x9*;Ei^e>M%qg|h%bN#&>>KC~ zZ3R`M!@FFzW2$8j(GSWC1^-15_@*)}3#1)H< zMM2Mw(8iVWT54uQ+59;nJz=;wIzoo2(E0PoYLK{&QKrVVWL*#a(eiDt~bs5I)F%MYBT`_;a$_6fTSDIc?F2YQUa&uwqU1{ z0yM(7CBiaiwWP?$J$m|!J8bsurNbUQ-pz8G&COeNrgcS60Hb56Q1Er&J%AcM_nJ%Eg+viah{VK1Qq6GkE4-$-Ez2EyBtS=7pD%I^`rkr4T=sYN2x1p}Uh`wN+?!d| zKW(dzRjAVSHVXePga(uzL>Pc?y!~b9K&e9+#0^$^wXSsXDkN%C?H?>==a<0=qZR^; ztdZ_sH=T*DTZTLx-@@o$mIo-v^!@%c^<(<`t%6q#f4ftC@rr;!JvhOSQqqU5j*F`0 zEjrNY*1*>mQ05co6U!t`6$}Q(<-e8G<2xH!q2|Isg=eg9IRDsXOKRxYb>dg>Nsh+& zx*z|kzass+xhf9N&Yf>O1ib5hg#A1;quVg$Z(3SB@qL$*G@1Cxa`vLfhBUwBhfH3? zJa*c@kR-4&Vt`?#C|AA-)RH15lsH)|^`YZkab%do`4?Mm+Qf%RR6)>6z7SGgNj<)i zEjJl1sX$pgoy(YBah=<$m8dmAN>>w(pzYURxIaY%&A^tuNpKFBFJV#3X2g=USo;3m zqB)U=P{zN3x(}2@1i1}wz0G}AJZ)|CvsQ~|u)IHRm`AO(1x}lUneF(K-O$`?ezUC(82k4&HR6RbKjzO`YCoOa~HP@omud(CK3aTFdC zTkR9?Q^AD>p}OtUw42HU4k}22-4UT-MCYEAcsn+&Z55(Kl(SM?LYn%Y`OSBkHr=6?Elgv2JtDKVu`UpEaj9!lipyk+@8M zuEWQIUJ)!+OT?$OTrp;7QF zjrgp|{DHw7mCA{L%nI#@+eQ_p-(@YvxIpG69tpJ9v@ZD~LT$2ktT^So zb|MupP9B=AEhKB=Vq-UmQGMV1)nh-(`7f;6_U8iooZN_<(74C`{aXP5fShC|P;l+~ zTQIUvQc=K!5_hAj8wHz{LD;s~gb%3|FZvJNM1>8j@FcCu;;ckzzjLI5#p1P;g`;7^ zZq`F1*Da_j#RFoV?brH2^)}ECP!q_2PQtH@u4{|+f)ti4374T}oQsYo8J_?b^UBFu z;khzGLd@TT_m6Tv%p$R{ciW!nwFWv@EJ)cKtywNr9XSRFM9X-p_^=y5T2l}D1Gm9UQ%uS&!0bwLUU34J;65C^@RN#@2N=5)3#nyHB1(9B;f%;B7%uf4foms9q?;NBSCIK*&%FBQZn?DqdT8 zT#nWyU7jI9S#?=4_QFOX@+Qe)zbKFH!!H3n&=qe1WbI`|=WZfSf@N}nC;@O$N=oVl za}}tJ7b5;LX*ncC1G#8NUx=30m9mLnH5&MWlE{U#OPum{AjPK(RBb{uo(u1AoN}>_ zzHCSC*d6_qwh*0DYv2*N!dpvwBOiUVg!4gjP6IaQ1CLr7YIhjkr8xalSJ%a)B#`7U z{m8Yyih=Ys?xJ3ltioQGx%b^;9_JvHsIcHI( z;}PA($bJoBW*56s`7H0V$zx$L3icvswtkRjWz9aJN4o6@MQ_|ZS5Y1f!T62Qy1ydH zcLhZF3vWZ?%74tjrBNKHCAYH6IfgW`5hvELkXmaaKHpJ@O46l}0aWUVyoknuubYcv z+ay{9xqmkyZdeaSU*4t5LdyT?rR40p$yWQcm1@WCVAXtS0LTWd9mxZA9SAER!}SJ5 z-hsY<|*?duqF_YgBd+ZYyI!h$H=#fyr>o0#7H3s^4ECQT2qNr|?CL z>X+WMN}nZ1(NEY9a0)?p8n1wPrkRz=OwPcS1eS!BEd z+=i=z84cSJG3DP;W^H18YFeFy&+MhCWGX~d&h1==Vek*F2ZL;4J;VWA@TXyN1I zr1yrL?^37%_aW4ovr*ynlc%WHUIpy6^hL5~Z5+yHue;g4tY_A){A`HDZyf*gLtbE` zNOTe!k8X7-IYcZWk{7+e1A>&x?%PLvW>RKHAHTG~!`#zmxrv>ayoZcq46<+%LZ<{a zu&{j)WUq$pCGmN(j<*`3zLQj|4^gtDN$Ro#tkMuiXJ2m9H0bPOIeAkEG*a&d?X0L$C;Is zQLF}Qfc1H)kPx-@C*ww3U{32%S0xliwj=J+aH*nlujrpf_X0^O>9^q{6qX?P61v`u zI)&HB%+T?&Agj@*V#H?V{8cAEO6Qk?flBI(gjgp`y_R)9J-sypuYFaS^@)C72I188 z>#ykv4i8nBdE+tur9?r8z_XCMB5Z0a*eANta4kMd)q;>L7utBnL=0(U@<&)|@9}WS zkAKEVnn#FR@R;_SV~6?41Q%2)2JEaVIP*{+s)^TO3a(>|_Hm8!Esi$szH&H)7<0(E zUn?!WESq+I7^PSwY8)H`GPs^QH7W=&uY3;psYFF;>@4zn4fi6zWw`o?^9A2&JK?QV zt9>jxfRSKkx=dvJT3%iYzGL)xBuKDpryQNGC@*geIxN6PgRj-O6u9i;TYBH%<1p9D z2g=MK-u)WHj6C#fe@*f1vPD}B;KvN~if8jNf7`g84zrl_-5p~6HZ*iekKnHSgcj#8 zMnL~M z%D7=UwZaN5S$EAXrVHeHHf7{TgH274Dc|eUxm}afNQMY#8oWz$UD(tCWyiAZX7MYm zfK>_67dbJhVrOT!KtH4edLS97Ba*rZA#egoa!%cVG75hyJl!9Wr`W#3mvMmZUl6#^ z4tyRAvAI|_$rwQ?djtP}*X!-7i}3*~`)=cd2$>M`9O-Z00i`fYbSK~7(-OyNgI9>p zB;N~=p$Zeuvz{mhXz8@k)>3UPb2nPt4>_;Uw;S|@1v-HWE%vG81AZiZL@5cYBpQ0N zw@8M7%Cg$gBXBM8NC*$Ysq{Xaq1vR~oqQGX>^7t>nlqTO4amUPQ}roXY7@n(hbW_n z2>Rqawbm0Yy+G4nz=?uac{MaOJ&_KR78)tNLj95eBv3*sk{X4W{tdEzpbUcVcfdnS>qy@Sag2jX0=j0= zPL-)Z8XzDehcFiRT&T8FFip_E#F{s2l$=te21lG?nvBIYzugTdD+6E~DZbXIdrm)I zPt<95OCg_aJp&JK;@=<2#f9A@3;1aWvqO6vn zCV&@*&PSYh|R(@RZjG#!#TR*T>3zU7g@l*NBirZ^5J*c*a8{D#Jyp%_ z>=7Z&d|;#3^1pE_qCUt>Ti52&dZFu5q@&r29HA|E7%=%j05p{j z0ebH`<8jnpA!IDehfdr*qi_oft4uAjQ;^qnFPmMdge?#;M zJO;gB!ib`7-qb93ZV&Hb*n3&2XWg#+EXeR(`^O9JKW&D)#PHX8ap4AcJXlV0e7!&a zh6JGvgu0PA=np7**^FddW?&H3{7PffAG_!gwi?+$E-Ot4VK6_DzRP$OFV*(WXXr%n zd4*fu0J%2-v_f9<>%)&Lmrz>P?JtGzBtaRW{S8 zC%)ayixNfJ+wVZPvL&$k)%bo5Hg^J+9VMo`yw>aI5s9wP&Fxp?&Zx{~wgUq6kz5Y!Bk@IX}ob=HeEqlaVh!Z&qvD5=N@o zf)zc&pYl%wL29IuUpq=OHSf6}aEWhe-5c&mD7fvo(hRNdA4zTG7?nHFnS5AMVHjA7Cq4Woa6`wYLVB z;nl13Q#1`)^954t2#Ki|MX;_{_yOgvT&Z5br9innk{+1<*kus{PJ%BZ-`iL&Av8+> zKe2BR{z+fLOL;kSNr;~~h-yF&R1CMG6o)l}r>4^A600V*QB_kSiPFQ>My?g&E&$pP zo&=@kc&>J{UknzIOIT{6^ypR$4?%?yIYNlXCy+(Zx&30IUFSBk()$!HthR1AgdgE^ zJ>QDjeHtMeu1KS(?2}ja7;bL&abt>eerLBwpK2Z3y#$VhRG9F+3t3ZgzvOEt-1;jc zaX+#0ua`uRQ~z5It`B*DatJtQh~I=HFztrwmYi+BJ4W+}q;w$Y|z%4&w6#3?D?8|-3 zhMtGVYrvav*sRS|0uOjb%ef(t!E5Adj*{LRP!dV^7@HMX_uE%Z8hqt)mSR13?r~Vr zGm9}#{FDapLA_YgErgZFX$p{^{{EfLo{lSIDHy_z`d?S1@rj)}NGkaNPwW7#-j$PXh{^y?&Ge?02%)vJ_XtX7{ymFED%bDr^>0wG;u|bat2=7{^3kk z7UTOSiRLvSqkaT#$jdABWOi54Pnj)1 z#wAjpraoan2HJ3EqHNE)55~V^fy5)w46tm7+$UxQ`2KHV@qu}=37^TG5By=2rAqeo zW+nMF;4?meVwg#id!`+Guj;W}jmbsz_Z()0nhF12gOF(Q`;N-3=;9F?IS&zpD$L=R zn>5&ktF|T8#8KNKA5IaVP@}nK4l8FN6iU;+)PEX5N;gYXqoG$d6#Ga8o z^!fAWZ|H$ZbqCHKm?mK3QfeuS@*9NQOk3=BV(HszdS`so_T-^AnSj?&8C$!1sgm zX^-|+6JRVvV0JxB6=l>hXkfin&nk_6o6FeqT1$uTOcOz|F5-g02SGMqc2-LEQbIzu zXKv{xh_P%176Tw)upXb!=gM9pB_ch!$v{lNy|+0B04pY{`=sYXAXjMo{`wp8bn-AH zIOGvYWcZF&I)@C23Q@L^&KDf1CH^aAq6jI2AT53G{XETnCK8VBYAZEY((Kt!?d!M`O0vJZuZCd8jVFRlZt)b?SR z(yv;BmWdNwRD7e~X2B|DuAg`DR$!Pw3MFVbY$&MWd)wWcBp1MU-fDVB7Ap$@;YVj! zWH;I*%#!-cXMmSmH28-U5DcarLMxpgxL6VxGpNbPgkNChkq=x z3u;BnJbirheJ&aNt^;OI`h7|V%%Y_NoA7!7Atjs=1Qj5ng|C@ExNyPfaT>)`7-Y_$ zIziTit>fS&@lJjJQ8^2gDoRQFkMt1G6?Fc480c@@6#jgarj&>n`!fujw6L7>kHx?jmZ&t!~=Bzp~UIn~~ znsI}w+(q&K9Z=NpY|tiSk#&Oq z_33se;yG6;8oB#xYCa;w3Z4_?%1pH&e|K7SaGeyP86t(`6 zXB3;-uO~xSRQ&kXZ#OtH~LcKMuAgB303utbb1-@El6Dz>Yk zkwY&wGYn!2;}l$s`*6k9$6E{Bk{INQemZCA?-hFHXW*|Rb?PqNwO6yksxUNbmG@w= z&wTyCRL)0q>+*Bttx>_I-N*Rw3d#d^NRhW2krC+6dUor13Y|>8yLrt^)#Uj`xOx3o zw@Y_hSCO5|EXsFoxI*UcoR*6>m1leIO?VEM!pMv_%xUn^ViH7HyG zE6}Z z5lzW`|9)8>IQK;F=Iwc$EoM}p%nDtG9k)CZ@6vhF`jSRd-OEHFGSPxtOv@}9#>tHa zuYAz-%$Da2<}LS9wkKUrPSHdvat&wYaIGt_DQmuUg~Sl?)=E82j20974N`b+;~X_l zc5rQ?Ai3U*881~!ID?Qj-_5&_N#3R^>bFVRwuUWq?`3#yu@h^O?$%#%^X9>z<=@q2 z<;D+-Tczp!oeW)%-I$s1a)G;u73E#Gj>oWkp(@N-&@A4z4O&vmcx0OAGDYH%^`;(S23b5W6lr<#@27U@SZB3jXi)N zka?k(_pEqoEoZU%eSe~OXQ7J#XBG2O887WO-XBTjtXO9JYeqw!`K+os70!8S@54@A zdNA$Ja2DssgQb0ObTeggk^P8wDU6j>pQzkI)+Mv~i_IR*syzBZIkIcgAh)i44Scs~ zGa)g~iT%PChLsowvcwSMs8XphXP_1xyEMwd(yO1-m6YbD;d%7!$=WF zxyc3zn#Cvl3^z>9ikUx=8dI2{g+EZQv#TU%yNMgYNcx?fd-(xUFwOzr&PeLtu>;?0JE!B=s%NAO zI3hr1Ub1rMo35Nio_pLyVVmno-5!mz~qFQg|l68=-PN9rT&<|DGaJ=O$fGsYfG&6U}FCUKy>A@&;Ln z{R6uhVJk(+IrX0!q7=FAoI$9FhiY^9t9Bwx!=H1c?T)#iUj#ew)4$iX*+K8oRv%56 z%}*x8yWe3O*SjT(DO&#)RkgzBXSi?!rn~xf zNFn&h{yU_#H_s%cMAu=zw+)gTT`TTEuM>m~v z8+2YLK}O2iwtj3}x~P;Z8&yNI2>jkj(N~wXw=z7U#B1g~AuYQ+XyrvpD0ab!QlXMz-klj?b2r zrL4I_BAi#bopVl+D>rsE%CHnKQyJ*N)AJ|({CYt)Ej4jfcN>p`ykacpVCMG3^y!%! zMgDM;`&1qNl_=i1fRO+M}N(RX~&VOB3Gz_B6Ped z28us!gFIh7#b2eVLN8Wb63{<`34fo|rBmrfZWBb0%vp!~R*lD7mQExzi?P?{rUZMVMem z8@#|iTzjH_l5@qC0h?#}U1p7>8Vx!Hhfia&6nO{F<;tH9(%qKqH&H9CXS!hMGT;Xw@ z4bbN%7U!PsJNGFBI8|LR*2KBW+o?XA&p}g5qbJ$?Zh^+8Fp&Lqnpp@T*iyLF+igcTrO8+caBHvhRE@dbE)3mWgd^!q7Oi#u@&Nt z??HT=y1}0F^)_JcWKvziD$udfq$?|XA3-U&JUc6^>wCRa<$M}aoECz3+3P0GnYePl{*QC}J-^tC*^#dE#(}YtR zq_mV#WLT@NsMzxaY4!2K!ovH&h;|Ge(Y0XF+D>=;5JkIhF{pkY9Cq&93F%*hwFU-Z zqR+`PLssU4mY~6A3yb>?dq+gf&CJF_@s6=uJ}vH|?yu95A~+QkBW@b}`5Ph)pnJsu zd>L!{)E=@mDdsiH?c3u9ejf2Lv9M495R)57-&cM0=^JF9Qyl~N?@XK$jLi3RSy`EE zWrwq^rKRP@Rh)twb1%Lve(UDbu2PV}J>pt=`!S+zbanN{4XYxp(dwGQ09WXz`hq++ z={y-ay%T(W=NRFCbFvoA!XAJJcc3&v1R_Oo_xFwXAKG7k-}AJ)ySoeQz)+8e+;;?4 zbRlXo=N2wl@ZnPubnAI3O6DtI{~;%zb}0@X(cKUm8(a4?sA3;J3nh=v)hv+=8$c<* z6#RZnzher|?j2e=y%f(6-rch~Y;J5bM6YuAg5#$lLwM{ zu>$X8w0`}=e0(<}6r-~*ST{uli2BQTu*K0&yX4Yazx)~BfR4}8`=M zcXwssS{h#M{G9#bvXm^T#{NU1=cA*4q{hV5uU)bvJ$ToyPxBW?%RlP+DTfg+f4k1b z9MvFHM!yog~aON-)i7a`o=*H>V8sPJrY>{{yot8WhL(iEbw=W-uWC3 z0-m6y|4=Yc9he~TcPJ6QvaP(i4pWV#wjS3H&&tYrVkpz;yMO;LUas&PU%!6cfZNsJ zzZKzzE{6{s7>dIG-!e2bWYGN{RSm2pDt;^31c5pQjw(CmCDgom`z7EQ;Kgob4!Ob! zH5b1Il-^T8Z7onI7TwvWUjpbjz?0qBbEwq8C%bWecR;BF%54)L0|94?c=XW^Fq??R zp18l|5E77zdX_h_G;A@-E74Q)MIz>KDu0jCEltub6}+Yvi=0}Y?y}N-N`P-ey5)hD zlENp~ryH)6^b+PfG4p{-y|mD&?`f$FMl#N^H`!%olkFTk^VqYDBpE4D3K>O3DP%Q*sJoiNnHAMnkYFrQqL~v3`P7?&e zC`0_iJ^-B2=>9+q{DQQb#i1t=ry-+a)(PWF(E2K{n>dgs3oc%XLnbJ?OLFgJTM2?S(qc- zjR(RF;rW>yfDTH~(d{ed0Qx*$=FWf+_*X)zs{dK+?Af16y1_MJ-w^p}g>Ks3&M+QL zn46Qkt0fF5&iu3cc7w}%08o5|b{C4|Jpdejs>;^E!QBn+Y7TdD{kg*L3j7ih=kJLA z$K3!I03p7gq5*)ftzrH1N|E@(d7Q0(D(nWAvpxHtWp|(iioqP+02>0#>nD~2+=SwP zR`c`voo$@pPHr|%zpx+FR|2&@aV+_ zg;|-qJHVMF6>Z@*?iLIxPS#FbU_Nmkg!?+!{!_=!*?+3uzO^6kLLA^oR27I;P;i%feEapUV=;&&Oph zAPD9XfkJtqmI9VgUa;V{=6{O+OSPPnr8^>mepL+xRKtX4hG7yLe zg#U!FfCyNQ4d=HaLTwT4hpdCSo13JwtCN$JC=im6I)1qRFL^nb ztE~qNs^IE$cDn^~Hg|P{Ap*1n6EZ@hnyjz>wjJU{ZU0RP1OgU>SP1cQ3Gtc>a)AZ; zMYt?1ghT+#v9yGU@bd}?LSW)N{}TUpPY9pexeWbXR{kaaGwn{lP0Wt^pUD5L8rNTP zv!mvR>)%u$$@`9+?*a=VSO7+?Wa|brXx`slK!z+Wn9D!UA};{33=yXOJI5%dfi_sw z+!A45l5XaJognD@?%cOq-jP65v{T?G<{<*%Gv^l+u;hZkcr5_866EKyfLaJ~2}5|T zcrCzy@ql@e`o9zZbse5IFh@k7{60MKi}DH}!_&^4Y?u1A5;rF+xTm=*?1VKC1ir?p zliv~s23`Sfeg<|`b4y!CL~95{s~xrfl>4=QOB11Fru^@Q`OW;k8RK^D{{PYzp*#5y(jmTj;g8PnEs%ZZF(^bBw*78jKHmS_ zh(q|HeC9${U@nLVn4e1k2IP3=Fd*B502iP_0=xoN{6KEJ!{A?61MFm{d-?x1^V(fs z>3<>Zhc4=ls^7@G_Ct<`=0Mm9gcXR_n zBFxd!TT;Q?!42r|eO331LjOsdI*|D|BM|(hs&6I!tw+_|3-JV6wmz_3_IiM>3ow)> z>M6-5%nt@2Li|C*vYnIPYS@YXh_g155E0%Vi`x9N^YvA$yQ3{!Qp45@=HQ0Zxqa%J zS`<{@-T8N_5Zyim4H^!>$c!40;<Bvfk!^1^sNC>bpX!0A#>e z^?xmA0CR9~^8ByGWE_AY{hycGIs4BWMnUk?heRTvzbZP*wP z)hUWvBdS5bEh)D%^vAGMJ;QeW+c*4up>K5VC_zB`O%GyVMRI50?5oiBnO_RYIypE2 zE(QPLF~6xsu=!62QP*}3wg)%&*!rMU?A*EAHr(3~=-Jr9cU)Rg^t;nPx&I$@oOOc2 zBsCRfzT&n0>wlMmBjD1|RF{?4()ud7ed@nUx&l3JUIBrxLWtk~yNtCf3^AU7lG^$G zzsp%TxC7(0D6#Ede<}5IUqGP+9Q>bU?5hKAo8bOuY-dscT+lQD~)Dh{R zd!vmPnZE2Z+9V(X45|_kFqpwWLIlj93>^L`UIz>&|AyP~0@KCY=D8>S`1Wb*CIJT6 z19&r#5EcN)Ljtn@J8i$^dIHGUwh zuNlg=-w7bNLxj#h$?^Dqhdc;BviaJR=OM$0c+#yzpei!{$Yv4<4eZ{z{Z z-5*IFg{XBvF+D8Za=c*EX+c^uOqlfDH?)#+Wx5z`R$KU$^%f3xFQE?ME z_=Ea+{K$E?JvpAxZ*d1fQNHiuCc^W!<0k(f#oli@p5SjqP2e5oul~B<$P?No57ABk zqefUzc<#yb1b=Hbh?f_+T4}$Lx1F(U50L#)H|&MI-y7Q8xAU;u1JcMDJzfNTs2H*D^G#IVzZd@akfiU)`9zSJ&VCD= z+xh0seC!_;Ilm==dm;}xQn@Gc_OJ78kCFX}?ETgl8ESTJZ|tEO+5P5wz?={=asr2c z)LIBZWD2qu??bf=d*V+7+0WQ-^Z^|`Bz=G%{z08}UI;Q1+zWdUBoBLHPk6sJe5jNY zm?Zj>xI>Nj?}a;52;38Q5C}5Y*>A1~B$}!~q6u^u`2Q^S5MN>1^I8^&FmiNlZ}bTw z$947_eE?~y62K4{(BDJQhqSVN-(i5DR-Ny~`5=7AIi#_?W`zQO3K2+!g7E=FFE#GfV(25|V;GVn>_2%Y&V{bd{1lqi>ub!Yra`%112SKG+ zdvU(sYx($)obOBafJs{98hF%uqx+veFfyL)g+5g8ZBNbzL5Au5M&EX)VVl0~(1}_x zx9`~#L?XHu@=#Mtdm@h)X(#^^c|ZGT4eH|^Uz4z3f3)S7AOHBBZ+?9DS()u`vnaZn zd%JzTg>N6N`N_u>z7yR3`pmbnN8n30f7bVCWVc`5LBuUY$^cB(e0~0XZ}_M|!@U?j zN@#C}kMyzs$?*S=f0*W<)S=%0-1p3(nvT6NhZ5Qob6>wQzVF@-|A*5F|1my?8tL5& zb10!bF(yV z%k{SSOI48e|GxY8i4Plked>p0Jc>|Y$W@`7Bkx`5@gJ+|Ft{A z*QE;E`!FLmOBNR3g+hS6jJf#uEzP;CMED_G!u;k?E*KapV9p2T6BI(c>GEH89Rgn4 z;NJbt%Rf`LQ^h}T_b+0;p#=f|_(!T9Zx3e3?OkPt!4 z$NcN;j}kJra5oK@tCsm$=bvsu{59#mE{aAhnW(45d+ipn$ zVPm_-AJ6|Nr;ph9+QEIh9wA=DCSW{3c#=b4|cgTHX?1|?LPzi^?T*v-LzgbP<($_rT6Zx4erz#O3xOrA_q zl7ut1fgliw!BR<66$J8O0fB;oL7h-X89eiR#5gVOym`K64g;ZJ9m7G;+{eE1+9S@%i2 z_FevC?}Q8Xd{&+QPD>+hYV-!@S>9Ms$WsU?1vOGDrMHX9URb-0MbsP16mz&Y_Hg1% zAeb}}je*a#aBX$T(03v~q}*`5TO&#-1bi8!_9}Ym5xrEII0y@0F13Py5o>Jsh&4K#Yc=~VP<;r+&bVTSW}4&~pI2c2kT3|oR+GMP`oK{I;I?`X!e zXh|oD-^*m!cuBXlk493FC(4{~O1c_n{K64JKSJ8-rgPCQM--c|FFt9U0h3ZyqknWJ z5qgBx@G97|S00cip==C3{aK!9(&7M7|AC|2Dd>`)r7@0jryim+lEsSZ104cm)8h}@ zC=geCxWr!77=_LHh#t4JjFu*tQmE+?^=BD2!=~s%Pc5l%;O4}oid?rV(W{&fQg6~d z@glrd5G5-dranz4I?Ze4LKJ<6n!b-)t=GUW*ffnGSGuAB5?<{>Dl{!AI>U4{Aeclv zEUqB5p)smu+JY*%ht+4=a@gyLmKPxr^ZDFq9GPWqx{3pm)kFG~&u92ET}VusBPgAz zFBM$8AKr6#JoKFTe7|Uw3wE~*p8`1KQ$YmCq{3Vde3=BlPjXe`+}-J;2dcm!IJK74 z82aUh{fHK*s)Vf|p(j-=aRr1g2isJWT0IWJtTm@Q!D)`xpHStvj$aKy!{mu4mLWgE zS@Q_LCI zCMhG%>Bp!gWTJt?(;Ay(B8C*KVnqBQ1_;xfI!voA`z96qiHqFe)m^b-wDkgI~ik zb>7#6E203L0veXVVZoof%NC*$gwp|C3C&D;>$FUe+{@=TjpNxWF~Ub(YLk?!KO6Rv zME%2&8CD~RdgYP1N@K0r_~Ufor>%M#^8TJUm32fcpNW=5H0ej`V|Ye+WoAO(Kr1O+ zDkaQK8_8xA)>9)gA})^9=xfLk>o78>Ux54YiBCYTp!-)O!n>sc$7X{=n*;|DGwze!ZD&ZAQIzV2zQwi??Z zVlo^O3KtSemqasd6>Pv>(2uG}Gb9?u%kaHWP%RMrOq>;=UC9B<$Sxo*IFoFHudDDG zhimX+F&+@Q3M!X~Nx>;0=B(K`2;*o!7jhu!Z7#=UyVF@K zeO-^=O699PSJ!FK=Pz4!RW34NI; zdb3IV!Mw(hH1*IbS<6-OL&U*on5_430G?)LWMn+=ok~;Ct|@b9)8p#RiLL6mk@0cm z<4AVmsB>s@>y7g_KhnwPy*TM$;jMG*)T1{M5vpRL&^LHI*>TFZF3>0w%;nGFJ=th! zYdPD0JGJAq2+yImq#Li#NAjIeWiJaEUk=w34#nf{Bzs6dQSe@}Ij`}BzEtCK^`Mz- zt+kR(m=d<7PR&71TfKKvOp2j1^{?MV1YSR5z;!A5TBJ^tuG%x@&=BpM7^ja?CKCDl zBK#Z*l^SCylA+<@5%1O3~qsCSSjTL(4Qts91OVkFJM$c8PbUn_Qsp_bzDSBLF_R#en z%;mw`D~6c1v84Gb&3qR^wGBQ}=&u>MaFB4tceFgs@a7d^wDNl**@_nA^?|Qjyp3N( z!X+>-z~-5{ny}Std@C>yepdy;tOQB1(9n8GO?q2jKB2|%RVYfM?XUBsKL0TGV*ws7 z$7R@-%!v~xq?N1faf2K$`taRJ<0RFNsAP zQztm-^iYy@o@=&?pGA+ZX`im224_S+?=cg*S-QCk&{;aT)FG`VYgqYG3w5`dzmVvVyh+S$p_Dd4j2X(?Dy9lsfXM z@TD&nY@Cc0gv(8WPPGTH4skc=5{+%xwL%Wj;}TIb+Yw&mIywr+1mWwp;#U9_us$p7H*-3U1ClSE$QWB1k`wkjI4NSbl$^QJd_8_y>(9Hka}MCr2cp#UQa(U z#UycBD6gb^Hd-q-?@E|;Ip2G)u@vUX+Y6Dc1in~RSXh%&CFe*;NL)zBBEu2R#1#v= z7-Z)OXEl_XSe`3s>MHzb*5!rxhayk1;o?3m>v$&SzdL zi7a46-?$tc`zour?)_{3$3X?*T8Kv__8ri>BLlzV`^Y=fdbUUUy-cUf#8aW-a|(rY zxEy!o^+zLJ@F7^}*tKvT`AjRPhNQ9F(nE#E6<_<$Uy|=S2s~FftxK+Q>&2pu!Q7=z z3EoG*bLENXn%f)~(>NXQuGjIAi6RQCMDCb3WxG-`^FBX*Bl=L;L-61MXy&Ppt{h^iEZJDu zY)eLgtx_XLu<=)?RE%ehrU2vp7(&b-j|Lo)vhw_+GtVbz_zn_vmwIp`cD>0)0opxd+%tZ0hvxT!3 z^~H_eca3jnCyfqE1xmOEEZ(4^&>%GOAH#$5~HBGS)k{};#cQlI#%(hr$zv{(*e3A+)`bN z2P-}e*Dh2Ob$%43hmJ~p(8OWQrFdUj@pC6-rmr;R_GK| z8y0${h2jZ?kWaqikuZ-=u|VdhprOBMrH zv>rM?%{nX^25ZpENb9NX=1U0RCX0Nq#-J0K!6{bRJ}ex1p3wM`9+4i;c(Kc1=XHC> z_O3|d7M@FJBv3wOgj({~UbiM*8Ba|mOZj+iNe?Oc*S=hzx(AabiJTw1zf~`*H1O)} z*zLoY@8xMW-A^VJ0#7C79=_j1_*8|5FtYnzNxGx()Ws8=P0s+c_9&V!%D>^<|8Rqh zC~&p$7Dlg6#WiAaQ8`@7fd!8g!KPakb^WTQv~!Zr*D?!?wsgJ}(NOX+$=H+Pf;%oM z)loP_(WufCI#0jbQ0~;yfm~K#JoAM30C^wNWYkkrhr>Q^wj!6T<}SaO`60DBE_;i* zU@TVk0RQo>60_h*HSBblZu*&n5pXQiS6&GYdDB*#PIXn#$yd&8x3XVJl(cjT^FSg3s{9jEzUX5WUv(^8kfSzcg`$qKTNu`<7wkxUv zZY1ECELHH-n{csa630ZzKHp~dv<15_7VB4NtXl{HV`8DwQcCU;X!Bhfo@ntTA{4sd zpEz?4op)k6@0}@oG1PuicT7;pYQ?&)GT}0JMbsuM|0+?4`lXx2^|~fD1;^LqV-6i9 zH`C;>F@)Vq-D=Eep;Viv*MFd1K=zL2MxNY}tYv0C{aeRu@(O2f7Iw?I9zS}y)u~7sPU<;A(5)uJSi4nS?*aXav<9VurDJsNY)d8?J4N?Ic^kv3_>{iNedn zGoi2)8pbnRm5FYz+8Tl;BKt?JzDD~@T4T2X9p)&7L|02awf}l*Hi}9eM20=4&(+XW(!Q# zsJuSk9B-GS-8q@%<bIMou;bS`$oowtdDrBh0B;{<-nw@0H{WkNBsFza6M z`2N1zS*u4+z4R@bFj~?R zFcq7`UaSRt%p!W(Zd2oZM%q$?-{$bG&fZgeZv>1_j~>VnSQSVJVHF$4QED4e#-=g| zHRN!AekrRcOSB=ii)~11Y$I+cFL*)}0FB)5k{9JiHu{z2k^9kV3!J)C76NylzKGC< z79Nv%CdTV6Wt}IUb(Rus>{y@D>&uq1FA1XQCC66pZ9cF(!1yr==!=+)1zeC_=#pC4 zB&)bPIi8rSRLF!=!Iay~+Z%pG*<-RL<;qfFM~SRe6blO$H8mJSrGHOfSg=hDp0{zo zGg%<8x8Pl3IS_LM!v_sUU-%5y^9A~>PEID!zfp-fo%r-fJf$(rvxEuCmXdOp+1S2a zF(a<=n1R}5*MwYQ&%W6biRe(N(UnH;hZe*Sq5wYgB-2|u#b@4BHyC@v{TlqPnaAoe zi{P_f^Eh_PO z6&zzAH=7N5Lz!*a)4QhL<=a=aS(vS&eUs}+2MG^D$_W;-mMmH7ka2uD(fa(Zxi=rT zC~DH5-j2MW8hiWU4GED#-*ISzHEptBa@#>0M3Z7KLS5I{`h2MmFVL@daMM|55trTo z{ekX!4rgrcqk`iy9%}gb4@ySR@4;+GYLjbq{9Q)NH(#nNY&p;SFA+ehE+TN!y_buB zfatbwZ}Z&y=-Sqi@==SthSi+Gt1&9_mTjf-Z`5MC29h)CUs#8oHN0#54t~Rs(a3wC zH}-Zez1VDTGyN9|&gM)&QS+Nq6ViQOR_?OW9~sj?2R)s)lavyYSMt6%H@Z|B6cj{A z8|U7l-Z}A5vUTR7hptH)HV|9S1>vW-4aZhKpS)f2{?Hs}wBNA@A>>STiM26|iehX> z78Ey+UGTB!>o`utGeL;iXWL8hj?^wBW*zz=D4&C=cVvt@ zcaC0kz3sDNm9=Y-SUKR4@k*)rl~E>Qd*=Vl_Zv^G0a6^*B`Bp(#84%+bGf{&3U@#a~thpH#jii*6agQuqpK5=YF zn!x0PhI~=Y=DKi7Id8@2FgWF1=&GCb~@_)&2SdS~VI%*RDnt=1v^3 z>Gplmuh%Q}M$96r|0zQLPNB?&avpq12@m+=PU1lt=4(j{ryUp1PEOJiO&R;5l)XdIh4a^e;ExR?Y>VWh8jlTxX}+ zkwHb4=b~M2tq9GBQ^aV1!QFIDE30pM&aHYb1*8(pq^5!5X!EE&bqQz-x88i)=P{P$YmfR_*x77rIC|oMvr=)p``W|FeuAm6Zg&$a7(WoaeT{fa zmg+B2no#?Dy?~UwE%I}xJeQP9Dnmzuk-MP{lL}1as}P+pdr4M0>>%ngzEr$9T*OHF z^e7;!l|KE{SlE3rpXm0)RFI{2Um~%l15Ve6WNZ)wzm3S>e$;HevcEKI-RKCZpAmpm zF=L938r^O0mGLppMXuEcZpt`}EQMxjnK=|G3!a;Ob;{Lrn*;%RE(V;9f&eU+o!@#* zjXy8(gN?U3XlX{73$|&*2P=leH0yI)<1z^((57F1K^eXHoS#-~!?BMjN(}OBBn1>V zF#{I6=_uj4mce6E)MhEHnVS)I9DI;9ojlnPy6}!S(LN#YAz#}dCM4|%dy0X$miNeIY0@u<=L09|Rb9$5WX#z)tj7t|k z%Uq4W1BxS+mwmvz**ffYoj$qqNL-dVZsKh*CjaGgKxc6z#`b)iU6iF{zSaBT*Kc1} z%XG>rCa+`+H8|*i zSz=$aC&Xw*1)|w6a$Ji%6mQ&nZ?3O*^I4j&VPM-_Y`Zm>FcT0ZGcCwwqbph)t>#f1 zzwDd0#p#yQ80jUTuPnj-D&*>+tlF#YSxK~8nS|_hZGM}6mPZyRw^YY4MbzJ&29l-+ z2^p5Q?}ZxtW#HZKWZZNuO*pWnJV7oKnZdKFML z+lTaEpnY2^7s(z{_nR%RyY566QK(z>l%pC$4Z9;}Hp%>{?drg0eP^-6lvqhqrVTTY z!=`_DpQ@Tq5IFYs$dVf8Q!2v}A}}MgcoihgI9TH0my$o{wf0GCN@UW_l@#!Tu=Ujo zQKkU{{qG&8hKWG}&E~^?ap9+cK|~xYp_WtJ>Q8>* zGMRW$ca8!Ru!8gl;1ovpN-)p}d%Z}_y}vL~ADtf%T$Kun>wDmH>vrOXf3E-h=ya^J z{HZHCRw0KO(>XO7<6It%U}WBq^v=kbtDQ_VZ8M1*r&L8_^+;oE2YapdPx-Alm_EOf z$zV;b;#y9^I3P0$eRPVumViA3!#|petErx^bUuKQZJ3C?tg;GAP4xQWE&s{5_a)x^ z+Seldp4<&5m3e#eJOLGPjFPIF=4`;w7ot@$qNS%>O)haP20$WoeSP)1n#uC3%R^J+ zY6V)kj2+Kz)mY2C#e>!O9Ij#iFpvJ~?Gx+C=Ms7M2TYfe+&G&w0sEU~A*JoTn_DWl z)~aovsqBy~a0>%hRNw1_l1S_oV?M%q&I(NU`{7OXQRYsb&9NFO(Xhk_z=F^%OzXs4 zdqzDS1J=53%Q57y*4>^x-V)8snC|BntQ45eNhd9Bp1B#Ucd+PSanzP!F*nmw3e1Er zY^6G6>H!NIxupVKv5#Kd7@@Lfc*W1p2pgrxhpx)ryg;JUOn;O{D5}04-_w2L{$iHd za~r@8hARfnkKfpA@0j}RYmr*zSEhO2sDi0L!SE^$<3RBFSe>A(=G?o7M*3+3mjj$E zo$mWJ2uKs1Kw~`;V|rn(>y?|6@v=v^V+CINC=K6}%kff?Au+CTJ#CU?N-KC~C98dS zc%2VlE^PIbGFm*P!c2I8fulDtQ5~?>!ef$TBgnb-@~VN1l4E!51RH+CM#Yw)Q0r{7 z)QZ$~x2Hsy3F6Zv&7X2gSDS8{O41R62(fiV$YcbGDeh9ku|c8=hFu%4;GYt?`#oKQ z4gs!9V-q#prR>xdFjsXhgt={u;!W2QOGr!`<_E*8S+71HC)D#>9EZQlX&()2%DE(r z2#NQrN*uS={VN^yS3j`EaNJJ;kpZJAvOy}zoD#;j6P=Bu#5D__kM(Q~uL#qm z-(oL=9_AK4!dB5)lOrgYy7qb|q#Wo-3#DJeIEwBp%@oV>@-rrDh`4@`9(}#VA#2QyzyPcC-r}>0fs9T# zx7zQ3$g~1FwTJ>2;)qP>CwZk5i>BY|&rqF?z+t@4thao~oWx4{x={4`*@c!{oq>1# zxyqkXVkXSuSuf1H4F&r3hmPF1S~)sW8M^?dUkbuuEM)F+Fg2G}R;kl|3{Qk!U<%X)5#PP#A`Et;@s*n`=M-bMoOnMaY#7nu5C7U(>t2Ue{3KZtce}=$!tV2|kMfiRh>i*oVNi|O zDuxi}b=>=c2Fy=<^z2T^T2>o4M>KF$tBJhl7F(%nYJdCvQvb2OtPrA@7$r_IUUh87 zo5xBIf^Y~9!sRxSr6{w#3InFZ@=U=HMYOJyv&U)iupCPkdzTz(-Wv}-bjlGs20->u zR=N5WI!?Dx^o8{X9s2caFJ74w05vd|PGKzed{tLUHekIZPsmu;*c#P?zBgAz;W3IlZ~$YA%#zjwdZg}wOvPJ`>ry}yovW_ zJL-836Mq=|#O*DlfNiqVv_Cg~>XNtNMf4YEot&_qt`756I9*OobPzN(Z&k)VmM=^t zEqdr6KBSpsP4~&&6L$0>nflT4XkEAOD?_>^H{QNkI;f^_D0Qk%!47+k$wgJ zh5sGXh7XS$y1ceNVPN530O{MwV|VCE)~>62c8RSYAG$Mj(NxEjnB-$9`U~1Srrnh_ z(Sg%<^%Q)1!Ns-UKw0b#$%OnSQc--xt<7#R<}SYaG7*wHF!nN*5?68xvb*)0J^k0K zMN<>+D=7ez)Dyv{woE}mf_Y;VLZmrbIb{Ylaj(dGG@}A^7MA0NO1EC)#not)9$X5* zVf>^S=ANL)mU`JeaNeiVt$AowuC|$%yodCfDZiSe+lnvc?b~*O1A4q0D%c&F=ntCx z*BDqoNd=0Gi+x%0J6dgX1lPHSF(^KZGN;6Q=3;13>v5(Hv3@g|H_v9RPvRWPoFdiY zvM!~Vp`8}qWC*jR$91;t%#nFo_tJH)SWlg&J+t3@xF!}45fB~NfI+W!ZE=a>ApLtU z=e<6+jk&EW;5TQS4pO$w{~)n8`f{pEvQXc0@mI%As+StWeyVbNcK%Rv)V&KiaMXZa!v{nG8Jd+cXwT;cMabUXMbv&#j0cQI|$ChvKufKGd<`*7ggWRr&33NEi!J>XZL*jDpba~y~c5zf;ZlS>#*4}Fw4 zX?Z&6xogI~6AQJ0OI<{nbhssSq>x@v|y-Tt~n zCUT-=rF}yPYmtL~$p>g+8EU1=-NxR$co1=Gx$u5;Fg1JG8KzRgj?%67Qk(rZnSHOZ zUKtu-uLy~`buv3sUYe0}YHD%)UQOwI?rel+E1;G2U2f|t(bnqSDgQv|b#K$mBhN`> z-b5WT2L-VO%&WC226P8b6_f|alJ}@yGkvpZDYbQfsYIyLw(IJ#l>vVx?2g2Aj)F`>A!aObK_{Oh?rkbWY#_;!TeZynp0XoK^1` zz`pQ&Gjplv4j%M?QR^_}($ZE%ssGT!tLvFQuc^Qcj2#OR#CetwH$R2h$_pJhCqN0K zs}s>%Gl8Ss)$9rAMn)^0OFV%qlQ&yv6Avr$a=kc6))*4Q8{1u*dK4(@GxRWU<=y7X zt38*?$mbQ>Ir0L#c{XQ-q;5}2`;^BXd!;bT7YqR&qxy=3z7CY)wwg!D1{q8D@~6zP z2jQ8ZPxKoRz8AVTJBpoDJus+ax-e?SL)jyE7@>;QWrv-x-YE`FPRljq29ml24wX*5 zRKfvdI7&%3)&dqNOV^sTtyxzhDV{TlNs-BTX zft0h#V{4GDbbSPzW`^sWIv7he;=N|=v;}?e{;KI*D-VGpHsdjsQX9c6a*%>Aa8J2` zf_Y~wgWo2BF*ky4+7--A(3fuX_xc}Of!*R`40$jifJx4PwY0HXErYp9xb)%3IrJn- zH5H+a)wQV&8=KsfLhnf-GE6cV?2|Z7+4Sdw&@0Qjq}ChhNdu>-Gu+wBjws0FZVYCe zqbH;bVQrQ$i%CKQM!Z5|w4Isa79a}EveZHCfjtxS9_mx8*94XEP6r9GmqvwU=}E1) z42SoUH>TXR-qZvF-O(e=$6j$5L}Z*`>?`oLnZIxiXTsAp3`icG%mgTNZu{Df=iXtv z9CUc@MpLd*&V=yNbz+$ZCM`-A*f=CItENNg=sgHWC%bFB!??(M+!=a8=nCe?7m`2w z@lUGLifh(LrA?E{yg5~BTSuJTYOZ=Xw`6nq)653R22nkWPXhtA^PO79<2hoPHy>qQi1S8itBZG1J-oauNoJ14Z_{{1tctux zz`H2&PPg!(M?oT-jN^&EUc+@~8fx>U&mR|%!9J!PHDJ^-VeL=ibZ1oADV;QbIt`tB zwcQB)!P`*6l8tri?hSFX=TK((T@@t8wHrm9Y=7g#)AH6BWm#&^?jj-QB4r$Jv|A&d?f5R0UTaJS}d67rAo_>v;n65IJ_ypFvn0Sx3 zcR%`zCor~!mXk_D)6AuTbC-tVM{70DwKksMb1&EA1Q`0% z)w?yNE6ndd!iJa`UpKz;>{=gx9jd^oET&9VQ%Dy=EF%~d>o|Lpc9l~gV9}z@qV0;u zNi8OcqI2j*x=O;}=Cw5;DIfPDE-~&yBN$(R^0SwG^h&Pax>|jd3V3PpSSVaGtWG@FUW* z&06Pi7@;jp7LUmbeDf?GyO;C_-*9G0f(-=&jH($&V=LLX&U{#y>Jle=02^1vW<1k- z5iK<1hJAEf^z+E~$?x$`qSXsF)SvM@hOJaPA+j~7$D<~c8j05f=pFl_7hE_e)&gIA zon3h2_2|tkU0QN8P%rIDzbFH-!|GrgktzKIZ<;9%&aDl=c%dzg%@TPG3O-WHYIWKZ zehUfGcOHcGd?D^p7Mr{v(0I;-Q`>)|nLZF)a3m&1@F(zF#OXs(CMEWvh&*@iJOijErml7^oGjFh+KMiIKanL-FMA|n$FR~QM zw(0)Zv3w>}d*BJ4k>F+F14n6wKP|PH-trfm%wH^G0!Yw#{OFX=aLCg5lQuG<29cbb zsi#G}hG@{oyH8VgfWY0eD@&5TXLDHHI^*< zPT%boKjc1hkS!!elV$Y9^su57ofs3>W-%vo&zq=aGzt)Xl~|YeSlo4HiU;mMi=oMC zG*felpyI67*^O9uau|^0Hh9VfN#XH$v<1vpECoFMAiVy)491>5`Y`j37`myz1_X8GjVT@az@EQ@w&yt zv;qyOqs!1dWuX7qqZJV`Ngy0#U72YRirRcv>I;3bCi^(e*n<$Q-J{I#R7GEt`qt=Y zOPq7?q0>50Nj6UdGyO>j#x4>!Jc;A~Z z6@i?SSo21DW((TI2w$GABO^(}wC%(lsSAx_g6O)=GRs&d%j=(7xZ2&X0RuN7F;^Bo zbBsJZH}Z_fWa4Gv&=4;bj}%&0y$lOOO7G%G_eR@9ZNUYup zFfNJ*-^Ya5OSs!(i8D!LgGPo7CY+058> zZpc#EESV$Hw&JGXpZt=v^!<8rF&j~e&xN|-Pq8GAKq(wyCadFX{ajhfH#&6K zu|yz&GRy``594v3YRo3vIhIaWm6wF=M|KKfBOM9%WnL?Cr{>eyi)~8O8nRL87x~Q0W#)+fv zBXvnk3L|un=XeiNFk44N66Y<(C6Jh$YhCt!pih40`rG!v`<#JR`sG-KmTuk_9j=wN zeRVX{D+XSgo2M(^DFrQ@^=`ove)MRooG3?vWQoDR>zZL4^@qZ`mrtC|;NW0{4V=)s zf9l@hvFryFg|Q2F2dAz8OZmb=D*XalalKC;zs+OYA+32|JT*~C^xEyttVD6XEh`_p zYwI+Psl~a;j%%A;sW;yF_>es5$6T%#eBpO#I<;_Qd@MQ8J`26(l65+dy&W6u?x~9^ z-25>C%Y0)Fj~$F(&UR-#Rk9|P;5cp$O#TSn;EQjw)=w4*y(Yo|J(;f(@%)i^zZfL) z4d)81$V#zX`qAu@>pocPTK#E^+77aT^Q~2wUTX{Iny3~;MS)!t(Z(ZGHM~H*bsTguqXpQ~i6)*3|VxPz9`C;-C)0dw{ zCJRoa-d(C?6Wd&V`AqCuX=JFUrspuNz}U&M&=SkijhPAN!Hn9j|L`+4YubO#`;aa8(2QXxEOJ9;UcL)gXUn?JMrX>nvvSM zEYNUcT*DJIODw6_%w)kpll<|am;S?^P7CyI*OhU7*4)dRiAN>YUh>q3bS8eH7ZZh9 zzSd`b%8~m?s-GkBfmZ@?-s$--kJB7@u693Dv)l5#yT)s~;vJg7E6>okI_olVd59H# z>8(rh>$IQrA`ZA3utc)^T0FCt7+Cq%3`L~3n5Da@`l71z`K~yYB zD-vXcZ&541Zedas;*xn2?5}mok2VIx@1=2xl|O~B&gO&!M^7-Nqj2vzyd3C^jrg%w z$_dq+ll^3V5hCO34p$90Z{o7H<^dA}8^Ap{bw8FWx-)Z&yX@2PHbzLn)W(#tNJg=y zkpJLvqyg61tFMC{LBo|qGq|EX!FYzVF~N`I-VS!FN-N?sa#|3cE+Br*a1e6YHnNZI zAhG4RayXYdYmYQ@QDcY|{~dx`(+p@t*{5;nKRZhY8}*-5Wrz?i7e+hR&3P%;)1*uK zsJu{dY>rGe<1efL2qRLxo>cbg$A^KPhpTs4*ya@HMs&f%KUQ$@gVrpu}V(CGfns1F^DJ!k{qx| z^aD&UJrE5aP_=K(_AFQ_4m;JFM*VR31oLQ9gf4#yE}im;_%|z(2aIaiM1syN6ThN0 z#k$qOt^LTOU)eZRW8p5(MDbeHRZpXCwD=Eb)iDR+!6T(JpXp1)TF!BLYsBYWnC*B+ zE`0g6z!%SjYNK?ggY=_K=P;B&*yh;&1($CYgrafEm|7-SE;91uzpd^FjGN#(X)Crh zu(U9G*~XQN9g}AI4qB&h5XOgK$7M8|nvJe?D09%paCtuMM0IO~T8Dxz-79GJ+{>xL zgG4VnpK3pzn7binMw1#uycD%LSu7Nl-sSGSc=lBO*{N5IUbCAV)Gwx4a|7`fdl)&H z!p)6dpPlcO9L$>}LbHzx8nAxIw>G8!_%=&AIcC|%Y&K?0JNfjRT$CNBov;?oh!#_6 z16orKFHC=M?$S!3jqYKcr%kRobGcRU&dOcAXuEE=Zl_JZwIi<{lhGw=ynm?YC`B@d zt9--YTE-ga0G;e}F2y$ojBMmE@Trt~Y(H13;+qj~P*okcQ2^t_D4bFYON^;X@#RCZ2murt}-mDCJM9U(jC%W(w)*Fp>%gihot1v zT}pRJcQ;FSDVMPs zQw764-g)f-$A$9~y{CB*TT49!NQYdZoiSt|10MhQS3Sn4P{BA99+B)X_(+|)S&;Z_ zU5gNIg(A3$NQQNVVyj5{HGc5F(c9|Pu&OcU#ZClCE4`uX5Ul!IV_PlSl6l?DdR+0X z98g|2f>Aeu8bD=HKCEvW!ZFojTQc`O=P5oBvR9~FPn-6B-cQTPGN_iH7Px!6E`r3K z1UYNJo{QY>@CHlQGV60H_+E1P%jx_5-9a%cz(4gtY$EALG8%B2;&4d*#6JJ^Hw#Bx zz_^$G2uGbvD-p=rPaY}QMl9yrktF-tT=XAWMnZ-xSNI?f%U2tgF}!;$|BHw&&I>!~ z?U~0Jr@Rm^i^k(Um;3;pvvI!jG;8*L?82UM@hST5m2l^yC>W}LJXw3}yNzLuCCbeD z=REQI6hCOm0)x-v1#}UGx_v&(i+li=e27x3KU?gVp~|yRf&fP9LE~6AmWYP&zrDPs zkM{R77=jy{GZL$G ziQpg~beS{?&Xae&PaND6j^)tIAQ3J_8X4|hV}d@b79M2oN%QRX=1D$RIxBtp+cvA zX$eTATIecI50A}XVNDydd*i^9NztvR=yg#pcxSeqHg@jBCw_0!hwDjq%%$-@R`G#K z@{0cA@~xudoe=cVx$tyu0O;tKGe|piW|UbSrqfFJ$4Ybrx-{HPh2o0*u?XMO77{f1 z*%9lcU*@5JZFu?S1U_?55zQSlC{TYBXjv3DY+>)|rPH<2oPa-Zjn93!c4)h&rZBe_ zjDD-2&`0Ih>Y=$UCb$v&K`i?CfxvVRt4JRk`ykzsDzffJ@;SZ_QRG~>z(d;#rxM=| zu74xu)wf|iB5SJ8d`)?6URBHfZQie|Z??_htCjv*$lhUe3*1EQOp!;=k`s;H*s#`- z-H>D{!HnDIgLTM9_a4CcJEX{5D0I20^=F6GC(Ej1hr?}EuNCznYgd60)8H2UaS?>PvdL9Q{T9$KOE#$v>?Kc+X zZU^?-`gnw*gbwlEhrv}N#pUeL`G(M7xVu8ik7nTfUE>W|p-DL_(g$`IvMlkF-~p8r zcNhyX$I{raRcxIs9p#|+rI0-=w9HB@20gOgArr0)OZ9Xk(}gwQtF&HYt_>f#efQ0- z%(DFxaawEs@+=KB`fI>XQe>>4zZ$QU3`<_~_M=?{sa^z!)2rchNH-9Uk($LYO3De!{{F9k6%80;5eTuldJi?ObEyD*E`@!ghS#e75 zREIb#MpLV`)C_Vdhv*J8Nm>BLIY65SednK$ozdDp7&n+SS74Y`^GT;IYz6nJk`UMV zZaaD0Ee{XWyvy7h`m@gPm>OVS^R z(CYJIEXDHOORKY;-+Lf-y-O>3Zg&mZK3!(SPCK_1%_OKh$?rkwaZSJIx+_4hIT~>@ z8jXB_t8eo68fl>cU`S+$; zB3IpOSywVN1kWMYw|9H2^abHHQ?;&oP%JYysf<0v>mp50DUMx z{-^3<-E^5>L4>lSN-R)`ce|2J2dxNM_ksP$jQ!Qph3+n1zAk|2(l$?74@sy$zS}$R z<6aB)SSXpeh~Qx_@r#XcVHvT3B?VPCjdSxqZrts6KbKn< zxsXwa?w%jJh(9m~SJ%!Sc<#MPL())!ymd3fkKkJIrpQ)L#5x%ie#XtGw$(S$OAmB3 zd5>mXYr{yInMs$)pS~7O=R*?dirfX;md7oLAiXi8{t^N=nU{ZMU}62o9OKJQ$)0H4 zCg3xD$ZCyO$+c~NeQqhE1v!M7)S@aS^Vc+!v9Gg%4kX3%pz2}~K|>1dk!HTg@rtuW z>Lq9koH;rs>5!iN+>jRrHehEJlKX@mT;h607hutPLuw)c@xLmLu?YT z`}qlF{k!(B)@TU7N~{0Xb4z3@g5Bc|R^j7MlzJ-u^_zuiGI85M4v!so|9FQerhAXL z>P>baZx@zaEm%HSEEMWuPR!cow4;G^S4e94o57(Y!A$!%y+>oIao6SCr&;gK@-;@L z5FqO&joPCcU4I{21|%1I-+lAt=sO~wEZWwh?L_M--yXp(KsWY5pWS2oNA_;mVokr& z+AWBsxx(mQE)}_Fl;>+e!^CA{OK+9undjZi<7f1m+XUTLvPn^oSiCsP%Ieq&ZSxlt_Bn@FP) zGa>s)_`KZFL`}ui<etuPaZ^ znugu=Pw&fqhz}#_+(k~z1q)shVL^3&G;J*@Fx>56r>T-QQCIoL68)5DYY%`8>aKI1 z_GYahx2_=ng79z16dm5Vw^1z{Q&%afowvU2H`v_N-6ina0sCGe)zYN+FOtqivCsOK zqbPnGYY>=yIoHf)tzzdP(5ZXU6+VkOsEZ|+82{7bC8v`+th63WXzJonr##}j8vmr) zc}bo(`ptF~wCi9HL`=%%+`$yb6Ca%Z&2`&%cd02FchvFYcs`1kV zHl0@EsrI?}EhoxX8`q*Y+D8LT4{&{QeGH4voqBYul`^SeFTp38llF_tO~dwIiuj)B zEQfpB#_$9?*vRTz9}M~$>%m789QGEO1csWkOA&;VqKeVh66s@WM60Br);oo^-dNTg zXyOGP>CPs%-*-HxqkbP9WQbfWqoy&?it>>9jVkqD%NFuA1WER(hM0pYaa_aS+i>Tu zskr=u$7wk{2IpN7A=9PUKa{QhA3WZbUujEP&zz_1&A?V=;s9a^HF+_ZMYPta$4CCa zA5C|iPDj}KeJPSC-LA;emJ3nZy}dY5X=9*8hf;S}>MHPSd%7;}@Cbzq#ysfO*K7x^ zBljP{X>3v)E}&N5=<;s=f&1M-?_slwV_7UvEh}!5{7do2%^+?a6v(xkj$1dyQJ6!d zxDp|_093P*nCOPe`};wjVK2L81HBas!IYzNa+pZJ2D|Cp>=;m?7<~N~zbZ@)sdz$e zw>#bX86DN0`zf>D1-}`U&55Dmkr;2925tN=Db_PCcUl`gKIC<~?-$OKhME>V+ln_o z=kg~_UE|kK__#g)*)i3M)QUD8=66dUBP0L)_fP81;KEjmt+e0!X;AC2`qd6%=LP&f zBw+olIhpt+d>kmN7xzX|chi()W3Ab7hFPE5ssiYndY%7>ej+ACo!k;yBH7Q#6#3p* z66i^zJK3A4_z%P3l-e+NYpFGYFpXJA`}PDl0^W*+cxjzT-qWbX_qfv7bPTQtid*I- zhnn{JKM$N$U4HGo6GnWndEYIiyGT5n8w9xXJ$u@LZZ3z#ANbDZ7VGe*%jGc>b^^N7 zbLex}RSka~B@^NUHnC18s$@xMD$t7aH4KpAnTR}{o{QDyI-;7-)p&%3^SE*3^+WjCbAs8eK z2*KgO(gP@*&liK}#LXVQdZ7qF7DlBsc-nXiNT<-m=&j)%N6~~Z@9Ih8p!)0#u`%LC zN|PhwMbRb`I9NR}OLp7x2Kj(Y>os0=%d||?1p}^BnkU($NSS?h^1IGMM^dIDs z-Sl@A|2y7Bui)^57iA%s6cX!BC{^kO9mHO15P|9$3`M~fa~(iM$+>_7)4@YgF-ddg zzFvCTR6`)GY_*zP1x#e@l{cb43Jd6hN#LlA6eS}QNPtNta18)F2h?d?iC@C%!@jW5 z#C>6-Mx*fkTv41Rcm4f=uXL-7xa?wCDD;q`hl}6Ml8S@6^}rMNJIho=x982B=2NZE zh>~tFMF$?a;i*sAMrfk(lTN)?%Wlf&)3BBe`OS;eU_w5vJygEkH_yoD=~{`csTxMn zN)PJstPi-7z&zxT4Af$o(_pd;Vy=VOoH^r1%d8Ev-=*|zY5aM?}Fa`EX-~+&CLQ#n$$-oK=#;3IXO=_G>v_2g)p~G3OVQ2oKMXN-ZI~6lDTv@I}mM~mtMDs9Q`4ZV$q`9b17n(VegIH`zMgfn;k%#-JVC^OC%B4{E zrO+%TBhf!T2dNIzZF>xVXMmvMnrPc7mLLDqnHcGnhKD<<26yipS$go0)27vLqhRL;Tvlu!l-$GJAI zro#l+BNT-t7N>A2_#je+o!Vkf|G(<7QZ4I3CaVFfj#>r(G_|PI^)IfcIjjTf_Frq=~}c>#cm%O{H~YR&`{z zk!wE+r~d_`<)#5&F{Wp^BvR#0STX^#{!Q-s4}5E4Rx?SOWU*uNLAE^oXf0mPmhO7_4Z0(;xVtX6Q5<^45UECWt=mHrU|JHJVlts%NKgfmI*vib9K(GMXP66_JJ@c@4C9-A&ve|$k4!5LI?pXb zj_k=4C6#3u(lZW|dg4T5OJ@SImifDmerasG>Ex1pXSaig(Yj`n`@2*VcA#8ove77b zaJA`ovzw`Q5c&h71jW;u4TEE0mwV(QWFg^w~n+Z}`M-Jlg&NrXWtL7Rl&Q zgureaESPEH#k0h2_U1H#Oxt$k-4Pr;ZN`w0JXaK|iaDYsa4IN+ZE{Zq>e8_=6Fl_O z$4I%#3t$5#V)6MqBx2S9;RK{}abF-;U55i|G+LL_nq}{5$uSjLli@~NPx>j=uZNY3 z{Te^zB&77D?&(c|#{6N)txnZTi`b83CVpp7EOsY1ByYb%KssDaHIA~zB zJyfrgw<#0q(T_q^IUfZWM0DWo;M;#qu6G}$pj_ifb|Zwh4TVpF;+1lRbR7+HF>(b1 zCtegduDT&!lH$Y8s1NR~wZ2EoTCS<_h$yV%jME|cJ7mCT`Sur9Ufr`uq0)Qh&l|%i zZBSeEc9ZK{UO`G#p{8Y?lx;_cj1cwMQoJ{$`e@ijNR%?7GccD6?g6f^ElnnTg-E$8 zp`k|*6u?ktvQ^O<=JzOSt3Rp7dXYo%ETYW8ZEo22^O5F=>}MXFTLVo(21}{flk9q# zL}tG49g-Tm59xNgA6I_Oihcfc?kI<<&l(9Q==e7~IcaSyc{cBjLrbkCflH0{&$p6< zSHey;;P^Ner{S*P=|fr54w<~z0Vfm?V%g5YwvgFr-m|iL4J7Ie zftaUY0vvlhq!;4kq3M~igp)J&cklJILwK(oSfe>26S3bBcboE;q)$Cf(Ll3J8%_zl z6OI4rG}F-a3|L5SSu8Q2!aaCj)L%-cF^tZ|Xu1|r@>`oS^afb3WKW$Tszq-4puM}{ zx-&*1Hh@WGMCdU>>*K_@#ml5ieh}3bIoetn7{lQE8{%r z(O5QR<*uhoCtEjC*S`t4yBX!oI2F_~2<#Wx)^>jTt+G)9DN|Ha(noyKvVS#24B;cH z2_+IIlg8sJI?AlX6K8^}ogT23qJod{8>fGd4Das`c_I1%IT4wojzSFe>3hDUrMBMW;_J5xFbH78wu!q+qY=`Rjiv; zcc1e%oFhaS(T7*YV>;+`-i0!@;|qEXakPC{;ZK~flny3v0qy2on3zvL-gTbe?p)cX zpjLeelFxNcmrUGQm%mMADr(B@o~+a7DL9&v9Hf;{I3%CVtG5MQo4yBA=^aSA5T_e6Mu7z^c^?oRPsktnUofwviK#!q#&4I1gy9>zg*t@n(f}u74BM$ z`6dG~&b}i$I}>@@*NfCP88fh|-rMU;$B~C#Ku14Mc|LvQwS9B^@Mk>ob)os-2@1?2 zuxW{|rH$%X)W%lqxDA)@;c*358WxeLeCessH*Vl|`mZ57v1LQkkR0I`4s7nQ8{~FB z#>Dxd1%VV=RD_Aj^|Xs8opa$XwAV=o(<%^L1bRy1(|zA9wP^v)uF73% z{6CopnG#*bkg9gzCD4TRLZ@|7j?+vW<}8Geu{K3FJ(pXoxq(z@0^y^fvfHmY;@OYZ zxvvM9&HoI?0TfJ_By?P!{Fu@3Q>jR=PwRDdXg;aof|l!KWdV|!=kbm*)1LD+FDMkg z`;2Uiz>HFyIt-4Kc#rHVAD%})hS1$qnbU>2Moo{@eE(tH*LnY^_*cFVFD!FA=Dty`r+^>B+yDB=6{T_Fmu3T( z{;cXym^e+?J`=Qb3(S0T@Bg?TJlo0XBu6zu4sSG7U~~^aRfCv!^m{{i+p`RrkUiF- zap1XJ+Jozdo73yVxZKV+=@3Rm&V^gesC<+W^#*&4f8}Ky38;Q$?bYD=;lwx=N+o=U zNf(we>Gn4yo>PZsbt(Gn`jeOGpN{6ofWkIeNb0Y(hNiO6)X97QFZfP3IzZ=^Tw+q$ z=9KS|*}-yI!mHgqChq2mcRkXuDb|#Sk)MZE8y>AVIjiR2sMSO$&N%ugEt_=IZ+stG z3(NUBgVs&qAMU67{;^~R$$|gtRTY{DLHol+i^6VjoN#;KGt*GbGDM~ zD1t84%V9DTQ&0aimcnw05K!EV$ePVNTdzVy65L*o_`hqG|624#=3TGWxS!u_-d2$F za?f;&DHe!hrh&Q6-RHi>vftk1c>o{*{lJSO{TM#8myOh<2Q5%4*F$qvsl$QEX64#7YKuCqUb)*5yS|zpHI;BEN{qUvwX;2LpOlVIiHF?tr*eO zc@;9t;{Ix2m!6p?g=9>Pht_$$v?Ds|%gHvA76ymC@;aTvi{c{DIX8r$5dKXPky{=b zmLe{Kw8D6p<&q=vkhy1M@pHlfFu*CNm*N)EIC0E3j0E2PN>L8ts-=O zPPq|uH3=OeLa#SY*MdSk63#n<{2Th8H#Tl;o()$l1<{$5>%lBfW9(7?KSjP|B_Q{U z>?t8Jh%{p!e;6AdNIB;?Z4aSnyT1&b(sf3ruO*<&SP{{1Sbz)R=*CXVLu$&+KK$Y@ z@N*&W``mcYnt|c+9gcJLPL-8w{qt>MG8V(sN<0DHBP~00P?4a}6lnZ5 z%V--zd9e>HiK$l^j<-WF?%zs_ff~7;0(w+@=Z`S@qc@XoXYD``Oy;)VC#}5dgypWd z(ysdvyy`5=HKhp^kn3HVLHxWi`?@x27SMz)wdMQS&dz7)+58X9f zIW2-?v%phFTi-s!uad%@TC#^*Jphf=hHhf_NpP*6Hk z^?H}gUYvWejcoEQaPh&#A@VdOD%|x^!_B4#zrQ}e*jJ94u`2TIzib^xCiI#LYpP@u zJ(AaJW}p38r>d6KS*Vh_g!xEKlKxEN%`kgIL}RT&?ha-w7IHP@ba~XCqo!^;A+GN3 zV_N9FH>r-Aw*X?jddq+%pX>dVyamLPg#|hJJ}*Tsu9)`%#q=d{(H0vT2&#|Mx)9z$ z61K5<@wUYC&OaG!dB#BgW#?A}hS6!y#f9VE#3C}wO$4r*-T*m3=EcJpfvUpT!#Z_!ovr~Uqq3i4<#(up;fol^&Y(4P)mjU~Kpty1JV<*af(!J)EPJ{m&p4_iltgEi<&I-nXry4Eu~x(w94EB} zF&4%S%DzA;gPe{J(Ou1dCb8z?ca7Yapw&m^W*8eHLU7K^6A~$CtjERVag{ly^bw57 z@wRq)z8R-_r^Wo}q>Y5zW~NrcFSqTvyx8Y(eor7sl(2&({r`rO!B8;=?=%Y*G_il^ z=509MeeY>!=lBh&y%qvih1uVCMB4nxBw`V;%qR;-Wl;v14AodsRi}XQpE9c?f_N^< zdMt;Jc++GcqUbng2O`^x%lPv?0yTOKF7|f3(IDBK5_K?3+Z=}aMmU!5>Mq{r%}u}V zvVU*iY-@p*(Av4BF?YmcXb60K#dButGE1H|YrR`b zp7DAu>6y$0EL(Cq+}mHnGk(VED3;s1NvpcEdfsTi++qOvR&hoHHViGLKAadGl^FQT zQ+Ru$bln>_diQYxLgG81w^x?lskCJla>mvRL{j(m^JNKt4Nm$TJ?aXwtfzcWKl~N8 zfBw1I{j-a;wa3y5If!yX0TO;hDMU~;y@3-K8cT+fZ8U&`^*?zbI!4^yTedlz>?1eA zffj_wbB~c84!29(ybYQ8cmmK$EWV9Qdq~!$#+0ZkY6;q1f1~av)hv0Sh(maF#xf-o>A5J8Kg7@DiZd zeVOBVF@16+rZdpzF)8Phbz&02N8;z6Cxo=G9E1ZlI098+sZ?bX{{6_UYj0Bl!>tY|?8wLBUS^-3m9if7il@=t`$a0F^R0Uxagvv+>!Z$-d0`=P6C=C9<@vOEgYG%z6Ktyym{IxE` zJRub(!Di!x?w+b){7#)S0~mG6E~(r|$oo~sKBNa5Jd>%w`sndzGX3cSoVc$rY3+7y zj7lo|2ba5K$Q(y6_kmt$%XkV|xz~OG3>9Hz%z?s9;7(Zj^$Rjx2QmPwhcJ=6;%Oi& zdhAG;p-Fu7H5kdTZGMR7seh)v}T9A&xyGrYdS{5IeF}!nWw8NFanmppd?#`eOI}v%<54 z+uIHXhM!~_L;uE>F_^w*9(t?Z#(6Alku;mLeN~MYqE!`4m}-$JDHSE*`Z+h`OizEa zpBDxX)IQNrc1p~_EWE52v^E#7BW9naI66=OV5s0X%bc?g_va%)63&e^AkKgBi$S|z zcP7uM(r5ob=cUlc+R@Bi;EMCglw*OQBt7wij1Q)b0h6!{CJW4_?W;hVaomiTVL#p4 zMRPsxbU~02T@LJfo8s8t-`>{V7OJST54wFw^!eH{8Pgp%OnANDCbLEYMGc|34TW(D} zPh4V8H#WH#z)efkP-ULxiz0K`;ha~*$JJQU+7%wU2>LjS@_~JszvRk#>iycsayQ#o zwU>i}-kG0FU-)+Qwv8x2?I^w|ejdb}O58do6@=BRRvbTH9_^k*Pj*h|q$lzWxoOww9nvldL+~8?K6zoE_B1eYSEHSv=E^{-mfu1N1-&qv|A>^&Hy7N2j1_=-C3!JVemDs92osMxEY;AOP2?EfL3Jqxa`4nHjnE$&8zi`br* zR`>^Ps@f0UC+rItzgW)<5+04!FTYr?XNI%gTn}`!gVXOy-UXP1Ppx)e3*sA6R@d0vW?fdTv3_;B+nwB-AE5L?N;YTjnX0 zv^9fDZ0t!3)C*ryH}FGu8+O8Ju+@0iCb8bfvwx*Az)#SGB7zd>9DAg|4ZzI_m%Ps| zVEClpHZ2=3>_nZd3$@t=UO)iA)4V+Ak$gv8N|uq46a$ z0B!35+cqZ%LPtVH5?>{Y?Sjflvo$AD-sMugF-#0FU2li0`xRD!BjXg%gUjiY87o;@ zG`}&)O?^RYva~8O%^}dg(Yaz;7nYGxP4xJI;u0`Vlg2g~{3i#wCD#0xm zbYUOvn0n~M1YZW>9Mc73XUCH=Sbs4$f9N&j6=j451_4nh-ea5bteI?A*B|{pUfVuO zpF{7kaQJZt392mU-}~Ec-5suw6yq7)bWGxKDY2*jKRDh2HX7R=_6kJK?h`3>jCaM8 zg#tB?1&Rb_+W_1#(i42KbGRy=>^oD)hRm92t(a>5zB@rzU2YIZddaB0ZP%|oBoWti zM>#5>clG3b`U|UuU+7r@S^+=M& zDa@h;FE&v)qQNw>W>kS8*X{5K@L0@4Z)jJ~vO!QBI!{d}lUj#1HPLkthzOZ5c#57LhGAlU3-C*9+OC8L#m2VFfjF`v)6^h2zt6UN zj{Rx#@^i0Mq!6HH?m-_*exz8M?C(}LV8E&R;CB7LEga#PD2Ed-`)DU*isTryHv85M zCAhQJ2etu)Y}?1`JNTZ~Tg#Bh0_`5Ui*>Pob0aR62!|;RUd7S&K|SAzwPO16_fywP zlihs5uRw3hDhD=+|FferIol@bx>?9;GS0E|*Oe>&!I*GQmyDg6`%GbuIMFXY_}<)^ zXAF`jLt5PoE&WM092-NnJ%ZbA3zi;>s2DkJk9x&A zWQrw5I$pDNXzm#QJ$GFZHbVp5-Y0?XlrE*8IH6;RB>1x%0tGtjQhf`3+mA)E`kGynx zc8QuJkdvv)*x+68b!IJG`T!1c*n=0#QidRW6aRLqB=b2mAJ1q-WYO2?9lw8n@UqZT z6pqH6M(-O=*hiuCo4HKXeuJfM`mc#j-&ReX8uy&*+P{Z8#X}6;{rB3>vuC1xH7_s~ zmFh8nV>FyJw(hPUnO!2%l)xDYg)F;LX};h7db#-3NdzrryW};^vgKa%<6l{yy(K{P zJ{~-b-1krks%nvrm#YSA&xNDbPkc2;fZ%`J5fbCWqA8&!z2NB`>e4qckc@;~mV^|W z!p(8(zj1WDxBtRd0{q1pe_u;TG2;fooi#w0{u9kqnI+1K#{dK`S+psr(+qL7$nt_N1!%ZxI)Y?Hckk5lkbX9;|S|yw&>P;Cmu1-sbr#rnSDU zscy<|W{GIjmu5EBg^6%V<6^^3%>qR-8qvb$b%(FVf>GzmA*jlL8`zQ0hK_|23$2E+ zinj;E+q7>(di!S%4=be#1xmvJFWW2L#X)Gpla5{aY{4ML)5A-)pVj!INpO07_9?)^ z&^iL0h0Q*!^D~4wVx(FR%H8WVb=4Z+M;D>j54u&DcW#})Pv0~dr>oN>8%^X4DVG^)4is9!J$Tda8^wE-Y&s175xqRgMfxHj#W8Dfa!lzX=HXTfUzvl>J_>I;| zwV@4VPhLV5vGsTm497%@LRAXPf`@gp%l&K~k15}%jyj_vJ;$xxv$FDgNuarYoCd%P zZMzoQG}7s$QpDkl?(4!(bM~KsVQ?#kxXGc*<1+~Krdm4mj&K>Z7=n~n-cE`@X3^D_ z@G&)Ee&#pFfzcxq!S1uJdg*DU8~p7Q8;*2LR0M(#vMx^Ha_f7D=8$;oZ31ax(gYEx zw_%se-nN3|*r>AX@-)TN{g&R1{7Lu~c!5?~7h?b3ybzd@4V0=dVMq!e8jP-Y8AX?~ z!;>3cD#70$j~H0wEwnM*sYmP9M_zK$l#I1EP(=bNi&oz#J5Z4*gSnUcl4`0lqC52% z@EM>DP~uL{smZpD(I4Pz@=YI2x?-~Kz{)WP5z$!Tmk+u>AYQ9Tuj8S93YpIc>i%Bm z9H?RH&D>I$_c}mhKT1ZtDOu?V?uN7~);{G@=4vmV4iW2HG)Pxf&KU-qaA9WAGt zCIuenDxp>A*nnKxw9Z&?E=p!#Ze~ekS;p$GMAd%2Yp)dzSS(n<2QC_?@%@7v2K!Ml z%@yN!-%KMe9Iv=#s#;%Fk>#35;1FEn>G3WS=31n5*6T$!Bei^ycF>vneurW88Fg=v zg>OCvz|qGBcew%WIfeCS$jUmm`?x6C`ABc=E3=Jx97Ew0(gJ=9A!plkVQ|)WKk*iC z$46aEB#JboqX4D2?4!svS7a&+k)CkoZ!bsF65rt@fNMeCBp?;V#|HO5J~Tr942>WO zbv}O;E+aj3zurC#?^3CiQT$?8G?R{y+UwcX!8hxRHG?a9HgCT9HzX;BOC@r7Z2Oop;~=1QR8G zoF-<2J~h7krs(soCcko*dca@(H-9Nee3M0PmzlJ``yGvy`{gdp$>66HQ_-GouudGG zQk{A`9F;L6RO(kG|Ifgzm4pY~?`;?Bm&~lTz!;2s&_J=^CPn(#S}hSZ>c>cZ_f&LG zW9N0Xd8;~(U$|i|McM`9?j9j^FH|?2+Km>8= z=3P;_ChB}Q(K}7%iUG1B_!qM#$-!5nTy}a z6x3$XYys}JolzPOMkGBeLd#_T?k;$o+6+5#v?(@&NW6DVZlKoMm@vLd8~Z)ytQD-^ zssUP&-*P|z1(tN(LP)4kwirE2;ZJ%be0Sgio~5HbdkSqQAn0UNhG-ucdB(vTaiQ_B z4~Yw5z7Ia*`*V`yh6doioQ-9ykX&ft<?~CC>uO^`emh@02>EMG60b?IYD;+3y{Cb z7NL&9q>@VOqodqg8;4(;J*q8PfQouu=^j6d{ac%Lx?3F(&0`e6%F`SoDdDOVz8q(5CbdLdS zEf%355STt$!2W!X5Z<2h7Yo3#JI{KI`QmM!|IAxAMY^pQeG+r5b{d$`>F@Oca5E|<#K4S zsP0{>*~R{w4XmC39rVogRD%be@zFE|BULDhG!Lj-r@&F(6rkUKMM;JVwZpiC^tg5U zj=0k(>45=+nw%VkICa>3zlcml9DnU@cw3LI&4)r$LjSBGez)_HtKjR;Bt@zIH1H#n z!=Vu}8V+pqYo9Wk2*)F_kHv^Q>HIXEVptMhf4FgZiwZI$5-OMUmTkevBP=WssM(bv z2^~gjDRdtaA5u!06OS0VoJsNiPha&F7v|M$fd<)spB@s>JrME{UF_rs8ZJyLjB!YM z+Tz;`>n;4a7Kum$I*1e?h@cgk3zG-O)Ty$4!7yzr-U+S|qFmY3m+Uyql2%n}8gZpJ z#npkcpPTt#n6LfuHVgFP-C<@DNAU04LtBy|hLRQjNIn8AZz4Xm3Y@f{fyW~k3OEZW zYG6j_0I@|uAy!QeilbrD_l?+^>swS*aTic8SW-%+*ky~#9BQ+NWH0sBh!1dkt-xm3 z8{l~yq$h6>0#0G&V^);rAty8z4vvNI)!%v`+T&RsO{NGlJ`NzQWC+6-x0R;44@Rf= zjC*@%1fJ@$rgs4mm1f0uK&3qgtOM;dj@A_W!%e(^K8X}PGqq5n8!4$DKZ%+9fgQ%C zlZYb!Yr5oL8$hcvE^d~2F{}IML)K!gGQK3rEKt!_ni3SA2ch7ln3C-iM|B-mGO`|q z$s{(~nOC%iu=-u$HKfd!Qgm1dJK%)N9!-F@InN@*C zV?EQ^3Z#3S1EqFu*mXvX2~7QTL=3lOQ7v5a=_FP76L}^k7d&aD1XjLlj|g^?Edto^gnB;BHOIi;dpN$K{h&Ml zvuHb53I^Gd0vaV>QG}Ban1&&OjQ>5TG@Vm6Q1yG915HrsFR8A0pa_5}IpEsvf9XMn zWs)V$nTVhYlwreC$nmr<<}n0t7kBfFXI`)se*rNZZbm4{A(_P@UzxFwS|S{9*N%n- zSgVk58{!8IU*YnM5h;|T$gMqp|7qsT0@zZN1mxx5us^qzT8N{Q8q9>Yuy^P*C5RP6 z&XlsO;MfCMKAf5;VXc|oHSD9X0~~p|cIjW#$k|KslIU40NU=B?A?P6{hr51aR@H|G$fv1hj>pGK2&c)={xZe&b})Bs=`CfeBzAEQl-n zLop?TMnGI<1V01`ywYZI8Q zhv`K}1(ovbXgs1`5WwxkAy0ub6p~J$ks<;`^kFIVr0`(LL$MB}rTDmqPnE^aEh*`6 ziEua{e#dqMtK9Nd6#(|s-n9|n{UyM?CLu|1#M=2Vq!y;v_(1`Gn;?etKtRs5ul;Gv9IdY8nJ7vY?w^rD9Y8XK?p6Sz9I}fFQ5%09HA3Hd%z@95 z!4$gym+|hzc4?ZXgzJUZ#>2b11-u8dtWp=Ew(!YF?NxD% z6CkC$<^{_49ME@n|0`d8Vx=_ZU*L~V%L{NmKNydO(-@zG+;QN6N|!0iHLInnr^#{R`>^$wpq3LYc)%L^g}9v?40qPI#h8 zCCpldRWrwRRLmq|$* zto>MIs&zHMAdoV3-4ZrK#%FLwTgu<=E?nWl3R2FZCgUKPUabgJtUeCFhfsHkj_Dm`2uoSMZSB7x*nM$MyNkLoqrcJ?sp-Vv9 zi4(=ljE1oEhS#zKR9C=M{a8?PZZrf|<` z$s*z$xFZdIUII1*9F`UX(af4*ZpXvu56$8!c+ z14>HfVODED1yYDE5sj4&X`tpT8|v6dEAK#FpRoZ|-3^SX2|1BTSO2(@YKq7f#(OwZ zQjqD-fdNAOpTQ`lIiD73xLe=e&elzrzK`H0WP#kR%t9apM|uJ|Gywbbo8=qNi0iil zS&-#IcZsc$R9ts3K&YPxBSBiXQDy&wdJqM1A8MPv5Z=ohYGkzq?Bh}@fw?po$>-Cz zhWx_e4H|DnK&&7dsvjwF|8aE90d>A@ICrwmlWlX^wX|yKWG>sb?N*ChUTxK~?ccI( zTi?6y@ALNb@aA=2_vPmWKUctRwTVP7!F(K_dm`l8ixV<^Ect4TkJ8hIYe|CP(aZGG zfC+qph?KT2+1B>iQiayV#Wk?w2R4L(~}3Y9X5(MG)AVJaM6Z=^T_hO69T zukI#~`g5odR0bXvZ5Y-+s!XXzR+_-1r$8=yJS3IOXg`f9b+zIVP|$Skm&^!XW`LV& z5K{3m>7}fP75+merxj19nLOBWAzeP8KCS^J#(C|xJFfHRL`;pDfRV464QeT+HLNz% zKyAESySN4abfL&A z!mwLjmswl*<6psyxE}=IU-~77gQyXaFKpy8m!7vlA;xh*fq0Ea9YdD{5M4F^Wf8^1vHZ zSLgO?EFnZej4*2e6@?U#*DPIFESIEC68Ku9Zd$~4qxK27jdgyscCj`iP90%&07p2m zN?D015GFw>8>0$G`hS~DZ~wxf~tR%Vto+28@d#3`1U8{=pE)RWHhH4Oh8|x zufqxdU5*I>|4lFlLJB=IF&dXC3m-_|@?f)oS=;SWMG#VNv|;^Vv2m-k0|IR;rFLnC z!g|t4zxe>s051^7Fb<&>+WIU~sH)3pwPF*sn=@g!`J&ea+Twu)mq>GBN@YDxz~FN* z<||KP;3CNdz_4tm{O9C>J;*{0JmO)hB5)t!0sdia2DP5ujgvJ%=Cg&zgP5zuc1kgf?)NlVtedgc}Gy}PxeV{Sq607)(tF6F%Q-Z*|cd*o2pY3Vt5pi*<%DWGm(^!HU%gP z$04;UyuB7X+;QEQtwS{j*m2<~G%@i1Bpl6jeHi8VM+j@;4 zb*H8sevJun>`TRmm`)y9<6|}3fPg_{yHIc;`dA})iE?{eb|cqI?>z3P!>E;bG;qh; z$TV@Z03t96`&Mn1rKds4W7g!yEasDnXvu`V2uy~p#f8!(0}lfEJ~9BoC=L^dtAkKD z_E$nyG|nj#LArW(0qMKq8bkd=1K8sqK#US19CaVq9Yf*Dzc8f54Mz}8G!FSe$h?Vb_uCB!Jf!@Ohv z{mNiz4X2;n%6&Gy`Y7FnRy4vms7!aY+fhnJgDIJ335X>^;3cAS`1?b1vcqwqF_vy7 z_-w!VQEgbGg6TbXzO->z!ih|#dH}Dq_X9@@>{!kFFK_=|vdh6M><#0}Wu(Y6$T^xm ztw014OG6P7Ab+CF@TbjLlusm%&MMgV^j+sU2RTcOnOMQwY}DZDd{KFmK8|?313^Si z9LXU|oB=m3cok#ETqu&7X5pGRz-{Gl>rGA?7-2}*Nd!ujIAH*(f{Uz_B@-vMW*J%A zZ_tY34J<{GZqMu!^jF#j- z`|M4@gvuW^n*95epJW4KhD)EQ;9ZbwJT(tQz?~b$5-YVGjaPJ?aEJDTvP>Qs9PihofSrH_jHIKnt!cmM*|u&i!@P}baO>;fmsjG=wfy7dd0 zRjf*V+Ax9vbZ|j8HY;E%g0~LT_=-y$nfm@%@pDdNuL1Xc*a~Cehx7_rIuh}J~@E-t2OG+ zHgRm7JuU>v6~iln8VT5JDbiV8K@qY#xCNTX7OYzWMU+pmF^!V*Guvy$S70R z$iUmj9Kg+B==!DLG!mhD$dmLfR~DdgOq(M^>INiNo z%^?#uRsLJFa>%VPvU3D3bX;2!$bb4ZGvM|(V;I_akO9d=wNYs<|1~S*mrN8M6p`z= zFaEI$oPGDx0x9PP-V(Tn*+&4LI~kG1XGB zAZm%!9iZkaQS~dC&VC0$Nj4|i%lDl;(cRAE)rty3rCv1=}oQEqy|`|Qs28mJU{p0{h?cm z3l@{M0W5eVuWvSSl<0PyKFIZCSarK+1m-jb3|uzd>SHr0 zfW*Vx&_kupxjVg43XiJ!&nLlJW{PA|Gw2Ey6nz4f9 z!=!5DnH)#blk;6fLKWPuSpc(738{!eX~>>?W{`mNIY)_TH{ppI?@_i(8%nwd%T1<* zu0hjl+#;?=Ai_BfIF&_7ROM}SYz`8qIthoOx7DX;ZVjTU37sHj=%{8?AF7X-F&4Uf zhUD2SSLcKNegC4z_}BxYsJbsW`sz!Y`?RQ_U5x)GbdJ1{o#3I0sMBJCy!B2j(C`0w zldQBc5~!Sy24NpZp&jQ4QXcuXDxfH_nNDIPvagho*uuuZo;36cE8VD{b#Fd^7P;I| zO;GDf3Jh+e?z*GTrE(ef&KR2>1V7Tc_h ze@T`Tc*;$XwzBsgK{h=rvM0yC5*B+T1WPB_ZoDGsdhnY{^EatW`1IuhAaY0?6ogex zdK;fjl5Vf{31pit;DvmT312RXE%{3_sN5MR8c&_nqM3so18PkgV!|Zw8q`@R732s5 zRRYF`MUm7Yo|p3grwk?q5jvQmL;qX%;Q`^mR~e7P_0wLW+JYZ?VFjLWD^^DMO@yv; zB0gE(8YDUjRI5oYP}J6@sKBLglWdNZDQUf~U-+xqXuWH&oMO4d+I61_V=wI|VJvc@ zSe@z>eHHJ^&8!T|MFfvwf{DlooVN-FT$wY84;RhH419?a4)Izj>-z7HPhSh($(@=W zcTt-N3*4nQ%WY2kR7>w~IFUyOAqVy>^hta$G}fi6_C!k@BC8Wn{%4^P6mNaoD9j+h*&r|Ngr#Y1g`I=hU^BL1 zr#y?wmpY@MV-QC7jr^9f9Z0RqUZF_V7F@)GlaJ3+B%5QPNrC>xl6R|m0W=>0juWr(e`!am94I=oY2sv@h=GIOU6Du#B zG1th>gF!CEM6T`^?P!L9QV#;lP{%D3Dn)!MVYqaxa3zf#%qSeGv zI+zV^BC=q#7fy7XnrHz6?Nmi(aQm3horHyCQ!y3g8ytCd6rG)ywV$dJgX9iVml9m`T3&se^6khp!Vy55@u8k9D@EXgLpb8 z@>%N*>_|)v+`UWAGrG*T%7N%UBZKLI%85l8Ja*+E(F}(1qWdu#x;!l6-_6)7kxB;; zFg~~hM3~j5O*11Fi3{sk9PrD(hzP#yYhi;Q3wXb*ET0qjR(Xq>v>RFs$SEu%Z3#Wi zisj=Xz8j2Z32!lK1?B$S;x?=I7$6((v+bKWmj>S5ZMfzj0LZCupO^X1QpFp2i2H;?ShNhdYyhtAK<=VVW{43B*1R-MB#1 zB27R3B+M#^nqc{P@{NQ5B6`dXYujNuf`k0W#`GC};fc~O*!5t;ghnzdp0GI{15<6F zGsqm(0IuY_zT2dMzB=peHvqD{KMF5+lwYZbIzKh!k)og$e#y0`Hldh}K%#4^Ve(Rpl2zpbBlN8;ft~K- z6Xy=T$BJl*$~fGo0&y_mFAX+c_+;|^!<@80$@$F&({_K-mBHFf!)1ngeSnIbmi`C+ z0Eb*ZAV5LxtguIa5k{-P*lUgRs!a@HAEyliX!%BE8gl`$@yf3L&-{#|a_ViNI#XSe zK&c{8jXi4@$4t_KoX{nOo&p@4E>n1N1Ys|zTDJO&RvtHcCwr#VW!iv+G9qY$5vBMUM z0f=|+I1%W=aL1_FXfY^>@DJgIC6-nD^Qz8e?hN9?*g(OW(ANms|`gWN=5-PB63(rKFfY@qa8%&_` zg<|m#8Mcz=hQ2w-a#WcKz_vYH1)x%xl4loO%Lj~6u{?A5|Ds6R#Xq5>MYVj6D%@N4cta`;ydXcJA_ ztni=^W%6SexuwW^wKZAORU_Zk_OHOH{2APhVz$Lwo%)df~5Dh0}%Y$bbqrw$Xga8^{Y;Gg!^SjCkh+4x)tlF4A3IR)1-`t?Swn zU8sF?^+p|w)yo@w5uEr2jmHBIHt#(+=#`6_!jwVH)%qOLCM4VPxp z<=@~OXlE^(pyUiAg-2Fwol7?Ie~$VU1p0i78&E{%RsqbJOl5na;02nlAMxRtPLuy7 z2!_@xYWnC=YK_IB^j#7;e2s4c(w1m)Hn3iZ+>pxuEc<$~(Olmyg*W3DePCykl+`(? z-lAI1xRSK3_w2}4|J)#V9#>!qYyz8$c3?*+Rr7N zpB17XlOSi1w)|E55x$gA`fD>{aqgq!u>zNox$iE&#pLn>ZuxXKAwb*h5F!veIg8z$ zL2{RJv|O7cgu(I+&3{!21=$1Xg7`bdF?j_AE>d$6TaO|dHsxInWE|zk_M{hj{0bt_ z|1MSFY;$s$GDhisyi0u1t<&dj8f`)kz%q(|zSTxstDigxoyopY#FuNd+hw<41H$2O+tKm zV1IjJ|6B1K#`S$ZfJVz@m#N4M^*tz5V8E1LatJniz<1cjBZYUWAH|22JDr(&oZ3`& zp8ts#sGm4(f?Lqdav(00XA#tN3{|gtB^<^ttTexgd?0MECkKBs(Za=z^U%}Y*cbK?uLg~i_RLbfvypRW0)eS6i zIpAFxv|?-)Iulgx{ABFxGs++!7v8?b`BQF2VzdV|*H|F#+4vY)zMEu*1$~Wo#)+#I zR(7~*FbVw;w;3UY6KJKF-Ti085890yuQV!MGF>s9i7Jcz$9qzM+ zBbb{cdBHnb`>Bx#D(nKiKn}%#2Muf<|8O7B`ohGkp9f>$W}IKqIU9fxjphiwkG+w> z8!mzIcdeSVe_Gj6>WVUDd}xQ720Zf^{^tQL;U_I%8KIS3h)vLi&nt^5Db}4~F!e1S5`iNI_K0d;h( zoA4c$qCp{}Bgpl~`4>U)jLl(0hCV981I8!6OTSh3_wKSoi9+va@8DQ)N98&mPn3CJ zy7LR!-8Nw!4<^`L&www^(+T|Oe3XQG>>5J=pNmynwnRtr^bKrM3$<_WPk2U3oK0rS zzYfjrRR2kbdGf+PDAZblBwh+MOcj=Rp?`4LJZ8V0`aPs9^Ot^rrd3dr*YWF`SlD$v z(6_hC?$xE2U@5TEME9T#vmeJexkEs<0UR0h0o8Gvr5Kn&pCkb1&jn9W!mBlN z?d(N*VXyd>3<5(13BHiOCym8fc+eka#*)!7HpfL|Pu=u~Wy~%O^0RfV3c?EzcYEEQ zXdtpXbEcA1nj%|7M+RfQ;Xhw+`SIWY719R%lbl)T5xV$)h_Z+jjH|vF6u!PdZ=S0G zxB>zf=D?n;1JWEpLbr;Y3ooOqEl5>EAwY7R=r1soPc8{6yRh?vIeT${UxTO_pD$RH z?pwS9l8f8(;>%pz{mnWOJhP_Rb=C!g2ai3#T)KDfOiWf$;tcR`>|N5oFjL)hfFT`bL?TXzi(2mR*7|%;I%H|!^}z)F9Wi0!a3!)I1QyN%b@T-G zh!UFu3z#>*g=miPjfe|f_6 zxn)g@27MSb1oRG1=9MfAv{fb(s@ZRBTk8PamE#v<6BG~ec zvS85SCXEx~dr%^T=2#-m4H`e(i@UO5-F=zAtpvH=3mv+^IFXcatw_|D&AV8kcUh|tSqt$_T<#f5U(cqGpQSAC*T|KdLFApN=!GiJt|0bSQ2Ik}3PBL^( z?ZK%c>(i?H5dZxC^L||-#ZV`Q{^8HLs7{f)bcB4hI~^AoJB1QdC;#yWw&8#OncB6M ze`)pn>oMjtZ~w*T%crq3do{J?C1p=XJiUIZSQsb?%+Gc-NkYj&8pl(go4vNqC~68K zN93`v&dB#su!*R+Umkt$3(o!a$vI)6>JNeb!eLn3PM}9ArS6O;_wn1(l zd6#E3E|+$Q`tZ?Q{(|UTlmM%eJMbn~47xzTVaQF#K+oU7T5FOF`hMa{C#!dq?U#oe zyZg?Cb8u!10^61N$L?(|Y6VILCNI+6K>2iX#%ytZ#~w@5^>8hmk@`LE(a8dZX3uO_ zSBw}|)r?UNq;*mrkukorTot>i;&EO(wY4Qb0rj4q|JEC`;Hwt@fWPDHz3?Kc& zWLw(OA=Q_?_0}@0HTv_mN|M&qk*a3oYj zc=#qY6|z($yI~}PXU_EkM%SHE?Ju(P_EJM0llX+pOrX=2oytnv=Q;>ZDXi+of4i3a z7_^<#GBkyy6J)%?X2=$&k(wl?PG!Q}^-Q}iufqAaIa;r8X#9Q|TCvU9R_2igCFefF zQ!ySt`F+$1dQdYSU%q%FMNh=oC3`hWTfxvMg1n3lJ+G{sQUfW+?)8}6fyqd8Z;Eto zm#+*>1>sV#hP|o85O-R9Ik9QQCNrx+dKma(SVnBuIy2+yoV|Zd92P)8uJgc)KG`4z zJhqP?j_wI-FPLkFt92nT1qTmVM*4EIGQKx!PRppD9`9`P;*wRYd?pQT?qG0YGvQHU zJpxn--a}Y52v{(2;Y3s~xs9-%#!1lfEV!9}z1)2NDK(LYb8*@h6!CDCdO8?ys8!k? z;r6Q_>7wV!@!vnje~-}q{~trW8f`c}&`J$~*CeH$tpy%gMp`q-jQ$Wp_QK1~YRTYmD}ZDfWA1jEHNh3bhqOhEd%w{enS;Win_ZOfvw~hrWh4H)81z0C zEwnSi<#v-NuI|+m#~dG5^ABVKi2s(||E{LHIK=5k0tfMnAi$wcJE^ti+b?x@<9+GbttdCDf7|W3@7P`p1^<4FFdDaME$W_EH%_7e zq0JYvi95F5Yv`^W=ipl1R?ZrK#-O*QMG+F;*nP?NC%1YOoS$BiYw2Tz3xRh@h+G_f zPsAX~yTPe1cr5bQ-sAZCoqMk4X&{>(zU?xBt+}J~$-w*Gt2)nU=eg%-S6lY6Ttv2s zrxF*mN5%*1*QfTF)VvZW5}xcFrijhVmSJ@6cJ{^L2^H>SC0H-=xW?VTzqH%kS?Ykl zlaX-1D_^zMJBAiJ1|PJC7rL_H|CaGB#aU(nWaw5<}PyVZH?cvog zGad4Sit!F;i4O2G!!BmK!JeIcnGtd-T!?q@s7k^WTi^yVM7}q^ttpkL{f8ZUwdl$W zr(dG62?C5mN3XHo(kA(uZ{;@x+WfEA#_)DE7rz^?$qI_p8FVSLTxpsIa@&UYZ=fx| zYe*#3YRJHTs(R9R40mF2Nep~Pg);~&&m^@Io_9Ojv1DhVF1hwLv7p9XZBs;aE+~tR zS|JXGOl)ut&>HOvs-{rtm$MJ)B&VQC&foJJ5T2>5T(676Lf@}n()x;y@7q23=7#%S z>pxo(Wc5P(2o8GcDaFUxVS@JLpFLbs2)ts^MN$~W$cGb zB|Lb#8>U-yuEQ*EQ_yIKSZMdbC%aPa(2#LRP0Ot@-@29jziY)VrL(-C_ABNe2^r$k zI^TEwsMR(t4s@(01iN+_4D&Xh?aPw6ynTRy7VKUP_Cf5IjIWj36a-wuIw@5->vb!5 z`*vz*7?Z>21#fai!t{`>Z`D>h287nvCB0JQx$xt*D_dks@t3{^1;m>?&1Oq6xs;<#;z~?EjY;kNcW?%GuArl zL!IvZe*!uJ-6Vcpyds6S*XKNy@8k3^2=U3$h;)U;MEc%-Wc(-nxvl1WUr23%4ky0-6 z=Fv6zRC7;)(;K~b4eb)=-4lxJGNYAp#j6B_&fwXV zP9!3KL9^54p3C6Hv7yvUSDSH6%j6Mq=Ozf_6783U#K*Cy4;-up1BaXbM!P)xgrf(H z@yx>Ilx|KnKX8eS+^bF-@d}v-a)N_?oc~Q4v{TC$3vkNROi#bZ9vJf^B&@8mrv8cf z{Fuj`x5wzwkSydK1Jj)r<(e)lp7NCAm}xSxbP#E+O+(~B{vY9U=-&NuhF*5 z#B(d?iP=vEdLQVy%qS3D*ZySx+EP>@Y*i~l@7{Iz8$0%Qp1yCZopu|X@s@upzAAFc z6_yeD>z-J-*xIg0;WaDY^oMNV>OfiB57_K~5!+T0@-JJtXlX>?%A+ehHM$aHH1>q6 zI9&H=ywx}-6>Ajy4y)SvBVtrPF>hhDg~||(MQ&7G@!^@UA zEDxcojFu&#x?wl{A(}F~sECs5AWpZNut9&KftltD@vBj}t=IhS|Pagbmb> zja0CsiN6~Waa^C&v2yi)xhs=x|?r(8o4Y}e5DmmI=2 zv@A3%42lAMgj=|UE>YYDSK~Y*0};}@_+0Q-`Lw-u9e)?dk79p9;0&A-*RE|Ck zwLjvPYnmZ1LD^=pqX_(YRhZ1+&{+6$wLdb)x?(bq&@HIJ zq*y4U{?qT9DjWN+@j@ffkkSymVKH@~TXs@YRBm~3G6S;D`* zUAfg^wDB6eyf9Aw_oEV?$>L@ROsER`{NVhS3azcYUDSDIot-e_@!}^Uu8$q}euNlUr8Ne*I5+5<= zaw!LR@2;`r4$N^qCB>WF)_x{A)^`=Y*|svPiMrK!bY(}*=M%sn*g;;C?^gNs^HZX~ zGZu<`-Klgyd5;tSCg^L2Tk7ZI3zzp6Nke^s9PW$5ll8{2kWWQ{;jV~#& zfiGk*Q`S#xSe0X9xF)B2K0%Zi zOS2h97y5V;F7QteltWPhp-M2_>oSj=_tZ?}%PWKTi08c`(*ej1pSl!0nzVT*%9ZN< z+731>s!JY*U;=lk>i?FT=v{b?I(1G$pJhijNAt>jqtEihKdxXAx{OgP zV7G*KebsuM44HQSd?JZ0PxP>S~*r8od)&~0#N zhlHJdl%|2TIs1oxV~4f&TLi-A2WqihGzZ)|f>TGf9ypn=+$J{S>cR#)ROgz#pFEt! ze?)zhB%?K*fV*yX7(6V=bbb=--p~6=U0$1RyiRwmeN$rOQ5+O&BU)0Oh^Oz;v8y$l z#I06qKVIT+R-egfPFibsz9>RIyzlXaZDKC>dzjo=ID)FL0p5u@?0tQlr+}2PD&A)> ze9`PpjwtLWK{WnX+wS*{r^hVVmk>U_hIq27QaG8YBpMr}y^q%emq7ulm$c=AgJ@6D zru%(vGod&)CGG`(dzqRPOE1mqG3#=Do>cNl5@Z~1CUfBn%DFdanEvpQlLQ>}e9z7} zl`Q9tn^DP2sbpj6D`#o<42YHyw0j&B>5+TMaLsU?&ZoUe#f0;KvDQ@MZ|Vm_*hiRVp;Ji4Z}p?XJn z%y4pS6$b~-4^?|n!b6h0-#@bdnX->`x`K0V75ja=rACw#+D0iGPXaxB8LBc+79u?s zpH@caQDx3ew;QOWeZT=7w*^92Yabf3nmIH5Me&tz5sWIQk z)$5%6kC<;kvE@gV>t~J>43j8~MTOouVu7wqmd^S5M~)wrmLz|?lG!zT(ePU2poUwQ zw$Q!MoU=6BRLSC5%wTi%l+chvp(Kh-yT5dI4@ltbBV-B;+OWB%ipg4uifMhUnof%5 z6Cal>oA+qhEjfxwCV(^GLS)zZ`}@TFxMj9!>EfC4vFJGEX3r&Ua*^}a>(S-kyEqqp zoL3*P^|+dEn;^s)eMx0n__lD2&Z}PR<_vHXf6d1KG$l!f);RS6Go8qq5mWhh1P}SP zn{&QIgaE*Bc(8XQYG2LFU@(5n9<1_vO@29TrduU4^Vf0Tf{4+ypR&|duaURU{@bx# zK%2q)h|j3hi2#) z>XUo{y@LxKdkIY2^?0?5Lj_LH+8w2$jM?4)$1+Hv`YR7+bW>i8pAE+3Iaa?24hP}%0QV17 zqh(yy(auP-vLz`xuSi<029K-ZZ#^3e23QE%Cy-GS8#caeOtRtz(6EO()>ytO|NdWR4sQcQ&_OkMUJsIck>sZ*vxur*xVg znj_b2Mf>Vth@|68bxH9w#1&4ba}gVTw`*j(s+Le)@TVdQ9aM2BTS*30SD6a3vRDZ2 z3cyvAq=CTGeKJTGk#J_Hsa;*$bObHKDr`;#2{tiwuV^&0LF!QAM4kJ>W(1MPNd zp3T=^n5*=?kzBt9yYH6nMjn%Qwy+hF$6UcPq|i(atJOaYvgCWEDlRCyIUqji@?2-K zYzCUK13F#nG8bL6TT12qP}Rhe>Y}+PC|6Oj-m zy>8Q^P^&8?xokoQUDU3kX(qLs<18(j;yB;k;7s3g(iwKSzf-RIO51WVJf`nsh|OiUH?FWb6$ zd;9zQdnHV;%%Y&5h#)~?2!|!9(8V-SSk!zL@QyXB7Z;PocfziE1ZGBxzfP);4Wb@DT?@o@zvuS)k0WLloEYuFNti*lSiNkefAJ|lGklFLGMEq_(JV5EzR)AnhN*)FyLpP&MI8T@kmC7v*h`m5rGsp($nV{;elKI`ev^= z(`cQH8}w0v>^4nPMU*$~qpBcYRq2=S#7^CYqN@y6%^&id5e~j3LL(fSMRCeHSrTc? z{B99*IITm%r$|#X_h#^%O^y^}J0{mlvIF#L#((`YBkGnU!YKNhrl7$`S__#}A z*q12Ls#2cQiOF0DyA`v<$OG-X=I9`;r)}kiDyX}kR+e7^22PVAhT;CYvs}huN>naS z^TnzLyN518iWJdPk}LfK$M3mZUqa}Q(?4Hl(+3TFP3#Xt!oEU_)E=G0`2UA-$9Aj1 zFSF~MQ9MqM>KyFoB5uJ@u?wwQ?;K#%9*6eAi6H)MHdEL1Y37i&jZc5lvpBX~K8bSq zwW&Z(m;K2T{mh?Hcwu4_2`yl*WFJhd&5@blS%dy*?fWbVM*2N#8g(<2g?#q9c=O)I z-`+yr0jW3p{uj;eVNIFDZwB9RGM^dft)Z;;V}pv)++W~wJ4^W^eRzfbqgGP>YtOe{ zorB4?y?#Y)OVhxC2&zvAR0-=;!b?>7f@wj9vX>En_cribdlkdK;Z3W{uwqYiuQT^f z?ljBw$>bo%x%_G+V%nCj5pk4%UDRKIVi$Y=f`R%2hxr`GNEqYr zBX_uC6Gc~n<~qjA?-}CwPx-=TQh0Vjsf>74E(Jqc)5yUaM!gsJi?he*e)pMniN3B5 zA95Xxp(jOuXSK6F>#Mr6UynUbuXvvE(;iMh;?xc{crfZmZMhZ2HNy)V*rI_z}r4r&df5n%AS0gSj zeM8X=;tquoOaJ~icNAkXf#2PMly&ky`p2@YwmB&!OS^m56chnC7AjijAAWB&G#_}~ z-7s&rir3(iON+y> zpNAH*h63%Y!9cnP%wJP7OCohH0xD+%69YP>v7`LU@6)amn0m?2y0-B8S`0oyvv z=q=X`E1y*V5R}Q-8#zQS{;_R7kE`+F?X0MSRvoaN?-^fcc*0F4=h<1rmNl5fX^bdqCdH4M=_NaJ$&V;3q}# z35IVkAi}+f7MuuC-Itt?KOVUz85sQuFf)>8W!uJQ<&UNQ_lMjt80V}4t0*MJn~OHa z<%m;om|;jhSQuT(!hbffH&tsWK3fN_vS(wg#6x^b7I`n=+zxWPN%6XxLp>of?)Bx5 zKfH)B-SCOQ-P;u+BC(;S8dk{XgDaZ!9K!5RokE&&86Dq${9SiSKEFg&+KWa-%eb^c zy*zj3`u6ulheg)DY(LxAbL2rx?ISC{Wr4}m7=?3k=VTfUQFA3y^-P=;2meq_Wy9Ml z1^X-49?MPKEwH3R?>`{Bm5Q?!4lQ170ox+YpN)Er=v71D&lV@VitMtyej& zzponqrAV}ke0CV9rVJK^rZ^MKP6fATzEZ z&vuXgC!0L08RWa-=`F zud1lhI=6oBI%*yGaZGXE&(>|g&FGA%JY5Mr_xFBp;%!l6TW+CQ=PSu{!A#l4oFVs( z3#6RrG}ZU9-z3egwG8NiHyXL&)2^g1_xr4-toL;2)gcUf{K&>!`d}T51wos1NkOUP zVYzboje_hvcpS%j<2M);@%wON+mZi%e~@wvxEkr+j8ck$y1%lUTaQM3zU(m>=MU<& zFNV;hgT*sv<7Sk{+u(}PzE|vnM6+eHm7@rvVD9WTPLd^%0%=_nuVBDOgqu(f6W`PK zshWjHvzn%b=wp_gb|VAdFaAVSXHOG!;<^3t+)hVUA9{W`Mz<3?2V(5NVW!(z-W5j2#j7+h_6X5 z8lBsyn!WF}Z>NDzuSEb-vqR2t9vIG0aNX4JB^g8Yw6%VYZCJ%Y(UNGNIt4s_L#mlx ziRIx9k0v6(-ZZ!C7y0^v&5kGNbh-Z&1I{JQ384$1yvgMU)fG@Uo!>T)m z_9bkxB}oI*^1FK!n7#|gn!mjmMr1_lntw#kNlB|N649GCU9aeRZWAAOCO`+s88_0h zkog~M=RXgaon8ls{wYh%e|K04oDogbn_`k7qur*CC*WJ|rfDbYi23UmmVB6y?m^hl z?V5-Z_cNr``@&iIh~pto`iPAxy-QPw>2NHB^#vZ|Bh_!3L=O$5oa>thbal9pKYHqi zhUba!zw%%HPX0Yp60lus`Eq@V<1`QB#j4#a;qRcNqx6JtJEXuq!k>D^E_LPknd_r~ zL6-$B?=|KT>d&y*><(Q3Kao>j8Vn`A<;#+M_3eKoU1NA;U9+9owr$&*Ol;e>ZBK05 z6LVr?qKR!M6T9Qi?f1LC`#IHhHtJNZwQBF4?@92`yx|D<=8(_wIeTtr> zUVXDcTscgO`o&xrL^C4xy>DxuB!=1?x)O@#-hn6I{rEUfZ&K4a$y5U6;J&czPV+*s z>OL?OouLx9>c21G`5yQ>TH;K*0&~4$%CnSAQd)Yn+bs&W{lxdxAokCkQuyuWQK=vy^dLXX0H& z#6iB&VHJsA@(8=xZp#);VIrqrGq4L#zP|W7li2h9J~8ZV&9MmIdqe%GV;2&=A z>;-E*`Z{+wK)051RqZ8&a2<`Ms3$D$#Q*Uxmx(98r+X5la5@_@SQ5Q} zu_L~8Y7(E&+>ivL@4EbJjIuWSmFe0xd$%VLX7NEc){zG{= zq8eP#^c*Ahi07R6zZtkWJnJ#hTnV;@bEJ{w>kdr0MomHl5Ofn1v$~OcR6}{Ug`dN_ zXwG!LHUBCJE3JZLOh2s?JSABEfJ%e~JN>`YAe#x^nEN}V3s9TLzMkzHM}~+g*q;J&=7DW|`5_iK>_)X|d+|&+*Fpc5i0j7B z5Dj4av~<0wVZK+Mx;)+!XwLYsXN)=m8JHK-`T2Noe&sQKDgQZSoUPoc38gb`P8(Yv zOWJ}TG(dBHyREbR$Kd3&=ESFHs5^vrsbU<$;xJWgWq75#ys7?cnsQ*ROdRvV>HRs8Y6svJ1&P1t6$mtPI^$~omq@o$Ax7^(6@eLabx)H_yKT!TXETj zh^%b)fhRD(C?Fy>ycs{OBv`ihG;TPXx3u-jGdc3(RVRL>vj+-}rH0zQF!Q`9jZl8e zAA)X!`^1ie-WengJmsj!_EnUj$CM}CcqG*p3JDY{0&E00{m%b+?1G%kwWz1(4ar>Z z^ZutEp<5_jGda?UQ@n2l<|1#FCNhNORxo!>XpClX>Xjz%=qm5y?omiRwZ0jbr=lOD zcEWLm$y+nb*ppRfl6Xg2!dX4iH~r`df%?*wV)24B^CuT-+)lsOqM;jOA<<0_!5`L; z$vh&MzdX>HPn^^|#f75qQfQT#vN>_j57;%oa1h>J+<fkd`Q{2H+l_dVpZznU{ z@wt3*(*QoYH0A0)3Q+Q#&M{wqUAbrOgz8UW^iOVFz|wPqa~WglPxuDc#ba7)u#I^X zKqUJ)9``*d$(*?7LEB$gY2nBTMpGf!%4*0V*HYXsS=6fYXzV z)j&d)`4=fn>;p^rwm);;J)@eyX~L*tq?KEi&PhMKtD*o2)mRv&%VJqZ4YA_LcWp%_ z%Vl`TMHx<(<2;|fWz~RyLp`pjv0i^C@w8KTUK!n~%|h?1r9T|qDmG5q@5G|T^6xT+ zoyqWD8y_)Of&yddRbp+oP92k-ara#8Eh#ap6aZ@Pv6{6kp6fE>HU=*|2EKoKHS137iB~o7b;x7RA5M zq@(6p9_+YV924yFh6QK(Prf8^&L&hx3#6(8^w`esYm2C{f^jetY?#cjz|gZ((93Fa zjTa1+Zajxq?ZleimM|Jx+i-^D>p(G&wV1m(}1(tGhuNX(#jqIw~jBUVfx- zU3C!@m#3aP*;+_7eZ&TyHWY<{mLQ!&JimBXHR(eUnZUkxaK9PVZh@Px^^wxqo>O#% z>Y%y`O)DkcnsmB9UaeVOYZ0Sp`1%0y%zpWTojX_tDf|hsBt3Z!UkR?$yZiBl15RsmV>I#gmcET>fnB7z!f(TPlQ~M&Rs2T z$N3GD*Pym7l*<)dKNz-|e6d&lU9(;^X-cg#e8tmb)Gnm7OSCncEtPJn5=CLS9A;<3 zdmv72K&OYle6v^nb3^ZvitV(4z!>5y&IxUtS9A0V&1&`MsBex1tch5cszt3dRx%|4 z^UpUd`D16A(@~mLDTQ5(8;7sIxD06*EI%o0DbYPYi=UkGbmseoYxdx2n8cMj3eSu; zr~*d=YIEEU(MQoLktTSk|`Mne_^qh-~09O+n*o45pDOAZC?`_$98s$kCsEq%*8fi&%!U zxaQ$pU6kn}LMtl$LN}5%6PsQ@e=)-=uRZps|6Z0XXg#>pZBPz*m|}<0x9vOPTpK!p z4Z^7(SbknB`vIf1o;Dy*Ix7Gw(k5ncg>EE%Cn415-rxkIh6`jH?kC1gxb4x|&0v}s zXyDA+0gD6F!sKFI`=OL3a22jg0}>d-+YdlBrR|SUTZaIpAw!y zR`pvQty{x{$#Hb0VP_v;3%mU%;ssjZSOuTcL~^+?4sX(;k#uDR6&64!pZBaI1S>5z zf?gX^`;Y4kJ72U4=|KBt{7B5_#V*T54375bFrrp&JB?N8rtrsPMLpH9N0+rPzs=4m z%+mDKxOW(KpJGzsy^QU+k8hrq3*_S`**{I)lj8#T6ZmEOqiuCHER7K}PAISTO|87o z$y^7k{LpS79&87j6AQopRWc8<6W@zETe_Fw(Q~cos36mZWkE%#>dw#;Ub{KHxQPHe z{n)bgRx>p^q%MLwRpdApz~oqafn0~103o2| zV)}RUFg#9kE8$el-QgR=+MrXK()2D^Y!&v{hEx=vqzs?Z0ea%HG}!_xM)9H1V%8Kd z^?CVJ-0M&&AvumiP7C%%($#I~U%ed{$88jC%p-^yq16yGJD#J>9J3$5lk-m{>(aT* zq)h(Jd+*e!muo?2g{ifP{_=vtc`46eqb7TK2@VJL$l?JsOE?8w^a)h?u#{pZl6qip zNO5S-+keK`nr1e_lzM&Zu1<1>Xks5Zrx1FEN*{b}@l3hCa<~KkunESL*D}OFh#b(b-2!Ysw(`-9TA1P9jqvai4@b`HkpyYE@2BXFv zr@|>C=Rn}BhE^T($z*>=a$xlkLrPyO+M*Hlie<0!6^Ol+Q)6RTVdF9khnmH zLrp(Wtuc44Y1Ox?|LN`C9aXEcev`J*yyWpd`hnfZVxnhNYxx0^VePLIZ?L#$mXW6p z0UrgyQV+C$n$oJ}YDLZv(ruzW&#!boX^C&Cmh|_|oN>lv_JO)*zSho}Bs&1t=}~{2 zSTE^;;vSm1V@Tf`BfkFoWf7tV^73W(^SfW*y6Teja0q+kQQp>I8^IYZA(k2U9Kt57 z<)m(YH#y!_{QLH8j~^X;XNV3ghsNdo0`n^JIAl}}?ULYg5+SV1`Vz9q*gQ}8eBFe` zujLKBJ(rD#(D1xo<){KC7N`S7#~+I<`g*|8)t+xvRZ09;Bv^9?vOEz>9sc7Sm-^od zXUZaJs!VBu_NMa0j3yY|*;oHrV6waIbRv)XC0F$Vj;ge}G7PqkEOK;}mEF`n z#HlcIW_=Q$5S9izLZh^2m<><4>fa(UPi7FQIo!0S4^si}LopLpgBv(ynv92jwJdvc zX#+Rb?dT>y97o8#MsAUgSWUZyfu*N<%z)Vkmo-6jXQL3ojJ>_c=eD}@dLoB~4z&Wr zlXk1jIv06p_c5PB66Q73UeqaTQw)L^q%mA2CPxSiRR@MotAQOsK_el$y(@*+_5}p~ z9~=VSF*Mrr@Ex#4zsiVff&s0@j)cx(pwng6qdH zS_=CU0zpL%4|Qy{np+9PF!t&rP9s7#qX0RJ=g*tEkG~20P$Hrg1AovI59$B4l`8d? zXtg!0W{;`UCXPNZ)wd>GoG5TNnt&Asi&1FQT0q^n<}D(YZLxi6ST2^F6!?D*I&xWr zm!^Sa6A4d(VhCW_VER8`)siQ#lS~j}vx8INGKha<`gfgM)EhB|u9QJF1P_+RobN9? zNosj6fr^d;YKW(5UtdeXIQ6?pnyRj1y4^|2&kEWmgUZjGvpxJ>fo6m z=LqB1Y>IRc99IR0q)Rp}C66Lcu|c53?9ncG{b5%G*3-s9@+Q8h6QM(SVGIr4hHzO& zY26JNKGgwCdTpb#JbXji{prcV!qYhO%JHeeQ+Vq@{~)=F_v5Fs1X9f_^mgk5)mW!z zu8?m$-9Il+Hk>eM&6ZFk`%1V}`W#BePU|=q-ltljXg9`~)BJk3@hb@wEQ1TfhEH=w zTbyY>rV??E_!YavfD0a+jRcX}c=NEaOVJSX{<#mQ*<=Izv5t`xK?=N7+iK^ZcdTQ) z3OK8s@+`xM(=jnmOhl+6q2Y9uDYiK_ zQY%zPN09#@!U~qbN?)3jX5y;qQi9;%+Sh9ANG6pQh4n`Jwd+1vD?7@#2wcXmT&8A&{taMhKN4#4pqsj#xL|8iJO~S zMW0+dj$Jks(xqoe*%8 z3qS*>YoPcjQoM$_6q>|(=K#?zAaFayNZn|yh@(mi80!H*#*kVP8Rq`0_etZkn%nlx3g%ov_D@fb~W>ED<&A}RKeTe zAHi1CWPUjvPw`|vY9KMoD5h|+L+aB4)fRMk3B3dC!yK7dj*!ZK%)(!9$fskZQPxU) z%XG)jNIpa8xPECbu-m@r=%UpE#9=CDInlv#8U&trY3c2LIhOoYpks0}JYC%(04LTm z3g74^fDUtL=7Iu+P72khUf3l0EH1TtK+3Ovpj5x#Jrmc#KC8aFVwOf6$~+XJu9D!Z zsne5JL+AmeZ0mm6X=;$kyU*m;HS3) z|F=478+nL(&UQt!BD{Swfb-YPt%PE;1bYr7xL6aZfitI1VE#fK&P|Zd)u!z&HKqg5 zFPVXUtWNlG0HOGphFBphRj-p_LHy~SLoh$@>+l|`p(X8@!RC|@En~6`3l?%zVuXK; zTD;6|T~E%iy}nbz`@Y*6R{LNib#NdRmGBWFco}fHM zvWI?gl%eNjB*;iaL%`x`Z^10NpQw!e@~Hz0WJ0ChGvjw~W`-9S!ea2;g~9n%0lg?t z)1GBOt$T!yaHhw5!zE=yQ*!z*3(k9}s5P=;>kv_8LiJj9q5Bl}u>pL@QWqC@O= zKb9^@!oaL5YG6nWipZ|PClCdi;P%O6!MwNTLlDeUwaN@XH`6DWT9)iD#^tFXGJ(Yf zJY?wi<^Di$#Z8NN=-!@Bf_|N+>*%|K8-L9svVZ2!CUtg-*MUv#j$a&2G42E6<;cKA z8SPieAz>%fh>Eb?mJp%|G`wfGR5||{@#bVGa4|C5ovltOevU{~d}g87gMbFj=N0gM z@=97eHbHwN&tsem`fgih!4pX7DkHPdoxePvc4Z%8EXVZzajUq0>(!@K&2E)IA5#^*P*2|9 zZh!R~Xi3|jAz*R0^a9q>h0`$J;T-NR(L&AX|2d~)p3jiP{;0?8x*LTeWaOo4=cTd~ zFe^GBB!P817Y@VScf$Rsxubt=v1H+R?+0XBJi3?1xYH@Hg3xP5aS);)JjJbB8n3_x z&82yIOFmwldIQkdeR`0ozd^Xjp;y|n3GvUzb{yrmxp)Z#?gQJfL{krkUO!`Q{nTkE2-Me=RRRy5!Az zYq$LtC8Z@q*W^>%*kl%YS*TtO6>1{Sd-sEZ?|9kUKTDVDd%74rhcI%Xpg#_&>ao8s zxQecJqvkW%TW!6=a+QQ!z5h1GJ1z?+fdCmg)c9vI(-wZ&z$oB#ljYH<-_ZU185ypl zLoiAH7e$l*O{!ev`RrC@{D-jB9ENS$;`&^acQ!ZoDKXj)@8H-xnCr|s&3gW#t##NY62BH^ zl>u^Zhvlj6b99k^wi0;R!etR?Fs#9bfnppYCAJ4Bc0R#!gru}|I^ZG3#mVWs@Ih{5 z=^Xeg^0gvwj}Ht0UpVafvl}M34%~p`GRsHzqowBE9Y&Hj$69WK$TIk&!AhmjFN}Eq z`0){vDD7FK`X;UFFwhzctTGWwZ@tSX@Owca*wuvKQBhZE{;3$OQwy z)rTKnh9lLd386%}SJvX3_LWUfoKvk`8=+g@Tqv0_(S`#H`C1Xh<@o1>+hF_i&j!!; zs-`fpq1f{RMo@P2^?vW%h*1OJULCbfyj;oXC!m889i4zCCwU{DmONBj9=7=C zT4?VVBHB1t?N$9^+6=l!_c^m4Q}FoQT?;ts0WSyv#=@{6vEPK4n_}{QHDA3y7vZp> z?!%j4VmT&V?30cUWMW{ee6y2-m66Q=HGR-*<_g0o>TW3qJ-koPMj-nEOnWInz3M*m z^v3DhWODvm_1!$xt%#Y+mP%c$@0eEqE2Z~3!4JZ>n*spq*w}7r=Zv0Kx0le zGNcY+l-kCHzS-=w=p-lXQ36a3|2{z15xY0d(g}Q&Gs9~5(WO=5PONtAUT{R)KX5R+ z&iNF^XhKJ($UYKgiU^AB!bz_5kycK;_hGwBfxNtyrfefWY4=8fBWO)Rv8>3 zaioM^I0p+1CrDIuzqt1#RS6had$8z)xQaCciOjMrp|S50U1)Z%WRpuzgiw^{8k*H3 zgRwbE3JV$f+WtH6Db|ti>oHcTAM^5B5iWWZFSsB%SO9wz$$36H10qo~PLtT1UAG>= zlFiRtsk-4|759UxZn4VP8mz6tPiKLpaiuco)@qh$1-|+h@AKluQ70g!%8Zbs_ya2m0Un32G;LDDdk*&}nQ*+;JyUf5Ja$Rysr;BbBP5xvKfy*RCrp(8`d;Vp+ z6RVsfUy^A-)79CDPXr+b`iD%{mAZdH-9K4&#Yxy~GAe2-?eCurLaV+bZmBdwvAN+) zI+Wj-Po~Qoc5xITnH}52Cl*c8bB>bx-*|9T0wwdlB0bvAsnn$Kab ziHy8Zm43*zsj<1qif6HME0uUBJJhCBvY^6V3#D*#3akqXHUsAhb{js%@o#UXi)iVJ z4wT6D_##dtHdcF5etkAqxA5T+x**UaHYkt05^aAV5LY@@EE)%YLmI&N;S&O5lYaW2 zwO);d;OHnhV^C{ml%Wo=U_D(3$MCXp;D0)_Q|xKXpoAr%G7;)*|w_Hs~@=GDh>;V0H!2104HnRn#<2H=6lhdvu(T* z!~kjSe$rZ=7`sI&y?9w+haX;57GZZ;?vp$XfdP8vMETQ}Gjh@22Gr-zl17BEDHa~w zq8)w+Tj^T~YVbg2MlNoOCkg6iB>r$!DydDn7Jt*H(y{YOUHxa8*RUYEZZEx7mgiAh zl)CCIirxYVKG@qRW6U4F%H6m4_CCPILiTLF0;%8mGeZV1evLOAt~46)I*93cTW z%Rb`fpPnPJQKA7dh`_7CqZzn%oA=a-!*%4ig$%}0f%-VoU@*x~?606#?_ry%hH6zp z?{<~#h9kN-OpsTSdefX^D{J{aMIQdj9rlr6(7Ih+A_Lm973`E)0d*ux!d3zGr4w|Q z)YRBuwGz8fpc=5JV_#*X>HW7hEt+7vP?h#gjVYm(kc*q`dZ30R9a5uow*W{z+j*S3EB9u%Xyydr;M0f)%*ls<&XH^ei zq~@;3%wVkq0s}{<4~zDUu5YG#-(+W>UBD+)%BI+~&E6KZ1SJ0a7JH@reFzkO=?@fi z2{7_y!J%Li?cZO<`i~BWDfafk@6_Fl@P~!9_whtR_!YODaS)&O#`Z2Em>@keM0i(i)8o$vg&LojBs<;V4C&{V%LU-d@d|GEtVDW01jPK^Te>>u<^Q`whD}_S zzxar51sOGWnci3ujuh^l=n5Vis00VvhHBYxR@=!Wz=J!j$u}%8#soOfCfub1vm01U z23CvltS$0tMzas#*wP_BU8D$LZ<>Wq5gt=gK)SfFSrsCqni2*0Ps-YpnS`J=lG@m$ zz!+$nUd@wy4A0=ybcJPUe;?pLZ=^Jt2FIUu6#NKhf0UZ_ti4q?S(dhnGO?4kF+hrQ zi(t^-r>xPL(BxJYejmSw&v3~S0~FDvW09jW+D^xYgdpHXdw}x4*h)kCtFnPVoy>^g z!B#S+hW-=94ZaocJJ0-?IEq}2K|2*96#RNWJhJW5T4G?l^SIJfB%Us@BL1%VT@nJUb{%s{PY{go0N+Sljk@LssdnCqN~sDUhb8FaSs)PS z+})*oF&*_*NNX_qq|^vv=QVJ4VQ3wn-YWNHVs+eq*5W7Gj7zwZ&KRimW}MyDGLs^J zTrCWbNwExdb$ z^W9ox#ULJ?;Z+%$LX{bQ&*prlB@<=Y>AH8P%b`PzNS{zhEQCy#Vi%A!oppJE;|FOL z4{r36RM1Q8Rliz(g1IF;*la%h9~-_mBpzJ9hxhy%_^}~Sni#Jh*gk`)YP0*5x^J6E z25^hivNmU8(4NxNZBk`gs@&FxPK2oB8-z$RRTc`mnI8Q!I!G#pV#PP>OI#wEQ6tJK z@<_1Td?VGr_R+k0H9n0BUZCf8v(97`$blW#`$ogFTJ~h28}vG>`5@K)-eCBiV{;A{ zG&kk{aF^l zPM`1~4udLT$UIMx|6g&gV!dkwSjmLGeEojRATv3p;rr@A@m{p%pfr&+)jM2pbG!dt zBGC7NA=*Tx|NFp$#9(Xw=gcm_4hgWc~+kt2U% z3{!_FY*}&Fs!u74-PL|)xuH&Cg8sa?AxrwG4~dRVO~~QXd;XdTT7Q!D*N#aD4?ax~ z)teQ1M@e|PFMMKL%nl{i5XBm5c@9i1Oh(o!&ea3xoiUz0EW#b=6ezBmU~DqBzx1$fm{(a0jtCux^ecdYr{t>920{ zDJ6k&V+>x(pX`!pXpy+`Hao^BmW?vd^rpRUb6^*wB}-+qZZ^JKHE9bSfghS z*=7ANbwN7p3>!VUb}|LmvZAhRYN5u(DtvuMEm$H3_t+G!00zjOU`aA)Es|HixLu&d zDSiIkV(jQ%l??o1u9@2}$Jcj{KWIsAJEIjri^y5VWQ4buy$!fKw=&x-vOAuVf}IlI z2M`dV_MW1G9KAtNq2?klln1#J@|RT;T_w=Z7Crt#MnnrkH`u&JsF%)2VODg}G%YqC zR$e{%RQt&9l1!mrLwr+M8~Cv1=li1|0@c}2xzXopzc4et{~m{iXQS8@5aFg#nPVC| z6KDT?rteTnohI-PkxJ>)RyTAuHUaX4A3&Z(bpGDf+YZ1vAO81C5RPo`LuHVs4Jdv( zep~3dMZ)y2N|e7;(P_KZ%oluR@!z36s>*v`&YMxWTvx?HZzly7)F?6vP5KV)tpEN} z1N1pRiMQQh!;8DQenB{@b71z*g7e+F1dT2$5@Lo44ezc!?)VlLc z8jMDf@H~g6B3EEV&h6>a(oGPO%IK&Z1RQ!v&?)BPX4Rz<$ZCMa1q;M`l<7TTc<{rF zw09U*1CKWqU?K{JVR4sB)h#lm&`OCXeYWQB*mH^1QeVs0j60;ra!r+cm7zlRXP;Pv5hPoYPsF^xbU@f1&a%-oX85mJ8})KL>@lS-h#&=*A$WVy`SHTZl}fYNBUUohMI;whD2V zXFBl@UU8-grim;aO{g3q0RapFPA)GuYFW7N!Z-19wQaP!tWcYmi z+YUzBgL+TBEUcn_uhD~6h|$8~ZxRtl^vIlXf3css8@8;4PbBeYk#a!0taa3W8Ad=u!vF7N?q^SJ7dJadN{Q{J^tB%p~RF zjFs&}h;vQAeQGu47@{rEI*qex?p){hk>)JFwp=7e8~r|ExM ztS-(3z6@FZHUBQ|n$n1VrMKos&GR##*gP+<{2yuwW~LZ49@V)V0~f1u7UX;uBwJN7 z#|frA{~1*zwI~Kv9GfB(F%l=`XAydGzguM48YM_;Zd(OZU~z?YC@S01j$Ifd7}P;yD8-z_hCY=Zjs z_Lob+G!faV(=H_)Kbl;m_r5VBbK|F?S6fRXdHr>Yo0ox67I!-#>Ija?ZhmGaCev(I z#hxC5jc~^un`(t;-&(2B+hO(#{7~azCKrS42;ML8^XfH6k?`9jAu%jbJC-(7tdY_= zh{ZO^vCbLft`O-_M=p$tQUqPtSc-^2AtoqccjksfL1meOi8CM@z#~mcCQDbE$KD(uk3?@N?S;Wu^rmnkUfGSF1*qmg>0Nm=J#AtuHX_^m z9u>u*{{_e?j@bu@bgi6M9i{O2KNvuU(xDz`rv8>xOBPR;WnlH+ca$bRt@=9=H7UQ| z@mr`*O|D+G!OFb8CT+PkBPx6LF&7JrxG2wKtk6Gjeah@=0}J#~Vg|R~vOa2Iny)CH z?F;aUx1xM-$y=JGc7oLbcwOByCirP*Xc_ciJ}ph1Pg$j3&=|~G6*dQ))vcX~8wELT zGni#cjv&K<;h;d-bsz1`B*!E_2hH`#=ygIoE%-#)+>!G|dD+=h4(pRLD-QXLf_)tOW+hbm47DJo~vC&^BWv^?5gm-AU_1=w?L%O2WLon3~+o-X}= zceVRJK2*Zkxl=PpzRT`$!E-FfYB+aGO1e%$g;p6Gm%)>^Ece+EV8k>CZ9kIt!{xPG zY=xpavkeauYy(u*y%lz0X1DaK!_JoG&2tmV-W0SB;jZ-|t@h{W z%Ym>+s#27`uUofbuNklZ!a56jj&sjmRpk8JzFSkNKxw(5EJP)N2KDg#_dzeYVek;r ztE7sSLXXHmu<9=2fK!Hk`uAEh?aO%7+k=HcExkgcWsTS2*#i8k6=4*OO5Gv2>&Q?8 zIB^6>HU5hA%aj$jU0;*g8@U9pu@M_k4&%N#c%lDl>*i(fykphCEZ2U_N=+pV>xcu* zoPgRWn)I8i?4e-M11I_A&!N%R^2{!m{uQPi1!NCzdjv<{!6FsUlw%K|H$g#BW7`+xhu?RXVu=TK0`Q5O`%I@&ZtH(sxWiy(I_8=U22i1a*JNE-UdD z{HwbyG=dM=dNqj05U<9A3XK+^Sa&rWJ^t;)=;JaW?5D2yU#iDPm^X7TQv1(n^J{dG zC#QvG5Lm65zSyPe%-~%W9T;lrQvhcp@0NQ9E_BwSexBa_I%O4T^yGR&x0)0*jZV1n17RJkeKW^m+YuirOG$IU}*~QQdibjJf~&rL~2(UfQIHj?6NMh_kZZS z`EWhc+ve%A$OM(|3wq*XQz%#GHQjILg#n$La28Ju_sh@-O{+ z5YD;llulm$>?&yJd5SmseR)ObdZY=e%U$E++!XbBx7814+qFySt(-U{;?K-St0G zA2#h%^H5VLP2nIzvx`~pRPzfKbvyi>XWx~88vzXgqFcCR#)EN1oHQ4~$B^0} zI4vibtNi-j4W(E0Vc~_0hFMb_R@)hdf9c=vNZw zEhfkbEJ0aj$1jb4)t{egN{{tR@MUzzb!1H-(zn4$=vXUcq)Tn{E6ICD&_QlG8MSc_ zlu&c%17@ljXN)dVt9K^AD{ENu~8Cu6Gcpt~2t1Nr421IQ|H!hWDnx zRr922;P-zOvxje51w4_#eD*!fOkKFQ(Diq5dPIqW7 zx(FT8!$M+ge8e&^0_!NEuU!Xp?zX#8C3RB7eouP+c7k38QyVzo*nh#G1nwqh&js3A zQ9dVLiOfvcV^)pcD>;S<{z`0$@rddB!r)-_Nc7&sW6t}QnHipaRR};XJvzNNzIIk5 z%JN&Fx1lx~Un3?1m^p!#R?jh3B0)VhOj@s1OP7=^551$<35xxzxMtE9AqNW~vFV!b zk)i3c#fmk?Na0u0l_b6%$9g;o-V_I(@F2|CTijQODX8P?ZQg*%dRHeYJ z%9fd^JF5^p9rcR(0fs~HnG^dMK(p%R%Tn{#I-^!jb*IyEm*f)&Y#-xqkol(rtIf*7 z&%5$yA%5EY(D3zgNF#aPY=+P2C<|xxc{pm*R%T<>B&24i(q{(|pS!UhV^6Q^>JgR4 zb-MCJ_ThTmG?JzM@t;FT%!;cyTEu<${2o^Xr##P590mbL9+o8xi zs4*g@;dM=Xdp8nToXRhz`N`Ylp@F*?@fCY|y z1hz%MV7TVd{CcD}tG|iQu$T@0868qH>Kao~hG(HNXz4#+t514*#G{3vPxl z8@t;-a7fjM9_`Mvw&t>z;&ap0=$u&)2zCC5JPX?2+mI#saKKcbMSnZ@t%-XAa@lB_Ce&9(vvan#| z60(I|$YN^B68W!`^jo((q4`nKew!z|ib=S|Zcb72j{PIE8li$|R z$VJ%SdGG8H!ToD+sos$``C$xq`LDO_8m0i z_e6&BM0y?pXW3UYB7Wy16?f?QHsi(YKu;I7#tg}{(9kktc1PEgxQqJLo86E3g2a-_ zD2xMc1TX}6NX4M3D1yyR#U4OB1&V}T>ELDbt*P<=bAmD`V`sf{mR8~J13o6KUbQdD zKvSK`!fGpP^RFrBJNX{(1D@XN1$%?Myo4m%lKq-4jB>+K8;ibiId}OE%C{N>Jf_0z zGHwPvZUi?rfPNrN-;@3)+|j}EvxFw4&!lS_@i!VUpu+DM0jFAd1RBm2tn6QI<+ z-`=5Q6m;Qd4zz$C3+}aT2h|UkLtTeNN+jMLHC$-ghy)vdK%pFS%WzrL+j%{E41_(@ zdoj+P!u>ZO@J6V*I9f=8u9j2DDE)!|`sQubn<`CYh#HGfmz_xGJ$=r`ux{E3t$Tch z$h<%cUm0|Ceg=tkXZ1`}Vb81b>03d}K?M!fBLgk#Q%w(*K`x5Z*WBCDq>P{X%71S% zu3o&T0R*U3UVRz~{=OM3w(yi`I`NdIu@(!-HX$Om<$L-!me{Z7acSP4QN}=KET)p; zTzjqG(S76pa(jz$>SQ)ENx`P?GT_aBtH6|r2hL5f0Z@%b! z(#Rz)879arq=Wv{yZA10=)K)=;i_2e1mcqbCZmHIBQ9LBQdg@w6!FAXP-YPQ2n_1N zX6xsr^WeGlRr*G$o=Epsi?yGTKKHWo(~? z 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 diff --git a/src/images/github.svg b/src/images/github.svg new file mode 100644 index 000000000..1872a00ce --- /dev/null +++ b/src/images/github.svg @@ -0,0 +1,22 @@ + + + + diff --git a/src/images/ko-fi.png b/src/images/ko-fi.png index d19991b5f3a895e1e3ee4b23d08ec2716024fd9d..ec0c9477371fe2b8dbb91599750082df0add340c 100644 GIT binary patch literal 40174 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 + + + diff --git a/src/images/shadps4.ico b/src/images/shadps4.ico index bb50f9995ac82ebe259df8199ea9a805b35df411..870569def13a9afb256f30a9c4f4d5e7a58da677 100644 GIT binary patch literal 410598 zcmeEv1$Y(5_jX!Zi0h5Jk^~46+@(c|yA&<1!QCZ5&;)mPcL@Z7ySsaWyOfr^-+Sif zCO090K&kwH&o|F=?(E$iIp@sm?(EDt?-&?-XJBYxX=%XN!=S|v1_q%91_lle+W*6h z3=E#|oQH?@|6-==YHnbVEnED5UjqX-cLRf>MYaF^iyIj1`^mtdVZ-?A8#J@9Ml(w-H8rzT{~HA8*YVYUX?XoZMuW?0s_H?Br^!p0uyF|&d_MrHNKP@gOa&yy45^5@2woY{CBj5m)T zVQ!sYG1$ikkHf8Tv8OGbj5NaeS-lV)+8?1BI}9+f#ObBWMJKp_>I9x#y@IFLuPOTT z{d>H8^a4+gtj6iKW*BJij&T|7ux~^^>=`l$6TF=<$;>nfESXHwkCS~-%_>6v-lGzs#9xm7uIuJ)j491*H?wFG`GbTAXVo|}o`1AMQ zvAbs{Ok`Wm$?SorS1w{r#Zs8*_lr3&CcE_YlX+(ngfvEwhe)Wr>l@)W`8I>CsUWf}K;>mWQ@ zzUtXpOI7mYe5oK@ZCC+U>J`M@A^y19Bo|`CJ>%}w z55(i;OJBvs#kF}iav+}83&OM3zH#?zlYW4)Yo;NN@X=<#$CrBkNZy8g#-$c0Z_Vcq9-oW3vI&R?>? z(ENogPs;ouu4iszM9)iy_8Cq1OfpAZL$kDzks0cdDjKAVE6K8?Oevf4obBxCgSzR` z$NgrkK{IP>Y@1|=X^l-$?|TiJSlFPcWy;iy=Np^bpl*5_G-2Gx)CTPwG-zX$KCYv? zkLc>`*~myVH8MskKG#}X+v2x$_L$kk9NWelqm9NEEiLR+EXN6CW6QKQXvOp)cD5KA zWQ&G|cBpA!hg01&h?`@HR_4Yyz9AAP*RDewQwtKyF>yp&Ge=CQWR0UM4Kb>O4chR3 zCwm8Ua?ny|M@O`$jLp9L4$X{gF}Jimu-zI5+iEbggdMQf276mrqwxl zA?8-G#mG!<7+~*)A?_J@oEc-XWyaVXSuiGhA`SI&#q#EjRr#|kmBV09FO18X5r53G zM(hY%Ec)36?`K%zVjvalvV{7MjI6iBdx_%&hq`J1Iab3*K~{V@1`n7{)QiI9D5tceBArv+uF0RwZ>!-g~1ge!xU8 zCrkAt%?NjA!tXmaR zTr4mzr$1)+IwG<~Lv?;lmC~5$@4`I(@p&bGfGSJMl(KI}ZN=j3-dK_=01MfU^RotF zLAI=zAWgyQ?)Xf>!rbhKo>-}`AvJSdRZ*sJ9t^%Oi5^IE`OHkqsqRw zV=GqW^uw}jL5Qy8kLx{MaI=py_SMRWMgBfmp4AI)V(+T*wp1vF1x`lT7t&PykE&A+ z%L2WbKO5HM55n4lT3VAo6ISK&gOnY+GZJgEXTq80p1?|Tyq;i(7ZDEX_*z#_tmM0U zWBx$=@#>|PXCCiuR8<}CZBzkAd+>g3oDrMl-JClZn+j&YI|>N&YK&X^5n+ZCOpUbj@QkEjX^%x`EyQH=I>7*<5bVK>i9stD!4q}95?1T z;p#MJoapL{U1hRk8^>1L@?^n-=xvDEx)WDhx#9hEE9@$k2YX87!QP*9V+Zfe*W>LG z+ut3#isnOfZZ90HTtt;CKhph=)~$?7!)+L6#o-#+adosOZqIkZh5nv6STPWfc5TO_ zZPB>i!h`pp1&)--hr=cF<8bNxI9Q?}-cGi~{k}~5Qvn<+mlp@}acon+Y(gEU>es;4 zL3X&%Au|pZE{H=#nC_RnxHHWKu`4X_>dYp*Ja_=-Dg@!fJTp8V?13}o3*dCwf{5+u z3oJFm<$A$5#s6n3<;U5|`EjC0^1z0Y;V z#d?8wdEfxzUOqp2uYM)mZr~Rex6l%Q%(cS%*?dkdHb*S^x=_9-u2d_SNYxAB;xE~8 zp=1`ug>a{D5MsgtaJf<;Je=)`N3*?fxqSAx7>?^cuxvg@s$l;%+$|P_JJqtsJ?X-6 zXSV>{t)B zR*ruM_s79)c-^upVk;IzOr^qzty%~VY8Ln;-LFyzk2vmnz1Rtl+h#{B<$2Sp3ZyIs z^?sM~CCHe9x;p>o$%FXw@GiVhD;C?DY3xw1K&P= ziYaZnU}!~eM9ne5#HJRglHLxr&1_M}{OhQ;nFh5u25Rc%i`gAJO_AE?~eud09zOq6C)4xUy&8$$@$ckfGgSdtqm%V%XRCvh2 zHd(Wj-|(y(O*y8Nab;b8Zx1YFfqjcIU|t6k)c=v=NshIe@EbfW)RgHOn`=qZH|2M` z{Fkw41AeoYWnIg=bmg-)ZZ*GUUv~Eo#BUjlIIh&7C8?RIE!uLtx;4TGQIn0(p6~dk zCLEK-U;k7rWwf%V6R$i;m7N z9Jg8^#MVwxTN^txN#}rBjV*9!sUgOewL)_vd$fzcu1lvM%e8hKD@)vzW8SGnZLznt z1|1yiP{Y6;g913#T*k5HR*qNln4`I+3!cZs;_00`=2bL&h4yh(cab(&nK|1XluNjWP=&S z`L5x&GB#P`SVvoqp{>x>)m_#7?CxE3cl1I#Lk)VmI$=yvJ1h;cRO4tFPamAe@0tmw z=%3L6U2UDvgJbF>#5sa48f(m~QyUux4#0qb0CZ#<%qZ%B2cuc%I=)jxZjQ6x&*Yfd z%?ZtZ(4eokC#3GDckW_bad#|iVuI-LW;nLg1SeJ+WB*K3tm|oo$>r=Y$j_PMbrD; zHmLf?^IhJ}=tuPB_d$rU6V_LE0+w6j_Zc?$Fo%3>vcezJO|iMI2OeCz$N8C?h#qB! zg>@_u9_)(2E*==@=!t<2P8b=O5o5E<_duppX>?EkLfxHkdAoWqVs6!n80PJUFwRd+ z%kRVQ8p^%e3QJ3Kte(vccZRYbt~cVhj|XBd$KWN$h+{MPV=#3U=HZ1=zMdFjXMweC zS}1wsTj9eWf2iZn{^)-@Na8C~DZ2s39<~_d?1h1(Vf=1cS;`I*a`@xfoqKq3?;a)u z1!AnX3&wFwDm2nXgJrd=CRF#wn>UC#c0|pAJ-K!jPp(}_B+iRHzI+kyU%gW4pWnWv z=C@uwe1anrhU4&WMp#hT9V5McFg}wv!uhQc>g0+ESvd}hxuJL#YkHdrg21sk2}VAXT*en z#B!w$sY~jV`d^XeaLhU_&;v91oui`}oU04BHAYlt?U+{zUtZy7o-@Mq%w7pJEr4@> zoZGv;ZL_L>Ip^gjuzyeE_tFfGpJ!xanL>JHOI=c@)cq2A?C4Se; zn96S?xvony&C3qA_`M@xZS``PkKfM;Eb5=!ta6bFjaospMPNGPkAxN#|}&K@H%;& z_Y^7w8&aj;_3Sx#T9$d?&Qf`hOqdsZyX8H6P{ zg803~WA@c~oVUC%oBf&L{fp;VT_ige1vp~`ztJSVyHkxF5&3IHEY9qXl`Lyz{*-B1 zu%D_^>V9)S1{+G`#OmDsSRR}mJIV*(W^ayr$J*mzm?KU#_rjV2*|0o&AeQ=Zu6m{R z8~aV{J*?&T+KS9BSdqmQG5qF{u(N(;p7X-0;A~i%FDusZ+iqQHo2Ze!unjC z!}YPk{oPTj{$0OT!@g#AoVW48=AzknJ(%B#{Pru7P5qa18}kQXYsuV@y5HW9MPzY) zH&y0$4Zr(-pRU278krDTBp6XYWyg(f9(;$G;?Z!<;TFn;EqMd5DTg~A9n?PWpPo33 z^?v+j-?~oKzqeLR9P40<*hp*KT*7&&zFvqfpA}p4?q)msC<5S&TlOVv71)o z{&uZS4%MxNqs^Sux9RcLY|GisxVO$0mm@e=Tt73SS!Pu3Y}iw}0Ny=_#oL(sI9R?Q z-c8~+Hor@E7t4$NrE=pynOutG8}(eHOl%V~Ty2|CT|ZDJH}(|r$KK*W97|qRJm`Lo zbDsQIJ$Kw);)uQS-CH;>jyDRz^$G5{zk&DZ92cBqJ0Ggd`Z@M_7keK^D;CDv2^Rcr zwZkF#j-_snmdm5)K=FdO+Cu6##q~A;{HD#Xy;eRizg_)tq-<{d{xDWoJ<6{+b<65X69&a;4%wkWxe|#4oxE6AvTroWC<3t^^juu&P zgx{nGZE1JnH9zGrttZdFt@4m*Ge2d<?-Vm**UR z<$PQRaKZIXS#h>x5k=QKX2a7+d)%Mlg|nQixKgLGs$0h2zt0KBS>6-3`AzzM2FFYs z|HwB!mYZ_yWQu2lym6^g5nQQK7+0&aJ@_r2glZJV<#NHeSTZxy@SC0U8IKpS5A+Gb zrIN)FJ0>Gu?zF?bL78!>Y*CzN|CKsr{7seRz`NIYP`e1O{~R25t5#t=>J)_M1AXzd zk3V9X=En8P#c`85mTRA+n!H}K2yRv`hR0Lb&QtyHbe5!2o)Ul>n@`Qhyz z3p}6huJYYwzqwl~P}PAq>f1+se@Xok$jd;*)57t1-mV%RH!6k4jZ5Q6<1$}NkL#7i zi(&cjG9ot~{aO}}>XlJtf@Mn`)cYW9tx12$gZBIT&p%({8T;nr2VX}|9`L>*-D4RK z9|F&%pa1#E@9(6X{B!?go|NNPrXMl*g!`nQ7Ay_3|8ls+xI zljmBbd&2OwqbG5E^9UT-&;-Y~*2DbX%`m26D~xZ_@^7Vajhka)%Qjd!ViazkI4P`1 z-@GTheM$yipDbxF?ubUqfXpbw+ox}F1FRckfVHCyFrcJ?Iw$iTvZnQS6U`x19j(aA zg(ztTCC8MMWFYU8d{W1P6kWV`oR(ldm3WPr1XfK zk{;c1m~kD_5_Ng~pCaK!coN=bk;ix$IJHEY5&Gp=GcUvdgM)bys~qbw835BozzI==^7Y> zS_a>#d9ubb&lNu>R-bFFA^b+zI>At_({31Lgyx(VX=tRKH`As4J7V3OT%8}SQ7fG_ ze&bx7@Ypp|kgzPFA?1j=cj^>svnj-Wra9Lz^{J`32ETC*Xi0Yy?3ikZozo35xUeM} z{HRHiPCw7r95>_qVoLdBt$J#48%*QcaP4#&t}C-&lh>G2r=<<#$g*Lpre8*$B%YsW@blT>`;%# zyPC_mll;qiIOofHdD*hBe1}CHItc43ENt5rP1B`A2Rp4DmDo}x5YM%oLo2YGRN1rR~%lrtr9@r3PJH?Agy9Ff54Yh41;K zRn}NmiG9J4dAJ5IJnO=ekPx)|@dtF_9BLOw65~#sD-E%5L_1R_^?zs{Tde9~j@?sD zuy=;BI&N>t`B=O7Jd*Dd)Rpt09c--8mh-V~P1t_4ljy)YB+uJv4wi))*0(KzbkXm!uOxEwIs(cCTb9xk#p&$F4U_tI&fZj zMaRVczp;M^=a+w=U5Et^kk`ZW$gA*6I>fnSi6e*6UZB1PBZ3{#%fS`htzFgJb${B| z^>hCWxjAz#J3Y3K)XsH@5VLAjLl<)smA60h_u^Vbdka@cdH05M&VuvW^4t+Qq+f|0 zOH@O9^k%vi21Z!bsbhjJuIy}#{ZnaUG@Ja+GE@8?nq!6?qb#tbnFitcoY3FJ4ZSt) z=<7(E6gM{v@^noz4J1$9ObxL;EK&b+YE(r}OVcD}^mK5;u#B#FJ2jqXxgVBFpCZXU z%>MOkoDF8?PLI`{IwkObHM%hlOf$d{>V0>F1y<2EXi_mJ4DobBKYPxb)7E7m*Cz&W zu6}^ySCE4Zx^gamJ8dX+VOFK`=x&%Up}c{RiRqRH;hWM?jt5U5!B$V;TpHM3id+Gu+IHaTuw+7pB&4m2Wc4!!R>hHk0@jUK0 z)zJaNoy@SlvmXE5x+2up0%OT%ctBDLmlTd_Jc5b&*l_=D4_al`f02m7bm_D~1>v zU@Ya+l7{ieG=^iYP$L8E8J2keE~{1^!_3lQtSi$i;`(bwcZBtDep|$*iCe^!I6kj3Hdhu_A4fP*{7I3r7{xF z`6*t7XIr&l0HSUE;XGp%nWqL;|u3>+3|vFgL89mJ;}#GyLOel9A2C1 zp~10niTrPFQWw*h{!{g(c1?H{o^@gWfS$COx5omp3H%&!9c-4L6Bcm&>&0zlFQSTh za`8Oo(#CK$?GG0+jea=`xE3~pYjG#WkJeR*$fosJ4#(u5t1sDg;Zb-Mo^|2Sz+RZk zwYDW(r<1j}&(YGnnVH@Z%L-;w{YO{MgX4!WpKEjrGP^_4B$PvW=J?v<)Pym*q=;%> zAM^a3(_CjlS>#c86`pnB$lzXB=egQT{tzYFV4j_a9B^l? z2It4PVs8Vk{}#%DO}TSoOTir2S~NQ%S$0y&rg#;eb>VziKWwC3b9BjU>?7VgDtE&=6+cm+atUkC%zVEED#k~zSTx+(*wONi_XZFL+(&U@k;2jM$Qg>&>(Q*!6R;qUfK3u{D1l zu8C*GpL5s_OD*tds0+3i&W-4wn66|_M3-RsnH_L>Y^bhy6`u%cgbTwG`|Jt&JZwZ{cNoE-whrx^QX4 z0Bq0hizi%*7yd=RyMM~9Xm6>UNJ1rZvAn#vKhRC_FYEgIe+f=VyRURE?BV);R8}Y4 zm^ofo1>*jAkEciWC;UI%sVSne`Qt3@p6^G}Cc7cm;B)1}fy&u&u8%M7EOu0K#BQ?1 z)$wjP-ZV4I&4pbBxlW(M2bV_OA?aq8gEj3sD=w!8E94eJBXJpA0A3#5mmtICka{>? zBn!?~&x40+oDegc-?nAx3qU&qRZb=9y&UuYME}B;+3|3tGoD6~_X%FKdB})ciE~NM zp01dSQ?%JQQ?4Lhg;THbGCUdViHkMz;%vFXI8#<@;}Am|2xSjJ8UNtB;9^Z)FJFkZ z4PxJr3ehg&Z0T$`S3Wl$uUUlHX`^wrY%u>beWe0;xY!ksmbu_O{UFa(pl>G2IZNAn zp<9EwHXmh&r`zmtvmgCVN*BaQ`ieYVIahS?jI;x{uHgJnfjCdUhpTn+(ziq20orzu zIj~n_T<~(FD}J9Q^I{f=BYjw4fjh0TF;7uks#@^#RE>TP74zU+aXu%1%0!>d0wkWR zK)-@nUU<5}30Hp2$NzuLe(6(&nROPWpHj<#o#Zb0CE0`1OlM zBFODl)o{5?5H43Nh8ryBHSJ5{7Ey*JW?B-v4<5%+uHUEf9%_*Tm&zAY_8!-0_mNh* zO5Y73Nq?0xU8ztM561i9#Wn}pwdBR6pGvTQ=ESqL&Ui)MA5QhbwOR#nnR>cfo%ST; zQFx6H!n<0(!-W&LUfehCX0`mdUbQ%`S1E@3ZF1m6s5jn(JL63R%M0VPq;nSBq&>*B zO0*qe{8#A)_4QzQCcKPtz}@!rne9*e!JYQ%|NX(VZxVf0E{dDAXn!KSkw;uSA-caO zyvMIWRuy?(%@~DS#k}M0)S#V7tzx)UwS=PE)r-+5CGr2An#FLpHf>P80o|qz&4a$# z@oK9RUaWG*>s|JEvBnLt?egGug`aSzW>MU&L;DxrfjHDrUz6+UQCAuu_h{g&zEp6OVrj#GBQ|c-DtD zU>twQxd)Y4cLizSkQdRiD>PapB^ zY3fM4nLiY7*9^cbe&4_5|JT!pt2(7_kOv9zJaL)#^MT3bo^DQx=CM3u|E=&$co1I5 zK$`4lJ}=$>Pw_1^Aa+_sN&QJWJ1qlZk$h=j`+AXNQfU|4`!VDHbcp9sc$JAfuP>EX zm!5g~A%Ug(!oGj|5|8hkp`YDh(h*!caT?c8p1}>;Jp9{q{p4xfxp)ch;{7UwZ|Xq8 zGQ4}|r;{r!G4mvRXuo;-3=0N?qEoKiSk&JbD~8b*Y^VWN($}t@<~!PPrK26xe}M$I z=10_^{ZWVDJXkU$6tAB=O=v!mDUGu8;j9g_I(I{*A2OgA?Luhhh4A_Y*fhZatA>Av zO%n|;xaS%$aeR2N*T4fB3 zmbjyoa%wQbxl%=as83V$`WBw3)=kF?7(wZO{I4A@B7*N@4d zZ1>s+n4uc&_9V@JkI0vi1wf-2}VkN+Sb)KwM?LT)I)Xp zwT`c6jBQiXWBuq1DvtP7@Kb1mXOfs!Ki_}j*h=BPh-_LZ)w7W(UD`nT7t8qtox3Uf z(uR~f!PZgFezErV*612!fo)UR2gcHNb3z8J3N_N&AR7L2c8@9X_StikN$geU^O+VO z)U$xHyUPE!d{)*Y&X!PWHjfRB#a7W8s|Fh(a-tzNg&QJ@wx7L%EtC(k@*$45u}rOu zf9ky0Sk^PNMt$=Ng$1eI$>V1Ys+vh)G@hI#Tk$4qVQ9nsX-G{Htx zXDgaKZeeMQ7W6Y-J-|fyx^Io3zx50w3@K!V#u@BBn?HGZU;1CLof^}}S?pSylb4k8 zi|_ox@|!c`9D?mXRe72ZDSKVMC7%_vdXr~Z@&@k zg{4lNeQi_P$aZqFQ~ujnsP$pTDjVD%WkY*l+R*Yn)sol&_b*>jeL#NhoH>J5rqqj- z724a`eU@6A(Oxgm7CR@?-*UjW)R=qsS^U>%v_gw>2|E z2=Tmh_+BZ+9f@%um?)P~v`swFhWCsid8bXVV3vuj zBC{^sJA0Nk#pdX!p&fV%w!)q5Xd`Szn`7F^cGNgxFm*nYSPxrAnj(44tC^;=gCtS@-!(Q=aYv?|K>U??{AS$wX@ivN`IhTu*k0LYl-8qt_J-HOlV8Z z{8FFDDCO(RA4B=O5O1z4eFD1KQC?zwbhUBDpg>1VtFFO@{ubCv3<$Av-bdL5vrg=_ zMgBe0c%Hft+wG_^W*C{D?WH098f}}!zB#QV={uU4U}}|0cyj9&?q9luC%127M$H=P zyp$tpLM)xxFPyQeiap-Vkha%i;pt=+xs^WX6CijVZP|WeN3G+do4u`)RbHcg2ZxPh)#AlwMRd9XZCp)^klxCj&|s#u|mwbMEPUSok2uVQ>^O9_MA*RYubh% zq|LR+zlZV)?VGASCwAkz#+hM7h&9HSa8P#LJ#1Xj)7BMzXu~Zw<6`&yr6l>fm>FY1 z!}>bWRlK-)Q=T_UvD_XGE@*8`?1VCoe8yWR(SgXWo{{VyZ)mr@yaMlk^4QVB0X=Mq zL8vGH%LhlXf0_Xf&N5`o_ML35NRAK9*4mx#rN8^yuGW}R#sNcoUC`T>cIS3(%Km(y zhbsos?))zi%jj-lf<=uQB$0nByqZj6-b7~t-TE;h88=}fy3V#4U!Ut!1ad64*D zd!O7IVvF&7c6Z@8FuDAD#(#&s9LpY)TQ7C$a5FY4S6npkay)mGt4PKtchcZ^1ps`l-Qm>U~xTb zjLYpxTW(Jbrmg=_#=~i2ALgs25sXLpd0>#eHAd3zdmL@lB4~g3t!Oy)x;CVB68SfD z?x6eulGc$G!veBmdp#G9@2!+<>U~FFhIN(soU|t%k&7q#I(TAX3EHWQq>jYTVXYO8 zwzeTok^}CaOO*fNrL!37Y>&Zqt{Cp_g^{$)*Y_3B`3#Jr>_dpBy1G>}`U$*Kd z@*Vg_@rOVENFx6S+T|xNU(EFX_?`CpD`;;uJ-xQ=+0J)D>03bT;4tBzehwq(qaheZ zp~M1?s_RT26%8V2Z#a-+$p?CN#t+V&$8h3OjU=Yg7=O=HX(aJeM|e2mwfN}&ogprM zno1X^R@eG$#C!dL{vYh)^edWM&|UcM;z+PCK;j`%J4(YdcZp~_-;pDS~7qe8elz)<n-c%xJK~Ko#Bv?uomY?AZuEH%y?PM(RY&TQ5gU&Oy5dVgae3 zC-?QJZ8$n@63#APg0ssOe=(h!KLpkRJ+JTR9yb6RY1Ek1Z!j>Ye0KINF~WQOQ2?Q}&Z#Md^g!8CUpg;lNh4Aw8aei^`wQDw4@OkMOKkn7oPHX9(|M+&{ljAeR{rz782WjnzK0$ z4(g>VHX**WWIkrNTMPRwMfANA|0_kw(uuE> zlrh)C8s}$CNg{{*IT;a#x$Fnah@GhXuae}a|5Si8_H;}t|NibFJny7@wLZyLlCn~l zLHH9{MP^-icKL$Z&t?VrT1B6#BvgQykh0%RZo!w#s_-QRV^T=|%XzuJis@9o0`x5- zCjUYo4X)DHDS0>++7}CmJF_xxAoDPvUVTe4xnN)CR!OoP?9m>J>6fLS?ynq+jIwu+ z$gB&`dAu^%7pwCGB%w#5(XPqmOZ?3Mt)Cfv*VYxviWT&C+e(bSTYX%$^T~_(4Nc!9 z=*9BgE&gzkrRaai^pa0}-`3OTOv+r5#Rd2GCLPmDy$9Mir5~Jae0^FUx0OM}oa~zP z`~L`awURz&>nQsg`qr%@MyT?IlRDDq7pI?2WE8$cW?gu3w`^2iIJLQAjcc&r?a_WW^5tPYw729vaz3%OJr4ZEA;)6<2T$ZLhw~3 zR(PvC{|Ea3L@(6CDdv;>QqC6o-iZ(1hMeB?bqh$WQ%?tb>Q%(r%uXup`s^M!*1fHc zEft^W(-CX`gINHq%+sg*gZ;rao8;mW5>01_GpNi!;6%rXmR(<(j zQvNOZ{Sir@)J+9~v9B`sZBT8@c{6E$`s6(r;*5jUGhthC-V;S~s965XdATm>;>5R- z`6R!TBWV?tpzmN#FKnYe-rT#bl`mcgam1?LUA-hWWOc!&U{9RrkyQSZeLG*`zf+ePVPS@zBZIe+LiI;5NSVR*%qMRAhCzH7o%*9<^QI9xz%<3^iP<7E6dql zJQzDD`wsdr?x2t07Rnb*za`llMTIy{WHJ1~@916hS6T04fm6Mbjv-I?>x9kOC_jC^ zuFrMAt!2c7rR;atYjAI)jq-84%zedtr3`fvRC?yZuY4$dlm_wc^L=sn&x>H5dn{+*e}oA|!LxHgp-I_queFX@Sso&9ig zo}>C7vr&T>o|pfpd--5*rEG{QAbZEK{$zabB)#O5{8G-2!o>5{&m(1!qJrIVxPAp)^$FjM z&rcu2xxO6|&-3W~fG*gP+XuUff-JSdt4gnfg|;^a4#MDhtjulcX9e}l9%1Zv$CvAN#uV``FFElMAOIj$#Bjoiw_)0 zj&~K!i+%JB-B16E)JU#NI^}1omv?vZJgMg08SI7=Eovvq8vi`_llv3t=2GP1#erS0 zE3Xgs6v@MV|GaU3i!E+UrB5>bSoaVwc{lNr_ZH8MV-16FWfXm8S2$1)+|OsTEn?Q$ z;l?yqoC*oV{xX!kP+n3FmX#SIvo5^8b_x561`&Tb7%#>+COp?;`gkgT(**y~WIm_* z=fy8o(ukKC(lPe)Pux}-o^a`&UH>Y-=X747hf9G6?^jglE1t- z-!mf~P_8=*oN=fu@upe6lzo8s)BA`+EoqL>XY^uUKipa9g!>!mGtBh&H}ZM0$O#ww z`{8iqAneJP4XpBqkNsQ|^X10goUXXsPp{AGy&HwUD}%dXe?dR;M_lcY%y>XqVwXGOc=eq0 zgQdKBezd~Jp`W>bQ_=i5QZ6^nwI^1=boy+qa>V`Bob%pbkCm&N=nj_(PASbSccEh2<8F7UEx@VhY!^8Eo|5;1SiiX+gZ%aJwaw+h|I*-a8 zSM(=7RHz^>w9BIKz3;E&y!QsK{fe(*?CFI1ME>LS$2~$H=UeRp`TVlbzR%f?SDOS; zH-&JL{=Qnu$5=<&b8=nMNxN&`8x|}lfPTXC2~9|=%ky!3f^Bs;k2~&68j--0?%-xv zZyY6N_;K#lb+&F!;)FZm(MEe*Y?GBd7f7Xi`X!g8Zit0`zDW+m%yq-VwT^hand67? z#E2+ItbvP(_FwPr+{CG0h!4U0=v=it+UNK@j&Ioq-c8{+qcY1YRap57pP@gnj!swP zK3+U8*Cm~#|G<3m9*}axulNl8eLqk14W8E%vz9BQs7>zlxV+b(|6mb^7C8?@!(qmA?<<^EzP`P@8Nf*A03Z>sXS z#_|Mw7oV>te!bkItW06t@4>MzeV|1S*=y|WL|Em{<89pQBIm{dwF6 zhq&ywL+PVmrV!524?Cd_UO&V3IA5L5;whZt*lMrnA+g=Bw9bLk^!q-;_wRZ7%ZkjD zT?S&x{`E%tdN{|uUN3NewDa8i^>w_zwbB9mwY2MVjyGdE@R`yoEABCt|8idXy^@{x zmE@EBQqIMy1-_7YuM&&?95K~}7l|)YhZpGweX%mH(VzSNRDQc}=W}eFFMXs7tMgLN z1@5PIzElxhYZc6WXt>~${Dwie%gAm?wX`~Gr>{ill+(I%ly?uc`jAT zi_44?$`D`h@{)aQYn1+7O^!2_(-)^~fcTi?L+r+(;ME1=b8xEmAII-o0 zbmfSQPnOJ3^7E%V<#$zo-n_(B?sp(`tp@$CE77mIT46jJ?9Ka{a?|Jg!#qCUxbL2h zw@_&=_Q}!|K>!$@GIquA|^BwUQqrgi`|v4_?6Q1>8?`{kEZ!3Uvcplf4a&Q zw?lH`3ip1w$~_2lyeRo9<)$<8E9v_ZT*U)|@-Gn`Je?IDceR+W*7v(sVO+09S?CXb zw{adkALPTmksZ0#u2Fqm;&*&5JRj(zu3xK2KA8Tm`i*}ozxa&D^v;Ht+t|jdU2&sY z5!`8+2hUcz;pKKmk^`R1^uaCe)qK5taopfO3;N}WY>#J+Rn1M=7stDQi2NM5XhUqP zs&O|<^B%8VL`$`b;3oa%Z&soIz4&~y?0XIKt62WadAWX@=@Rnj@_x&h<=>)DdQ8V$ zcqu;VYq*9L!8wKXE_fxf^W4K>S#XQ~{X$83A)k~>|rWvB1`-J0wVHAoWk|2>{hNTW;pH^q0Wac_*4`SEIlD_*X5 z#j7pOcrCKiFaJTO+}wZSC-Oy}lGM*WLRmy6Jb$W06j^m`uOIXLLY)pCwXGg^uarMx zxsSzt`sLrROWcHinPRE)2fr1@t2M+U*zAHgQI2>qn>ag-=+|H6XY#4XqhJY;cah;y z+iLoX3{nD5!Y_Fho*&lCkJob}68gNzrjzqiG1GO|=>TtE;q8i9c;2%yo-`_n z$CUkXtpfiJaqrc~HHif?#RJc~1>sQz?i<7XWfJ%yZ^EPS`gY|kKIdO1h#@k4O6KI( zm_8%#hh_Q&;P zQxCmJyGc)ymHnUlD?AFXWUQy~t}n~~KmPw3NUj0FiD*dLNz&m&^dHYZ-ypF6{YWZC z8p}Spjr4$||2*cU|J@+6GKkFED5VTMi;@(!(Kk?UsztC4AE}S^#xJ(;-~SIv1MxbM zw(v<+fnRxnWjc`Ns)|zhdCvU<@0{O_+vnEf&V|*8y|xM$_HV?IP20JT)VBW}+7gMA z+jrpdp(A*D|9%RZNH`_Ba`;MhC&|Wg8nJB?3YLTS?|#RgCCf3SYJH5U>xtDP(_`@f z1LBw%sCe~A1B|F?pzu9P@iVye{}2fVh~TnRNpFIto_-iryB_u|U9NPZuO#YBl-4MT zGQaQ-^Uu*Q^WEz=SlE9c>Tw?x?k%PE7HjTlfDPlgNBhX{uqy0(;>V=p-eZi1eb4>k z4aut!Du4gKK~+Q!;=#y1^c9IgBKxPy-fE(Yy7Zr&-={yf6@0C)GC7|2g=&|1i*%&z zl39YZ<*tQ`iOb`la8Jr}KcH$qn4s$S+*>TY85Z>w>=N$LF+xjgct30*o=dO7#>B@U zj*IMT_CHLb3(<*S!qnydN;~H*P&(6UPyO!TZ@RR$1Jxa+%PYDcU8kOE;|eTaFmrT! z(bddg&VFEluqwvb%Dvv!j?92H@w9m&F?vFbh*e{O8tK0fJW>k4smbvsVeo`+Q$r=xo4nY_edKEZ5T@| zA5s$sb5#32X&(Lj|8vLGk?2a=peJ=FI@I-L#LM zQtb@f^Dw<77WFn%dp52mhLq4c;s`|&PiRm{Q&j!W;wb$+*iK4U)S2k6XTCzo+n`=b zZ9sL?z(gBF_Yr*NNd9_kPr+Ie9H!K$8gZJ!Dw-tVHwliHknBM@zpD{(V%Tt(@b z5Z}Y$eC~`kR5)64yqTC{Z9FX42A?nIQ+52Gyhh!L4h5e|bgCDUJcoGRO?U10H*e64 zxEwXV`vHy2ttpB;| z)L~8HKQ*&+!tZYq=TLbcs5L^l4|qIEr~hSr`*VD2Ks+$w9x-oP=r^XT^F6U>elbH7 z_l*?{wT;xhkhI6rKE}jt;vTFfX)7-+b^aIAh#penOLSS8I$h3hCtVPoi|+Y91CQ^> z`SD zw3hdODV<=U3BPrIv_S)7EjF4y|8l)9b=um=mHy?wt0th%WezO^N#^cUIJA5P_c7(Z zyu{1;Qf#-T<~C?#sKLTc#ET(@+Ggrr5wYZC58v^%%oLW~mzMi~C4EB^!DF)_b{qFR zPRI73K4gDk9sigIPf$j@aS~+)v$D zTV690Go|Af4<4vCpzagat!^AWismM!Xk|gHL}DR+EeTd)SaB=tqP_(~F_QWh+A-M( z8-|&n9dQU-S$=)_U(45L^AUrwsi_@CF$9Kx`T_mcA>eU5!zT;BZOstEw#3= zM<*9s?)NVEjqz>3c96Z?13d?}xKJ*!M;E#L<-eqFd3i=sI~gw{07mXD~!N zzQ5aZpZBjOTYKVAI$%K?Vn}k|cEQh7G|q&$qQ;olf_S9Fdu-2iUtPyv;V+fEg0;Df z_;~6WE|`OSmJ9Zj=(jELIb|Pv!Qt#c+(se6>+D4g1f^?Y6DHBUUtGj(DA zw{l(!91y~GXlm?$ z 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 literal 74814 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%0DoV=0>tZQeC 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 + diff --git a/src/images/website.png b/src/images/website.png index 9584f6b829add7101f6fe51c5e451beade335bd7..e872e60a29d8524bfa709204c5371e842823e4ed 100644 GIT binary patch literal 73633 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 Zo$= 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 + + + diff --git a/src/images/youtube.png b/src/images/youtube.png index 362ac578120a555ad47a80f8ec63b40294e0ffb9..2a69760e6ab506a370e674415fcb03d3d4b75548 100644 GIT binary patch literal 33075 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%0DMMzX(FX9wl9I7Q#eYJdfcNnFlIf2Rw=VZ6-UC0( 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 P4F2E^OpX`-$>{+ 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 O;yKsX?Wn7K + + + + + From 8ed82712a05dbb6c127a7949072d358463411d90 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sat, 12 Apr 2025 21:13:04 -0700 Subject: [PATCH 123/194] hotfix: Fix logo resolution in Qt UI. --- src/qt_gui/about_dialog.ui | 2 +- src/qt_gui/check_update.cpp | 2 +- src/shadps4.qrc | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qt_gui/about_dialog.ui b/src/qt_gui/about_dialog.ui index 3842513a5..804f0aea2 100644 --- a/src/qt_gui/about_dialog.ui +++ b/src/qt_gui/about_dialog.ui @@ -35,7 +35,7 @@ - :/images/shadps4.ico + :/images/shadps4.svg true diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp index 7d3a42798..a823eedab 100644 --- a/src/qt_gui/check_update.cpp +++ b/src/qt_gui/check_update.cpp @@ -188,7 +188,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate, QHBoxLayout* titleLayout = new QHBoxLayout(); QLabel* imageLabel = new QLabel(this); - QPixmap pixmap(":/images/shadps4.ico"); + QPixmap pixmap(":/images/shadps4.svg"); imageLabel->setPixmap(pixmap); imageLabel->setScaledContents(true); imageLabel->setFixedSize(50, 50); diff --git a/src/shadps4.qrc b/src/shadps4.qrc index 83dea01c4..81a36af34 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -1,6 +1,7 @@ images/shadps4.ico + images/shadps4.svg images/about_icon.png images/dump_icon.png images/play_icon.png From 6c2574364b7d8907087759c2961d36859010ec43 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 13 Apr 2025 08:12:02 -0700 Subject: [PATCH 124/194] libraries: Reduce some controller log spam. (#2777) --- src/core/libraries/move/move.cpp | 8 +++++++- src/core/libraries/pad/pad.cpp | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/move/move.cpp b/src/core/libraries/move/move.cpp index 626fed9b4..500d89586 100644 --- a/src/core/libraries/move/move.cpp +++ b/src/core/libraries/move/move.cpp @@ -9,7 +9,7 @@ namespace Libraries::Move { int PS4_SYSV_ABI sceMoveOpen() { - LOG_ERROR(Lib_Move, "(STUBBED) called"); + LOG_TRACE(Lib_Move, "(STUBBED) called"); return ORBIS_FAIL; } @@ -18,6 +18,11 @@ int PS4_SYSV_ABI sceMoveGetDeviceInfo() { return ORBIS_OK; } +int PS4_SYSV_ABI sceMoveReadStateLatest() { + LOG_TRACE(Lib_Move, "(STUBBED) called"); + return ORBIS_OK; +} + int PS4_SYSV_ABI sceMoveReadStateRecent() { LOG_TRACE(Lib_Move, "(STUBBED) called"); return ORBIS_OK; @@ -36,6 +41,7 @@ int PS4_SYSV_ABI sceMoveInit() { void RegisterlibSceMove(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("HzC60MfjJxU", "libSceMove", 1, "libSceMove", 1, 1, sceMoveOpen); LIB_FUNCTION("GWXTyxs4QbE", "libSceMove", 1, "libSceMove", 1, 1, sceMoveGetDeviceInfo); + LIB_FUNCTION("ttU+JOhShl4", "libSceMove", 1, "libSceMove", 1, 1, sceMoveReadStateLatest); LIB_FUNCTION("f2bcpK6kJfg", "libSceMove", 1, "libSceMove", 1, 1, sceMoveReadStateRecent); LIB_FUNCTION("tsZi60H4ypY", "libSceMove", 1, "libSceMove", 1, 1, sceMoveTerm); LIB_FUNCTION("j1ITE-EoJmE", "libSceMove", 1, "libSceMove", 1, 1, sceMoveInit); diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 2e5973144..5dfc68e90 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -250,7 +250,6 @@ int PS4_SYSV_ABI scePadMbusTerm() { } int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenParam* pParam) { - LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index); if (userId == -1) { return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE; } @@ -261,6 +260,7 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } + LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index); scePadResetLightBar(1); return 1; // dummy } From aec6e330dc339de856deb77d8711caaad539928f Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sun, 13 Apr 2025 13:27:20 -0300 Subject: [PATCH 125/194] Update ImGui submodule (#2779) * update imgui submodule * fix imgui breaking changes * update ffmpeg-core submodule --- externals/dear_imgui | 2 +- externals/ffmpeg-core | 2 +- src/core/devtools/widget/text_editor.cpp | 59 ++++++++++-------------- src/imgui/imgui_std.h | 4 +- 4 files changed, 29 insertions(+), 38 deletions(-) diff --git a/externals/dear_imgui b/externals/dear_imgui index 636cd4a7d..f4d935909 160000 --- a/externals/dear_imgui +++ b/externals/dear_imgui @@ -1 +1 @@ -Subproject commit 636cd4a7d623a2bc9bf59bb3acbb4ca075befba3 +Subproject commit f4d9359095eff3eb03f685921edc1cf0e37b1687 diff --git a/externals/ffmpeg-core b/externals/ffmpeg-core index 42557a704..b0de1dcca 160000 --- a/externals/ffmpeg-core +++ b/externals/ffmpeg-core @@ -1 +1 @@ -Subproject commit 42557a704720d1b7d85c03bff0c2d369a61848da +Subproject commit b0de1dcca26c0ebfb8011b8e59dd17fc399db0ff diff --git a/src/core/devtools/widget/text_editor.cpp b/src/core/devtools/widget/text_editor.cpp index 7171cac47..12031d1ef 100644 --- a/src/core/devtools/widget/text_editor.cpp +++ b/src/core/devtools/widget/text_editor.cpp @@ -627,65 +627,56 @@ void TextEditor::HandleKeyboardInputs() { io.WantCaptureKeyboard = true; io.WantTextInput = true; - if (!IsReadOnly() && ctrl && !shift && !alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z))) + if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Z)) Undo(); - else if (!IsReadOnly() && !ctrl && !shift && alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGuiKey_Backspace)) Undo(); - else if (!IsReadOnly() && ctrl && !shift && !alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y))) + else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Y)) Redo(); - else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) MoveUp(1, shift); - else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) MoveDown(1, shift); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow))) + else if (!alt && ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) MoveLeft(1, shift, ctrl); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow))) + else if (!alt && ImGui::IsKeyPressed(ImGuiKey_RightArrow)) MoveRight(1, shift, ctrl); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp))) + else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageUp)) MoveUp(GetPageSize() - 4, shift); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown))) + else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageDown)) MoveDown(GetPageSize() - 4, shift); - else if (!alt && ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + else if (!alt && ctrl && ImGui::IsKeyPressed(ImGuiKey_Home)) MoveTop(shift); - else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + else if (ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End)) MoveBottom(shift); - else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Home)) MoveHome(shift); - else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End)) MoveEnd(shift); - else if (!IsReadOnly() && !ctrl && !shift && !alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete)) Delete(); else if (!IsReadOnly() && !ctrl && !shift && !alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + ImGui::IsKeyPressed(ImGuiKey_Backspace)) Backspace(); - else if (!ctrl && !shift && !alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert)) mOverwrite ^= true; - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert)) Copy(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_C)) Copy(); - else if (!IsReadOnly() && !ctrl && shift && !alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert)) Paste(); - else if (!IsReadOnly() && ctrl && !shift && !alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V))) + else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_V)) Paste(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X))) + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_X)) Cut(); - else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete)) Cut(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A))) + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_A)) SelectAll(); - else if (!IsReadOnly() && !ctrl && !shift && !alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) + else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Enter)) EnterCharacter('\n', false); - else if (!IsReadOnly() && !ctrl && !alt && - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab))) + else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Tab)) EnterCharacter('\t', shift); if (!IsReadOnly() && !io.InputQueueCharacters.empty()) { diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h index cd7208064..743702657 100644 --- a/src/imgui/imgui_std.h +++ b/src/imgui/imgui_std.h @@ -50,14 +50,14 @@ inline void KeepWindowInside(ImVec2 display_size = GetIO().DisplaySize) { } inline void KeepNavHighlight() { - GetCurrentContext()->NavDisableHighlight = false; + GetCurrentContext()->NavCursorVisible = true; } inline void SetItemCurrentNavFocus(const ImGuiID id = -1) { const auto ctx = GetCurrentContext(); SetFocusID(id == -1 ? ctx->LastItemData.ID : id, ctx->CurrentWindow); ctx->NavInitResult.Clear(); - ctx->NavDisableHighlight = false; + ctx->NavCursorVisible = true; } inline void DrawPrettyBackground() { From 14fad28a9bc3bbf68b53b0c63937c85bc1eafc2e Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 13 Apr 2025 12:10:24 -0700 Subject: [PATCH 126/194] misc: Few small fixes. (#2780) * qt: Use PNG file for logo. * build: Fix some CMake issues. --- CMakeLists.txt | 12 +++++------- REUSE.toml | 1 + src/images/shadps4.png | Bin 0 -> 35291 bytes src/qt_gui/about_dialog.ui | 2 +- src/qt_gui/check_update.cpp | 2 +- src/shadps4.qrc | 2 +- 6 files changed, 9 insertions(+), 10 deletions(-) create mode 100644 src/images/shadps4.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 37492eeb3..63dc7b4c3 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) if(APPLE) list(APPEND ADDITIONAL_LANGUAGES OBJC) # Starting with 15.4, Rosetta 2 has support for all the necessary instruction sets. - set(CMAKE_OSX_DEPLOYMENT_TARGET 15.4) + set(CMAKE_OSX_DEPLOYMENT_TARGET 15.4 CACHE STRING "") endif() if (NOT CMAKE_BUILD_TYPE) @@ -105,11 +105,8 @@ if (CLANG_FORMAT) unset(CCOMMENT) endif() -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - # generate git revision information -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules/") -include(GetGitRevisionDescription) +include("${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules/GetGitRevisionDescription.cmake") get_git_head_revision(GIT_REF_SPEC GIT_REV) git_describe(GIT_DESC --always --long --dirty) git_branch_name(GIT_BRANCH) @@ -209,6 +206,7 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_ message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") find_package(Boost 1.84.0 CONFIG) find_package(FFmpeg 5.1.2 MODULE) find_package(fmt 10.2.0 CONFIG) @@ -229,10 +227,10 @@ find_package(ZLIB 1.3 MODULE) find_package(Zydis 5.0.0 CONFIG) find_package(pugixml 1.14 CONFIG) find_package(libusb 1.0.27 MODULE) - if (APPLE) find_package(date 3.0.1 CONFIG) endif() +list(POP_BACK CMAKE_MODULE_PATH) # Note: Windows always has these functions through winpthreads include(CheckSymbolExists) @@ -1174,7 +1172,7 @@ target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE}) # embed resources -include(CMakeRC) +include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeRC.cmake") cmrc_add_resource_library(embedded-resources ALIAS res::embedded NAMESPACE res diff --git a/REUSE.toml b/REUSE.toml index d17594e4d..662987611 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -57,6 +57,7 @@ path = [ "src/images/utils_icon.png", "src/images/shadPS4.icns", "src/images/shadps4.ico", + "src/images/shadps4.png", "src/images/net.shadps4.shadPS4.svg", "src/images/themes_icon.png", "src/images/update_icon.png", diff --git a/src/images/shadps4.png b/src/images/shadps4.png new file mode 100644 index 0000000000000000000000000000000000000000..037732e3b14d6a97574ce29956c703a6ce7089ac GIT binary patch literal 35291 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

aHH*iAmjzIlNYQ}fyp$U6# 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