diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b1c0082b..820d46f9d 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) @@ -953,6 +954,12 @@ set(QT_GUI src/qt_gui/about_dialog.cpp ) endif() +set(RESOURCEFOLDER Resources/bronze.png + Resources/gold.png + Resources/platinum.png + Resources/silver.png +) + if (ENABLE_QT_GUI) qt_add_executable(shadps4 ${AUDIO_CORE} @@ -964,6 +971,7 @@ if (ENABLE_QT_GUI) ${SHADER_RECOMPILER} ${VIDEO_CORE} ${EMULATOR} + ${RESOURCEFOLDER} src/images/shadPS4.icns ) else() @@ -976,6 +984,7 @@ else() ${SHADER_RECOMPILER} ${VIDEO_CORE} ${EMULATOR} + ${RESOURCEFOLDER} src/main.cpp src/emulator.cpp src/emulator.h @@ -1102,6 +1111,16 @@ add_subdirectory(${HOST_SHADERS_INCLUDE}) add_dependencies(shadps4 host_shaders) target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE}) +# embed resources + +include(CMakeRC) +cmrc_add_resource_library(embedded-resources + ALIAS res::embedded + NAMESPACE res + ${RESOURCEFOLDER}) + +target_link_libraries(shadps4 PRIVATE res::embedded) + # ImGui resources add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/imgui/renderer) add_dependencies(shadps4 ImGui_Resources) diff --git a/REUSE.toml b/REUSE.toml index 3bc09e328..9c6ada42a 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", @@ -103,3 +107,7 @@ path = "externals/gcn/include/**" SPDX-FileCopyrightText = "NONE" 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 diff --git a/Resources/bronze.png b/Resources/bronze.png new file mode 100644 index 000000000..65aa2fb69 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..1af7023f5 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..c217fdd02 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..0d9ff8ff6 Binary files /dev/null and b/Resources/silver.png differ diff --git a/cmake/CMakeRC.cmake b/cmake/CMakeRC.cmake new file mode 100644 index 000000000..cea12fba7 --- /dev/null +++ b/cmake/CMakeRC.cmake @@ -0,0 +1,666 @@ +# MIT License +# +# Copyright (c) 2017 vector-of-bool +# +# 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. + +# This block is executed when generating an intermediate resource file, not when +# running in CMake configure mode +if(_CMRC_GENERATE_MODE) + # Read in the digits + file(READ "${INPUT_FILE}" bytes HEX) + # Format each pair into a character literal. Heuristics seem to favor doing + # the conversion in groups of five for fastest conversion + string(REGEX REPLACE "(..)(..)(..)(..)(..)" "'\\\\x\\1','\\\\x\\2','\\\\x\\3','\\\\x\\4','\\\\x\\5'," chars "${bytes}") + # Since we did this in groups, we have some leftovers to clean up + string(LENGTH "${bytes}" n_bytes2) + math(EXPR n_bytes "${n_bytes2} / 2") + math(EXPR remainder "${n_bytes} % 5") # <-- '5' is the grouping count from above + set(cleanup_re "$") + set(cleanup_sub ) + while(remainder) + set(cleanup_re "(..)${cleanup_re}") + set(cleanup_sub "'\\\\x\\${remainder}',${cleanup_sub}") + math(EXPR remainder "${remainder} - 1") + endwhile() + if(NOT cleanup_re STREQUAL "$") + string(REGEX REPLACE "${cleanup_re}" "${cleanup_sub}" chars "${chars}") + endif() + string(CONFIGURE [[ + namespace { const char file_array[] = { @chars@ 0 }; } + namespace cmrc { namespace @NAMESPACE@ { namespace res_chars { + extern const char* const @SYMBOL@_begin = file_array; + extern const char* const @SYMBOL@_end = file_array + @n_bytes@; + }}} + ]] code) + file(WRITE "${OUTPUT_FILE}" "${code}") + # Exit from the script. Nothing else needs to be processed + return() +endif() + +set(_version 2.0.0) + +cmake_minimum_required(VERSION 3.12) +include(CMakeParseArguments) + +if(COMMAND cmrc_add_resource_library) + if(NOT DEFINED _CMRC_VERSION OR NOT (_version STREQUAL _CMRC_VERSION)) + message(WARNING "More than one CMakeRC version has been included in this project.") + endif() + # CMakeRC has already been included! Don't do anything + return() +endif() + +set(_CMRC_VERSION "${_version}" CACHE INTERNAL "CMakeRC version. Used for checking for conflicts") + +set(_CMRC_SCRIPT "${CMAKE_CURRENT_LIST_FILE}" CACHE INTERNAL "Path to CMakeRC script") + +function(_cmrc_normalize_path var) + set(path "${${var}}") + file(TO_CMAKE_PATH "${path}" path) + while(path MATCHES "//") + string(REPLACE "//" "/" path "${path}") + endwhile() + string(REGEX REPLACE "/+$" "" path "${path}") + set("${var}" "${path}" PARENT_SCOPE) +endfunction() + +get_filename_component(_inc_dir "${CMAKE_BINARY_DIR}/_cmrc/include" ABSOLUTE) +set(CMRC_INCLUDE_DIR "${_inc_dir}" CACHE INTERNAL "Directory for CMakeRC include files") +# Let's generate the primary include file +file(MAKE_DIRECTORY "${CMRC_INCLUDE_DIR}/cmrc") +set(hpp_content [==[ +#ifndef CMRC_CMRC_HPP_INCLUDED +#define CMRC_CMRC_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !(defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) || defined(CMRC_NO_EXCEPTIONS)) +#define CMRC_NO_EXCEPTIONS 1 +#endif + +namespace cmrc { namespace detail { struct dummy; } } + +#define CMRC_DECLARE(libid) \ + namespace cmrc { namespace detail { \ + struct dummy; \ + static_assert(std::is_same::value, "CMRC_DECLARE() must only appear at the global namespace"); \ + } } \ + namespace cmrc { namespace libid { \ + cmrc::embedded_filesystem get_filesystem(); \ + } } static_assert(true, "") + +namespace cmrc { + +class file { + const char* _begin = nullptr; + const char* _end = nullptr; + +public: + using iterator = const char*; + using const_iterator = iterator; + iterator begin() const noexcept { return _begin; } + iterator cbegin() const noexcept { return _begin; } + iterator end() const noexcept { return _end; } + iterator cend() const noexcept { return _end; } + std::size_t size() const { return static_cast(std::distance(begin(), end())); } + + file() = default; + file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {} +}; + +class directory_entry; + +namespace detail { + +class directory; +class file_data; + +class file_or_directory { + union _data_t { + class file_data* file_data; + class directory* directory; + } _data; + bool _is_file = true; + +public: + explicit file_or_directory(file_data& f) { + _data.file_data = &f; + } + explicit file_or_directory(directory& d) { + _data.directory = &d; + _is_file = false; + } + bool is_file() const noexcept { + return _is_file; + } + bool is_directory() const noexcept { + return !is_file(); + } + const directory& as_directory() const noexcept { + assert(!is_file()); + return *_data.directory; + } + const file_data& as_file() const noexcept { + assert(is_file()); + return *_data.file_data; + } +}; + +class file_data { +public: + const char* begin_ptr; + const char* end_ptr; + file_data(const file_data&) = delete; + file_data(const char* b, const char* e) : begin_ptr(b), end_ptr(e) {} +}; + +inline std::pair split_path(const std::string& path) { + auto first_sep = path.find("/"); + if (first_sep == path.npos) { + return std::make_pair(path, ""); + } else { + return std::make_pair(path.substr(0, first_sep), path.substr(first_sep + 1)); + } +} + +struct created_subdirectory { + class directory& directory; + class file_or_directory& index_entry; +}; + +class directory { + std::list _files; + std::list _dirs; + std::map _index; + + using base_iterator = std::map::const_iterator; + +public: + + directory() = default; + directory(const directory&) = delete; + + created_subdirectory add_subdir(std::string name) & { + _dirs.emplace_back(); + auto& back = _dirs.back(); + auto& fod = _index.emplace(name, file_or_directory{back}).first->second; + return created_subdirectory{back, fod}; + } + + file_or_directory* add_file(std::string name, const char* begin, const char* end) & { + assert(_index.find(name) == _index.end()); + _files.emplace_back(begin, end); + return &_index.emplace(name, file_or_directory{_files.back()}).first->second; + } + + const file_or_directory* get(const std::string& path) const { + auto pair = split_path(path); + auto child = _index.find(pair.first); + if (child == _index.end()) { + return nullptr; + } + auto& entry = child->second; + if (pair.second.empty()) { + // We're at the end of the path + return &entry; + } + + if (entry.is_file()) { + // We can't traverse into a file. Stop. + return nullptr; + } + // Keep going down + return entry.as_directory().get(pair.second); + } + + class iterator { + base_iterator _base_iter; + base_iterator _end_iter; + public: + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + using iterator_category = std::input_iterator_tag; + + iterator() = default; + explicit iterator(base_iterator iter, base_iterator end) : _base_iter(iter), _end_iter(end) {} + + iterator begin() const noexcept { + return *this; + } + + iterator end() const noexcept { + return iterator(_end_iter, _end_iter); + } + + inline value_type operator*() const noexcept; + + bool operator==(const iterator& rhs) const noexcept { + return _base_iter == rhs._base_iter; + } + + bool operator!=(const iterator& rhs) const noexcept { + return !(*this == rhs); + } + + iterator& operator++() noexcept { + ++_base_iter; + return *this; + } + + iterator operator++(int) noexcept { + auto cp = *this; + ++_base_iter; + return cp; + } + }; + + using const_iterator = iterator; + + iterator begin() const noexcept { + return iterator(_index.begin(), _index.end()); + } + + iterator end() const noexcept { + return iterator(); + } +}; + +inline std::string normalize_path(std::string path) { + while (path.find("/") == 0) { + path.erase(path.begin()); + } + while (!path.empty() && (path.rfind("/") == path.size() - 1)) { + path.pop_back(); + } + auto off = path.npos; + while ((off = path.find("//")) != path.npos) { + path.erase(path.begin() + static_cast(off)); + } + return path; +} + +using index_type = std::map; + +} // detail + +class directory_entry { + std::string _fname; + const detail::file_or_directory* _item; + +public: + directory_entry() = delete; + explicit directory_entry(std::string filename, const detail::file_or_directory& item) + : _fname(filename) + , _item(&item) + {} + + const std::string& filename() const & { + return _fname; + } + std::string filename() const && { + return std::move(_fname); + } + + bool is_file() const { + return _item->is_file(); + } + + bool is_directory() const { + return _item->is_directory(); + } +}; + +directory_entry detail::directory::iterator::operator*() const noexcept { + assert(begin() != end()); + return directory_entry(_base_iter->first, _base_iter->second); +} + +using directory_iterator = detail::directory::iterator; + +class embedded_filesystem { + // Never-null: + const cmrc::detail::index_type* _index; + const detail::file_or_directory* _get(std::string path) const { + path = detail::normalize_path(path); + auto found = _index->find(path); + if (found == _index->end()) { + return nullptr; + } else { + return found->second; + } + } + +public: + explicit embedded_filesystem(const detail::index_type& index) + : _index(&index) + {} + + file open(const std::string& path) const { + auto entry_ptr = _get(path); + if (!entry_ptr || !entry_ptr->is_file()) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); +#endif + } + auto& dat = entry_ptr->as_file(); + return file{dat.begin_ptr, dat.end_ptr}; + } + + bool is_file(const std::string& path) const noexcept { + auto entry_ptr = _get(path); + return entry_ptr && entry_ptr->is_file(); + } + + bool is_directory(const std::string& path) const noexcept { + auto entry_ptr = _get(path); + return entry_ptr && entry_ptr->is_directory(); + } + + bool exists(const std::string& path) const noexcept { + return !!_get(path); + } + + directory_iterator iterate_directory(const std::string& path) const { + auto entry_ptr = _get(path); + if (!entry_ptr) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); +#endif + } + if (!entry_ptr->is_directory()) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error not a directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::not_a_directory), path); +#endif + } + return entry_ptr->as_directory().begin(); + } +}; + +} + +#endif // CMRC_CMRC_HPP_INCLUDED +]==]) + +set(cmrc_hpp "${CMRC_INCLUDE_DIR}/cmrc/cmrc.hpp" CACHE INTERNAL "") +set(_generate 1) +if(EXISTS "${cmrc_hpp}") + file(READ "${cmrc_hpp}" _current) + if(_current STREQUAL hpp_content) + set(_generate 0) + endif() +endif() +file(GENERATE OUTPUT "${cmrc_hpp}" CONTENT "${hpp_content}" CONDITION ${_generate}) + +add_library(cmrc-base INTERFACE) +target_include_directories(cmrc-base INTERFACE $) +# Signal a basic C++11 feature to require C++11. +target_compile_features(cmrc-base INTERFACE cxx_nullptr) +set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF) +add_library(cmrc::base ALIAS cmrc-base) + +function(cmrc_add_resource_library name) + set(args ALIAS NAMESPACE TYPE) + cmake_parse_arguments(ARG "" "${args}" "" "${ARGN}") + # Generate the identifier for the resource library's namespace + set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*") + if(NOT DEFINED ARG_NAMESPACE) + # Check that the library name is also a valid namespace + if(NOT name MATCHES "${ns_re}") + message(SEND_ERROR "Library name is not a valid namespace. Specify the NAMESPACE argument") + endif() + set(ARG_NAMESPACE "${name}") + else() + if(NOT ARG_NAMESPACE MATCHES "${ns_re}") + message(SEND_ERROR "NAMESPACE for ${name} is not a valid C++ namespace identifier (${ARG_NAMESPACE})") + endif() + endif() + set(libname "${name}") + # Check that type is either "STATIC" or "OBJECT", or default to "STATIC" if + # not set + if(NOT DEFINED ARG_TYPE) + set(ARG_TYPE STATIC) + elseif(NOT "${ARG_TYPE}" MATCHES "^(STATIC|OBJECT)$") + message(SEND_ERROR "${ARG_TYPE} is not a valid TYPE (STATIC and OBJECT are acceptable)") + set(ARG_TYPE STATIC) + endif() + # Generate a library with the compiled in character arrays. + string(CONFIGURE [=[ + #include + #include + #include + + namespace cmrc { + namespace @ARG_NAMESPACE@ { + + namespace res_chars { + // These are the files which are available in this resource library + $, + > + } + + namespace { + + const cmrc::detail::index_type& + get_root_index() { + static cmrc::detail::directory root_directory_; + static cmrc::detail::file_or_directory root_directory_fod{root_directory_}; + static cmrc::detail::index_type root_index; + root_index.emplace("", &root_directory_fod); + struct dir_inl { + class cmrc::detail::directory& directory; + }; + dir_inl root_directory_dir{root_directory_}; + (void)root_directory_dir; + $, + > + $, + > + return root_index; + } + + } + + cmrc::embedded_filesystem get_filesystem() { + static auto& index = get_root_index(); + return cmrc::embedded_filesystem{index}; + } + + } // @ARG_NAMESPACE@ + } // cmrc + ]=] cpp_content @ONLY) + get_filename_component(libdir "${CMAKE_CURRENT_BINARY_DIR}/__cmrc_${name}" ABSOLUTE) + get_filename_component(lib_tmp_cpp "${libdir}/lib_.cpp" ABSOLUTE) + string(REPLACE "\n " "\n" cpp_content "${cpp_content}") + file(GENERATE OUTPUT "${lib_tmp_cpp}" CONTENT "${cpp_content}") + get_filename_component(libcpp "${libdir}/lib.cpp" ABSOLUTE) + add_custom_command(OUTPUT "${libcpp}" + DEPENDS "${lib_tmp_cpp}" "${cmrc_hpp}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${lib_tmp_cpp}" "${libcpp}" + COMMENT "Generating ${name} resource loader" + ) + # Generate the actual static library. Each source file is just a single file + # with a character array compiled in containing the contents of the + # corresponding resource file. + add_library(${name} ${ARG_TYPE} ${libcpp}) + set_property(TARGET ${name} PROPERTY CMRC_LIBDIR "${libdir}") + set_property(TARGET ${name} PROPERTY CMRC_NAMESPACE "${ARG_NAMESPACE}") + target_link_libraries(${name} PUBLIC cmrc::base) + set_property(TARGET ${name} PROPERTY CMRC_IS_RESOURCE_LIBRARY TRUE) + if(ARG_ALIAS) + add_library("${ARG_ALIAS}" ALIAS ${name}) + endif() + cmrc_add_resources(${name} ${ARG_UNPARSED_ARGUMENTS}) +endfunction() + +function(_cmrc_register_dirs name dirpath) + if(dirpath STREQUAL "") + return() + endif() + # Skip this dir if we have already registered it + get_target_property(registered "${name}" _CMRC_REGISTERED_DIRS) + if(dirpath IN_LIST registered) + return() + endif() + # Register the parent directory first + get_filename_component(parent "${dirpath}" DIRECTORY) + if(NOT parent STREQUAL "") + _cmrc_register_dirs("${name}" "${parent}") + endif() + # Now generate the registration + set_property(TARGET "${name}" APPEND PROPERTY _CMRC_REGISTERED_DIRS "${dirpath}") + _cm_encode_fpath(sym "${dirpath}") + if(parent STREQUAL "") + set(parent_sym root_directory) + else() + _cm_encode_fpath(parent_sym "${parent}") + endif() + get_filename_component(leaf "${dirpath}" NAME) + set_property( + TARGET "${name}" + APPEND PROPERTY CMRC_MAKE_DIRS + "static auto ${sym}_dir = ${parent_sym}_dir.directory.add_subdir(\"${leaf}\")\;" + "root_index.emplace(\"${dirpath}\", &${sym}_dir.index_entry)\;" + ) +endfunction() + +function(cmrc_add_resources name) + get_target_property(is_reslib ${name} CMRC_IS_RESOURCE_LIBRARY) + if(NOT TARGET ${name} OR NOT is_reslib) + message(SEND_ERROR "cmrc_add_resources called on target '${name}' which is not an existing resource library") + return() + endif() + + set(options) + set(args WHENCE PREFIX) + set(list_args) + cmake_parse_arguments(ARG "${options}" "${args}" "${list_args}" "${ARGN}") + + if(NOT ARG_WHENCE) + set(ARG_WHENCE ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + _cmrc_normalize_path(ARG_WHENCE) + get_filename_component(ARG_WHENCE "${ARG_WHENCE}" ABSOLUTE) + + # Generate the identifier for the resource library's namespace + get_target_property(lib_ns "${name}" CMRC_NAMESPACE) + + get_target_property(libdir ${name} CMRC_LIBDIR) + get_target_property(target_dir ${name} SOURCE_DIR) + file(RELATIVE_PATH reldir "${target_dir}" "${CMAKE_CURRENT_SOURCE_DIR}") + if(reldir MATCHES "^\\.\\.") + message(SEND_ERROR "Cannot call cmrc_add_resources in a parent directory from the resource library target") + return() + endif() + + foreach(input IN LISTS ARG_UNPARSED_ARGUMENTS) + _cmrc_normalize_path(input) + get_filename_component(abs_in "${input}" ABSOLUTE) + # Generate a filename based on the input filename that we can put in + # the intermediate directory. + file(RELATIVE_PATH relpath "${ARG_WHENCE}" "${abs_in}") + if(relpath MATCHES "^\\.\\.") + # For now we just error on files that exist outside of the soure dir. + message(SEND_ERROR "Cannot add file '${input}': File must be in a subdirectory of ${ARG_WHENCE}") + continue() + endif() + if(DEFINED ARG_PREFIX) + _cmrc_normalize_path(ARG_PREFIX) + endif() + if(ARG_PREFIX AND NOT ARG_PREFIX MATCHES "/$") + set(ARG_PREFIX "${ARG_PREFIX}/") + endif() + get_filename_component(dirpath "${ARG_PREFIX}${relpath}" DIRECTORY) + _cmrc_register_dirs("${name}" "${dirpath}") + get_filename_component(abs_out "${libdir}/intermediate/${ARG_PREFIX}${relpath}.cpp" ABSOLUTE) + # Generate a symbol name relpath the file's character array + _cm_encode_fpath(sym "${relpath}") + # Get the symbol name for the parent directory + if(dirpath STREQUAL "") + set(parent_sym root_directory) + else() + _cm_encode_fpath(parent_sym "${dirpath}") + endif() + # Generate the rule for the intermediate source file + _cmrc_generate_intermediate_cpp(${lib_ns} ${sym} "${abs_out}" "${abs_in}") + target_sources(${name} PRIVATE "${abs_out}") + set_property(TARGET ${name} APPEND PROPERTY CMRC_EXTERN_DECLS + "// Pointers to ${input}" + "extern const char* const ${sym}_begin\;" + "extern const char* const ${sym}_end\;" + ) + get_filename_component(leaf "${relpath}" NAME) + set_property( + TARGET ${name} + APPEND PROPERTY CMRC_MAKE_FILES + "root_index.emplace(" + " \"${ARG_PREFIX}${relpath}\"," + " ${parent_sym}_dir.directory.add_file(" + " \"${leaf}\"," + " res_chars::${sym}_begin," + " res_chars::${sym}_end" + " )" + ")\;" + ) + endforeach() +endfunction() + +function(_cmrc_generate_intermediate_cpp lib_ns symbol outfile infile) + add_custom_command( + # This is the file we will generate + OUTPUT "${outfile}" + # These are the primary files that affect the output + DEPENDS "${infile}" "${_CMRC_SCRIPT}" + COMMAND + "${CMAKE_COMMAND}" + -D_CMRC_GENERATE_MODE=TRUE + -DNAMESPACE=${lib_ns} + -DSYMBOL=${symbol} + "-DINPUT_FILE=${infile}" + "-DOUTPUT_FILE=${outfile}" + -P "${_CMRC_SCRIPT}" + COMMENT "Generating intermediate file for ${infile}" + ) +endfunction() + +function(_cm_encode_fpath var fpath) + string(MAKE_C_IDENTIFIER "${fpath}" ident) + string(MD5 hash "${fpath}") + string(SUBSTRING "${hash}" 0 4 hash) + set(${var} f_${hash}_${ident} PARENT_SCOPE) +endfunction() 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..e5c11f4cb 100644 --- a/src/core/libraries/np_trophy/trophy_ui.cpp +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "common/assert.h" #include "common/config.h" @@ -10,6 +11,8 @@ #include "imgui/imgui_std.h" #include "trophy_ui.h" +CMRC_DECLARE(res); + using namespace ImGui; namespace Libraries::NpTrophy { @@ -17,14 +20,34 @@ 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::string pathString; + if (trophy_type == "P") { + pathString = "Resources/platinum.png"; + } else if (trophy_type == "G") { + pathString = "Resources/gold.png"; + } else if (trophy_type == "S") { + pathString = "Resources/silver.png"; + } else if (trophy_type == "B") { + pathString = "Resources/bronze.png"; + } + + auto resource = cmrc::res::get_filesystem(); + auto trophytypefile = resource.open(pathString); + std::filesystem::path trophyTypePath = pathString; + if (std::filesystem::exists(trophyTypePath)) + trophy_type_icon = RefCountedTexture::DecodePngFile(trophyTypePath); + AddLayer(this); } @@ -42,29 +65,49 @@ 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 - (370 * AdjustWidth), (50 * AdjustHeight))); KeepNavHighlight(); if (Begin("Trophy Window", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoInputs)) { - if (trophy_icon) { - Image(trophy_icon.GetTexture().im_id, ImVec2((50 * AdjustWidth), (50 * AdjustHeight))); - 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}, + ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + ImVec2{50.0f * AdjustHeight}, GetColorU32(ImVec4{0.7f})); - ImGui::Indent(60); } + + ImGui::SameLine(); + SetWindowFontScale((1.2 * AdjustHeight)); + char earned_text[] = "Trophy earned!\n%s"; + const float text_height = + ImGui::CalcTextSize(std::strcat(earned_text, trophy_name.c_str())).y; + SetCursorPosY((window_size.y - text_height) * 0.5f); + + ImGui::PushTextWrapPos(window_size.x - (60 * AdjustWidth)); TextWrapped("Trophy earned!\n%s", trophy_name.c_str()); + ImGui::SameLine(window_size.x - (60 * AdjustWidth)); + + if (trophy_icon) { + SetCursorPosY((window_size.y * 0.5f) - (25 * AdjustHeight)); + Image(trophy_icon.GetTexture().im_id, ImVec2((50 * AdjustWidth), (50 * AdjustHeight))); + } else { + // placeholder + const auto pos = GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + ImVec2{30.0f * AdjustHeight}, + GetColorU32(ImVec4{0.7f})); + } } End(); @@ -74,14 +117,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 +135,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..697840268 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -2,9 +2,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/path_util.h" #include "trophy_viewer.h" +CMRC_DECLARE(res); + TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindow() { this->setWindowTitle(tr("Trophy Viewer")); this->setAttribute(Qt::WA_DeleteOnClose); @@ -115,13 +118,34 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { item->setData(Qt::DecorationRole, icon); item->setFlags(item->flags() & ~Qt::ItemIsEditable); tableWidget->setItem(row, 1, item); + + const std::string filename = GetTrpType(trpType[row].at(0)); + QTableWidgetItem* typeitem = new QTableWidgetItem(); + + auto resource = cmrc::res::get_filesystem(); + std::string resourceString = "Resources/" + filename; + auto trophytypefile = resource.open(resourceString); + + QImage type_icon = + QImage(QString::fromStdString(resourceString)) + .scaled(QSize(64, 64), Qt::KeepAspectRatio, Qt::SmoothTransformation); + typeitem->setData(Qt::DecorationRole, type_icon); + typeitem->setFlags(typeitem->flags() & ~Qt::ItemIsEditable); + tableWidget->setItem(row, 6, typeitem); + + 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))); SetTableItem(tableWidget, row, 7, trpPid[row]); } tableWidget->verticalHeader()->resizeSection(row, icon.height()); @@ -157,7 +181,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); diff --git a/src/qt_gui/trophy_viewer.h b/src/qt_gui/trophy_viewer.h index 81b9b1adc..089de433e 100644 --- a/src/qt_gui/trophy_viewer.h +++ b/src/qt_gui/trophy_viewer.h @@ -32,17 +32,17 @@ private: QString gameTrpPath_; TRP trp; - QString GetTrpType(const QChar trp_) { + std::string GetTrpType(const QChar trp_) { switch (trp_.toLatin1()) { case 'B': - return "Bronze"; + return "bronze.png"; case 'S': - return "Silver"; + return "silver.png"; case 'G': - return "Gold"; + return "gold.png"; case 'P': - return "Platinum"; + return "platinum.png"; } return "Unknown"; } -}; \ No newline at end of file +};