diff --git a/.github/linux-appimage-qt.sh b/.github/linux-appimage-qt.sh index 776eed61e..6313a4ce5 100755 --- a/.github/linux-appimage-qt.sh +++ b/.github/linux-appimage-qt.sh @@ -25,6 +25,7 @@ chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh ./linuxdeploy-x86_64.AppImage --appdir AppDir ./linuxdeploy-plugin-checkrt-x86_64.sh --appdir AppDir +rsync -a "$GITHUB_WORKSPACE/Resources/" AppDir/ cp -a "$GITHUB_WORKSPACE/build/translations" AppDir/usr/bin ./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/dist/net.shadps4.shadPS4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/src/images/net.shadps4.shadPS4.svg --plugin qt diff --git a/.github/linux-appimage-sdl.sh b/.github/linux-appimage-sdl.sh index 7961f5312..2a66ce532 100755 --- a/.github/linux-appimage-sdl.sh +++ b/.github/linux-appimage-sdl.sh @@ -14,6 +14,8 @@ wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/downl chmod a+x linuxdeploy-x86_64.AppImage chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh +cp -a "$GITHUB_WORKSPACE/Resources" AppDir + # Build AppImage ./linuxdeploy-x86_64.AppImage --appdir AppDir ./linuxdeploy-plugin-checkrt-x86_64.sh --appdir AppDir diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 658308b39..a2b531cf2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -94,11 +94,14 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $env:NUMBER_OF_PROCESSORS + - name: Move Files and Resources + run: "mkdir upload\n\t move build/Resources upload\nmove build/shadPS4.exe upload\n" + - name: Upload Windows SDL artifact uses: actions/upload-artifact@v4 with: name: shadps4-win64-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} - path: ${{github.workspace}}/build/shadPS4.exe + path: upload/ windows-qt: runs-on: windows-2025 @@ -148,10 +151,11 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $env:NUMBER_OF_PROCESSORS + - name: Move Files and Resources + run: "mkdir upload\n\t move build/Resources upload\nmove build/shadPS4.exe upload\n" + - name: Deploy and Package run: | - mkdir upload - move build/shadPS4.exe upload windeployqt --no-compiler-runtime --no-system-d3d-compiler --no-system-dxc-compiler --dir upload upload/shadPS4.exe Compress-Archive -Path upload/* -DestinationPath shadps4-win64-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}.zip @@ -201,10 +205,11 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) + - name: Move Files and Resources + run: "mkdir upload\n\t mv ${{github.workspace}}/build/shadps4 upload\nmv ${{github.workspace}}/build/Resources upload\n" + - name: Package and Upload macOS SDL artifact run: | - 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 . - uses: actions/upload-artifact@v4 @@ -261,11 +266,12 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) + + - name: Move Files and Resources + run: "mkdir upload\n\t mv ${{github.workspace}}/build/shadps4.app upload\n" - name: Package and Upload macOS Qt artifact run: | - mkdir upload - mv ${{github.workspace}}/build/shadps4.app upload macdeployqt upload/shadps4.app tar cf shadps4-macos-qt.tar.gz -C upload . - uses: actions/upload-artifact@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 95766bc67..009244416 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,6 +195,7 @@ endif() add_subdirectory(externals) include_directories(src) +include_directories(Resources) if(ENABLE_QT_GUI) find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent LinguistTools Network Multimedia) @@ -956,6 +957,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp ) endif() +set(RESOURCEFOLDER ${CMAKE_SOURCE_DIR}/Resources) + if (ENABLE_QT_GUI) qt_add_executable(shadps4 ${AUDIO_CORE} @@ -967,6 +970,7 @@ if (ENABLE_QT_GUI) ${SHADER_RECOMPILER} ${VIDEO_CORE} ${EMULATOR} + ${RESOURCEFOLDER} src/images/shadPS4.icns ) else() @@ -1144,3 +1148,19 @@ if (ENABLE_QT_GUI AND CMAKE_SYSTEM_NAME STREQUAL "Linux") install(FILES ".github/shadps4.png" DESTINATION "share/icons/hicolor/512x512/apps" RENAME "net.shadps4.shadPS4.png") install(FILES "src/images/net.shadps4.shadPS4.svg" DESTINATION "share/icons/hicolor/scalable/apps") endif() + + +# copy Resource folder (trophy images) inside Mac App bundle resources folder +if (ENABLE_QT_GUI AND APPLE) +add_custom_command(TARGET shadps4 POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/Resources" + "$/../Resources" +) +else() +add_custom_target(copy-files ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/Resources/ + ${CMAKE_BINARY_DIR}/Resources/ + ) +endif() diff --git a/REUSE.toml b/REUSE.toml index 3bc09e328..43ae001c2 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -19,6 +19,10 @@ path = [ "documents/Screenshots/*", "documents/Screenshots/Linux/*", "externals/MoltenVK/MoltenVK_icd.json", + "Resources/bronze.png", + "Resources/gold.png", + "Resources/platinum.png", + "Resources/silver.png", "scripts/ps4_names.txt", "src/images/about_icon.png", "src/images/controller_icon.png", diff --git a/Resources/bronze.png b/Resources/bronze.png new file mode 100644 index 000000000..208efcc24 Binary files /dev/null and b/Resources/bronze.png differ diff --git a/Resources/gold.png b/Resources/gold.png new file mode 100644 index 000000000..8b4864b70 Binary files /dev/null and b/Resources/gold.png differ diff --git a/Resources/platinum.png b/Resources/platinum.png new file mode 100644 index 000000000..d5ac1629d Binary files /dev/null and b/Resources/platinum.png differ diff --git a/Resources/silver.png b/Resources/silver.png new file mode 100644 index 000000000..cbe1ae457 Binary files /dev/null and b/Resources/silver.png differ diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index ccd8ab710..91dd5b4b4 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -941,7 +941,7 @@ int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTr std::filesystem::path current_icon_path = trophy_dir / "trophy00" / "Icons" / trophy_icon_file; - AddTrophyToQueue(current_icon_path, current_trophy_name); + AddTrophyToQueue(current_icon_path, current_trophy_name, current_trophy_type); } } } @@ -978,7 +978,7 @@ int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTr trophy_dir / "trophy00" / "Icons" / platinum_icon_file; *platinumId = platinum_trophy_id; - AddTrophyToQueue(platinum_icon_path, platinum_trophy_name); + AddTrophyToQueue(platinum_icon_path, platinum_trophy_name, "P"); } } diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp index 4bb8c8240..e40a9390a 100644 --- a/src/core/libraries/np_trophy/trophy_ui.cpp +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -17,14 +17,45 @@ std::optional current_trophy_ui; std::queue trophy_queue; std::mutex queueMtx; -TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::string& trophyName) - : trophy_name(trophyName) { +TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::string& trophyName, + const std::string_view& rarity) + : trophy_name(trophyName), trophy_type(rarity) { + if (std::filesystem::exists(trophyIconPath)) { trophy_icon = RefCountedTexture::DecodePngFile(trophyIconPath); } else { LOG_ERROR(Lib_NpTrophy, "Couldnt load trophy icon at {}", fmt::UTF(trophyIconPath.u8string())); } + + std::filesystem::path trophyTypePath; + std::filesystem::path ResourceDir; + // covers Windows, Mac SDL, locally compiled builds + if (std::filesystem::exists(std::filesystem::current_path() / "Resources")) { + ResourceDir = std::filesystem::current_path() / "Resources"; + } else { +#if defined(__linux__) + const char* AppDir = getenv("APPDIR"); + ResourceDir = std::filesystem::path(AppDir); +#elif defined(__APPLE__) + // for testing on Mac QT + ResourceDir = std::filesystem::current_path().parent_path() / "Resources"; +#endif + } + + if (trophy_type == "P") { + trophyTypePath = ResourceDir / "platinum.png"; + } else if (trophy_type == "G") { + trophyTypePath = ResourceDir / "gold.png"; + } else if (trophy_type == "S") { + trophyTypePath = ResourceDir / "silver.png"; + } else if (trophy_type == "B") { + trophyTypePath = ResourceDir / "bronze.png"; + } + + if (std::filesystem::exists(trophyTypePath)) + trophy_type_icon = RefCountedTexture::DecodePngFile(trophyTypePath); + AddLayer(this); } @@ -42,27 +73,40 @@ void TrophyUI::Draw() { float AdjustWidth = io.DisplaySize.x / 1280; float AdjustHeight = io.DisplaySize.y / 720; const ImVec2 window_size{ - std::min(io.DisplaySize.x, (300 * AdjustWidth)), + std::min(io.DisplaySize.x, (350 * AdjustWidth)), std::min(io.DisplaySize.y, (70 * AdjustHeight)), }; SetNextWindowSize(window_size); SetNextWindowCollapsed(false); - SetNextWindowPos(ImVec2(io.DisplaySize.x - (300 * AdjustWidth), (50 * AdjustHeight))); + SetNextWindowPos(ImVec2(io.DisplaySize.x - (350 * AdjustWidth), (50 * AdjustHeight))); KeepNavHighlight(); if (Begin("Trophy Window", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoInputs)) { if (trophy_icon) { + SetCursorPosY((window_size.y * 0.5f) - (25 * AdjustHeight)); Image(trophy_icon.GetTexture().im_id, ImVec2((50 * AdjustWidth), (50 * AdjustHeight))); - ImGui::SameLine(); } else { // placeholder const auto pos = GetCursorScreenPos(); - ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + ImVec2{50.0f}, + ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + ImVec2{50.0f * AdjustHeight}, GetColorU32(ImVec4{0.7f})); - ImGui::Indent(60); } + + ImGui::SameLine(); + if (trophy_type_icon) { + SetCursorPosY((window_size.y * 0.5f) - (25 * AdjustHeight)); + Image(trophy_type_icon.GetTexture().im_id, + ImVec2((50 * AdjustWidth), (50 * AdjustHeight))); + } else { + // placeholder + const auto pos = GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + ImVec2{50.0f * AdjustHeight}, + GetColorU32(ImVec4{0.7f})); + } + + ImGui::SameLine(); SetWindowFontScale((1.2 * AdjustHeight)); TextWrapped("Trophy earned!\n%s", trophy_name.c_str()); } @@ -74,14 +118,16 @@ void TrophyUI::Draw() { if (!trophy_queue.empty()) { TrophyInfo next_trophy = trophy_queue.front(); trophy_queue.pop(); - current_trophy_ui.emplace(next_trophy.trophy_icon_path, next_trophy.trophy_name); + current_trophy_ui.emplace(next_trophy.trophy_icon_path, next_trophy.trophy_name, + next_trophy.trophy_type); } else { current_trophy_ui.reset(); } } } -void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName) { +void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName, + const std::string_view& rarity) { std::lock_guard lock(queueMtx); if (Config::getisTrophyPopupDisabled()) { @@ -90,10 +136,11 @@ void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::st TrophyInfo new_trophy; new_trophy.trophy_icon_path = trophyIconPath; new_trophy.trophy_name = trophyName; + new_trophy.trophy_type = rarity; trophy_queue.push(new_trophy); } else { - current_trophy_ui.emplace(trophyIconPath, trophyName); + current_trophy_ui.emplace(trophyIconPath, trophyName, rarity); } } -} // namespace Libraries::NpTrophy \ No newline at end of file +} // namespace Libraries::NpTrophy diff --git a/src/core/libraries/np_trophy/trophy_ui.h b/src/core/libraries/np_trophy/trophy_ui.h index ce7a1c63a..16e707059 100644 --- a/src/core/libraries/np_trophy/trophy_ui.h +++ b/src/core/libraries/np_trophy/trophy_ui.h @@ -17,7 +17,8 @@ namespace Libraries::NpTrophy { class TrophyUI final : public ImGui::Layer { public: - TrophyUI(const std::filesystem::path& trophyIconPath, const std::string& trophyName); + TrophyUI(const std::filesystem::path& trophyIconPath, const std::string& trophyName, + const std::string_view& rarity); ~TrophyUI() override; void Finish(); @@ -26,15 +27,19 @@ public: private: std::string trophy_name; + std::string_view trophy_type; float trophy_timer = 5.0f; ImGui::RefCountedTexture trophy_icon; + ImGui::RefCountedTexture trophy_type_icon; }; struct TrophyInfo { std::filesystem::path trophy_icon_path; std::string trophy_name; + std::string_view trophy_type; }; -void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName); +void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName, + const std::string_view& rarity); -}; // namespace Libraries::NpTrophy \ No newline at end of file +}; // namespace Libraries::NpTrophy diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index 4fa5ee5e2..b68f9698a 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -115,10 +115,18 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { item->setData(Qt::DecorationRole, icon); item->setFlags(item->flags() & ~Qt::ItemIsEditable); tableWidget->setItem(row, 1, item); + + std::string detailString = trophyDetails[row].toStdString(); + std::size_t newline_pos = 0; + while ((newline_pos = detailString.find("\n", newline_pos)) != std::string::npos) { + detailString.replace(newline_pos, 1, " "); + ++newline_pos; + } + if (!trophyNames.isEmpty() && !trophyDetails.isEmpty()) { SetTableItem(tableWidget, row, 0, trpUnlocked[row]); SetTableItem(tableWidget, row, 2, trophyNames[row]); - SetTableItem(tableWidget, row, 3, trophyDetails[row]); + SetTableItem(tableWidget, row, 3, QString::fromStdString(detailString)); SetTableItem(tableWidget, row, 4, trpId[row]); SetTableItem(tableWidget, row, 5, trpHidden[row]); SetTableItem(tableWidget, row, 6, GetTrpType(trpType[row].at(0))); @@ -157,7 +165,7 @@ void TrophyViewer::SetTableItem(QTableWidget* parent, int row, int column, QStri label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel layout->addWidget(label); - if (column != 1 && column != 2) + if (column != 1 && column != 2 && column != 3) layout->setAlignment(Qt::AlignCenter); widget->setLayout(layout); parent->setItem(row, column, item);