From 2c03ac32170445412b6236e6af143f0b2e531bf1 Mon Sep 17 00:00:00 2001 From: Luminyx <27lumi@protonmail.com> Date: Sun, 8 Feb 2026 20:20:34 -0500 Subject: [PATCH] GraphicPack: Support wildcard titleId and RPX hash + entrypoint callbacks (#1805) --- src/Cafe/GraphicPack/GraphicPack2.cpp | 13 +++- src/Cafe/GraphicPack/GraphicPack2.h | 8 ++- src/Cafe/GraphicPack/GraphicPack2Patches.cpp | 4 +- src/Cafe/GraphicPack/GraphicPack2Patches.h | 6 ++ .../GraphicPack/GraphicPack2PatchesApply.cpp | 15 +++++ .../GraphicPack/GraphicPack2PatchesParser.cpp | 35 ++++++++++- src/Cafe/OS/RPL/rpl.cpp | 1 + src/Cafe/OS/RPL/rpl_structs.h | 9 ++- src/Cafe/OS/libs/coreinit/coreinit_Init.cpp | 13 ++++ src/gui/wxgui/GraphicPacksWindow2.cpp | 59 ++++++++++--------- 10 files changed, 127 insertions(+), 36 deletions(-) diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index aded9188..295d5dce 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -328,7 +328,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) } m_title_ids = ParseTitleIds(rules, "titleIds"); - if(m_title_ids.empty()) + if(m_title_ids.empty() && !m_universal) throw std::exception(); auto option_fsPriority = rules.FindOption("fsPriority"); @@ -532,6 +532,9 @@ std::string GraphicPack2::GetNormalizedPathString() const bool GraphicPack2::ContainsTitleId(uint64_t title_id) const { + if (m_universal) + return true; + const auto it = std::find_if(m_title_ids.begin(), m_title_ids.end(), [title_id](uint64 id) { return id == title_id; }); return it != m_title_ids.end(); } @@ -1188,7 +1191,7 @@ std::vector GraphicPack2::GetActivePresets() const return result; } -std::vector GraphicPack2::ParseTitleIds(IniParser& rules, const char* option_name) const +std::vector GraphicPack2::ParseTitleIds(IniParser& rules, const char* option_name) { std::vector result; @@ -1196,6 +1199,12 @@ std::vector GraphicPack2::ParseTitleIds(IniParser& rules, const char* op if (!option_text) return result; + if (*option_text == "*") + { + m_universal = true; + return result; + } + for (auto& token : TokenizeView(*option_text, ',')) { try diff --git a/src/Cafe/GraphicPack/GraphicPack2.h b/src/Cafe/GraphicPack/GraphicPack2.h index b83ac66c..e33f0946 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.h +++ b/src/Cafe/GraphicPack/GraphicPack2.h @@ -109,6 +109,7 @@ public: bool Reload(); bool HasName() const { return !m_name.empty(); } + bool IsUniversal() const { return m_universal; } const std::string& GetName() const { return m_name.empty() ? m_virtualPath : m_name; } const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy @@ -122,6 +123,8 @@ public: const std::vector& GetTitleIds() const { return m_title_ids; } bool HasCustomVSyncFrequency() const { return m_vsync_frequency >= 1; } sint32 GetCustomVSyncFrequency() const { return m_vsync_frequency; } + + const std::vector>& GetCallbacks() const { return m_callbacks; } // texture rules const std::vector& GetTextureRules() const { return m_texture_rules; } @@ -229,6 +232,7 @@ private: bool m_activated = false; // set if the graphic pack is currently used by the running game std::vector m_title_ids; bool m_patchedFilesLoaded = false; // set to true once patched files are loaded + bool m_universal = false; // set if this pack applies to every title id sint32 m_vsync_frequency = -1; sint32 m_fs_priority = 100; @@ -256,7 +260,7 @@ private: std::unordered_map ParsePresetVars(IniParser& rules) const; - std::vector ParseTitleIds(IniParser& rules, const char* option_name) const; + std::vector ParseTitleIds(IniParser& rules, const char* option_name); CustomShader LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type, bool isMetalShader) const; void ApplyShaderPresets(std::string& shader_source) const; @@ -282,6 +286,8 @@ private: void LogPatchesSyntaxError(sint32 lineNumber, std::string_view errorMsg); std::vector list_patchGroups; + + std::vector> m_callbacks; static std::recursive_mutex mtx_patches; static std::vector list_modules; diff --git a/src/Cafe/GraphicPack/GraphicPack2Patches.cpp b/src/Cafe/GraphicPack/GraphicPack2Patches.cpp index d0d00bf2..7ceaf9d4 100644 --- a/src/Cafe/GraphicPack/GraphicPack2Patches.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2Patches.cpp @@ -172,7 +172,7 @@ void GraphicPack2::ApplyPatchesForModule(const RPLModule* rpl) std::vector list_groups; for (auto itr : list_patchGroups) { - if (itr->matchesCRC(rpl->patchCRC)) + if (itr->matchesCRC(rpl->patchCRC) || (itr->m_isRpxOnlyTarget && rpl->IsRPX())) list_groups.emplace_back(itr); } // apply all groups at once @@ -188,7 +188,7 @@ void GraphicPack2::RevertPatchesForModule(const RPLModule* rpl) std::vector list_groups; for (auto itr : list_patchGroups) { - if (itr->matchesCRC(rpl->patchCRC)) + if (itr->matchesCRC(rpl->patchCRC) || (itr->m_isRpxOnlyTarget && rpl->IsRPX())) list_groups.emplace_back(itr); } // undo all groups at once diff --git a/src/Cafe/GraphicPack/GraphicPack2Patches.h b/src/Cafe/GraphicPack/GraphicPack2Patches.h index b33cabf9..34750533 100644 --- a/src/Cafe/GraphicPack/GraphicPack2Patches.h +++ b/src/Cafe/GraphicPack/GraphicPack2Patches.h @@ -212,6 +212,10 @@ private: bool m_addrRelocated{}; }; +enum class GPCallbackType { + Entry +}; + class PatchGroup { friend class GraphicPack2; @@ -256,7 +260,9 @@ private: std::string name; std::vector list_moduleMatches; std::vector list_patches; + std::vector> list_callbacks; uint32 codeCaveSize; MEMPTR codeCaveMem; bool m_isApplied{}; + bool m_isRpxOnlyTarget{}; }; \ No newline at end of file diff --git a/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp b/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp index b6af542d..10e9743b 100644 --- a/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp @@ -710,6 +710,21 @@ void GraphicPack2::ApplyPatchGroups(std::vector& groups, const RPLM continue; patchInstruction->applyPatch(); } + + for (const auto& [name, type] : patchGroup->list_callbacks) + { + auto it = patchContext.map_values.find(name); + if (it != patchContext.map_values.end()) + { + m_callbacks.push_back(std::make_pair(it->second, type)); + } + else + { + patchContext.errorHandler.printError(patchGroup, -1, fmt::format("Failed to resolve .callback symbol: {}", name)); + patchContext.errorHandler.showStageErrorMessageBox(); + return; + } + } } // mark groups as applied for (auto patchGroup : groups) diff --git a/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp b/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp index 05f8c696..d93cc151 100644 --- a/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp @@ -41,7 +41,7 @@ void GraphicPack2::CancelParsingPatches() void GraphicPack2::AddPatchGroup(PatchGroup* group) { - if (group->list_moduleMatches.empty()) + if (group->list_moduleMatches.empty() && !group->m_isRpxOnlyTarget) { LogPatchesSyntaxError(-1, fmt::format("Group \"{}\" has no moduleMatches definition", group->name)); CancelParsingPatches(); @@ -347,6 +347,12 @@ bool GraphicPack2::ParseCemuPatchesTxtInternal(MemStreamReader& patchesStream) // read the checksums while (true) { + if (parser.matchWordI("rpx")) + { + currentGroup->m_isRpxOnlyTarget = true; + break; + } + uint32 checksum = 0; if (parser.parseU32(checksum) == false) { @@ -425,7 +431,32 @@ bool GraphicPack2::ParseCemuPatchesTxtInternal(MemStreamReader& patchesStream) } continue; } - + else if (parser.matchWordI(".callback")) + { + if (parser.matchWordI("entry")) + { + const char* symbolStr; + sint32 symbolLen; + if (parser.parseSymbolName(symbolStr, symbolLen)) + { + currentGroup->list_callbacks.push_back(std::make_pair(std::string(symbolStr, static_cast(symbolLen)), GPCallbackType::Entry)); + continue; + } + else + { + LogPatchesSyntaxError(lineNumber, "'.callback' must reference a symbol after the type"); + CancelParsingPatches(); + return false; + } + } + else + { + LogPatchesSyntaxError(lineNumber, "Unrecognized type for '.callback'"); + CancelParsingPatches(); + return false; + } + } + // next we attempt to parse symbol assignment // symbols can be labels or variables. The type is determined by what comes after the symbol name // = defines a variable diff --git a/src/Cafe/OS/RPL/rpl.cpp b/src/Cafe/OS/RPL/rpl.cpp index f39d82be..d39ea95e 100644 --- a/src/Cafe/OS/RPL/rpl.cpp +++ b/src/Cafe/OS/RPL/rpl.cpp @@ -267,6 +267,7 @@ bool RPLLoader_ProcessHeaders(std::string_view moduleName, uint8* rplData, uint3 rplLoaderContext->fileInfo.tlsModuleIndex = fileInfoPtr->tlsModuleIndex; rplLoaderContext->fileInfo.sdataBase1 = fileInfoPtr->sdataBase1; rplLoaderContext->fileInfo.sdataBase2 = fileInfoPtr->sdataBase2; + rplLoaderContext->fileInfo.flags = fileInfoPtr->flags; // init section address table rplLoaderContext->sectionAddressTable2.resize(sectionCount); diff --git a/src/Cafe/OS/RPL/rpl_structs.h b/src/Cafe/OS/RPL/rpl_structs.h index 4b283f07..aa734dae 100644 --- a/src/Cafe/OS/RPL/rpl_structs.h +++ b/src/Cafe/OS/RPL/rpl_structs.h @@ -113,7 +113,7 @@ typedef struct /* +0x28 */ uint32be sdataBase2; /* +0x2C */ uint32be ukn2C; /* +0x30 */ uint32be ukn30; - /* +0x34 */ uint32be ukn34; + /* +0x34 */ uint32be flags; /* +0x38 */ uint32be ukn38; /* +0x3C */ uint32be ukn3C; /* +0x40 */ uint32be minimumToolkitVersion; @@ -198,6 +198,8 @@ struct RPLModule uint32 sdataBase1; uint32 sdataBase2; + + uint32 flags; }fileInfo; // parsed CRC std::vector crcTable; @@ -208,6 +210,11 @@ struct RPLModule return 0; return crcTable[sectionIndex]; } + + bool IsRPX() const + { + return fileInfo.flags & 2; + } // state bool isLinked; // set to true if _linkModule was called on this module diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp index 72f6ac11..04e7d07d 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp @@ -11,6 +11,7 @@ #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/OS/libs/coreinit/coreinit_FG.h" #include "Cafe/CafeSystem.h" +#include "Cafe/GraphicPack/GraphicPack2.h" extern MPTR _entryPoint; extern RPLModule* applicationRPX; @@ -211,6 +212,18 @@ void coreinit_start(PPCInterpreter_t* hCPU) padscore::start(); vpad::start(); + // call entry-type callbacks in graphic packs + for (const auto gp : GraphicPack2::GetActiveGraphicPacks()) + { + for (const auto [callback, type] : gp->GetCallbacks()) + { + if (type == GPCallbackType::Entry) + { + PPCCoreCallback(callback); + } + } + } + // continue at main executable entrypoint hCPU->gpr[4] = memory_getVirtualOffsetFromPointer(_coreinitInfo->argv); hCPU->gpr[3] = _coreinitInfo->argc; diff --git a/src/gui/wxgui/GraphicPacksWindow2.cpp b/src/gui/wxgui/GraphicPacksWindow2.cpp index b6f016ff..90f5e17c 100644 --- a/src/gui/wxgui/GraphicPacksWindow2.cpp +++ b/src/gui/wxgui/GraphicPacksWindow2.cpp @@ -43,44 +43,47 @@ void GraphicPacksWindow2::FillGraphicPackList() const for(auto& p : graphic_packs) { - // filter graphic packs by given title id - if (m_filter_installed_games && !m_installed_games.empty()) + if (!p->IsUniversal()) { - bool found = false; - for (uint64 titleId : p->GetTitleIds()) - { - if (std::find(m_installed_games.cbegin(), m_installed_games.cend(), titleId) != m_installed_games.cend()) - { - found = true; - break; - } - } - - if (!found) - continue; - } - - // filter graphic packs by given title id - if(has_filter) - { - bool found = false; - - if (boost::icontains(p->GetVirtualPath(), m_filter)) - found = true; - else + // filter graphic packs by given title id + if (m_filter_installed_games && !m_installed_games.empty()) { + bool found = false; for (uint64 titleId : p->GetTitleIds()) { - if (boost::icontains(fmt::format("{:x}", titleId), m_filter)) + if (std::find(m_installed_games.cbegin(), m_installed_games.cend(), titleId) != m_installed_games.cend()) { found = true; break; } } + + if (!found) + continue; + } + + // filter graphic packs by given title id + if(has_filter) + { + bool found = false; + + if (boost::icontains(p->GetVirtualPath(), m_filter)) + found = true; + else + { + for (uint64 titleId : p->GetTitleIds()) + { + if (boost::icontains(fmt::format("{:x}", titleId), m_filter)) + { + found = true; + break; + } + } + } + + if (!found) + continue; } - - if (!found) - continue; } const auto& path = p->GetVirtualPath();