#include "Cafe/GraphicPack/GraphicPack2.h" #include "Common/filestream.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h" bool _relocateAddress(PatchGroup* group, PatchContext_t* ctx, uint32 addr, uint32& relocatedAddress) { if (addr >= 0 && addr <= 1024 * 1024 * 8) { // codecave address relocatedAddress = group->getCodeCaveBase() + addr; return true; } // check if address is within module section for (sint32 i = 0; i < ctx->matchedModule->rplHeader.sectionTableEntryCount; i++) { auto sect = ctx->matchedModule->sectionTablePtr + i; if (addr >= sect->virtualAddress && addr < (sect->virtualAddress + sect->sectionSize)) { relocatedAddress = addr - sect->virtualAddress + memory_getVirtualOffsetFromPointer(ctx->matchedModule->sectionAddressTable2[i].ptr); return true; } } relocatedAddress = 0; return false; } struct { bool hasUnknownVariable; PatchContext_t* activePatchContext; PatchGroup* currentGroup; // additional error information tracking sint32 lineNumber; // line number of the expression being processed, negative if not available bool captureUnresolvedSymbols; }resolverState{}; bool GraphicPack2::ResolvePresetConstant(const std::string& varname, double& value) const { const auto var = GetPresetVariable(GetActivePresets(), varname); if (var) { value = var->second; return true; } return false; } template T _expressionFuncHA(T input) { uint32 u32 = (uint32)input; u32 = (((u32 >> 16) + ((u32 & 0x8000) ? 1 : 0)) & 0xffff); return (T)u32; } template T _expressionFuncHI(T input) { uint32 u32 = (uint32)input; u32 = (u32 >> 16) & 0xffff; return (T)u32; } template T _expressionFuncLO(T input) { uint32 u32 = (uint32)input; u32 &= 0xffff; return (T)u32; } template T _expressionFuncReloc(T input) { uint32 addr = (uint32)input; uint32 relocatedAddress = 0; if(!_relocateAddress(resolverState.currentGroup, resolverState.activePatchContext, addr, relocatedAddress)) { resolverState.activePatchContext->errorHandler.printError(resolverState.currentGroup, resolverState.lineNumber, fmt::format("reloc({0:#08x}): Address does not point to a known memory region", addr)); return (T)0; } return (T)relocatedAddress; } double _cbResolveConstant(std::string_view varname) { std::string varnameOnly; std::string tokenOnly; // detect suffix bool hasSuffix = false; const auto idx = varname.find('@'); if (idx != std::string_view::npos) { hasSuffix = true; varnameOnly = varname.substr(0, idx); tokenOnly = varname.substr(idx + 1); } else varnameOnly = varname; double value; if (varnameOnly.length() >= 1 && varnameOnly[0] == '$') { // resolve preset variable if (!resolverState.activePatchContext->graphicPack->ResolvePresetConstant(varnameOnly, value)) { resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, varnameOnly); return 0.0; } } else if (varnameOnly.length() >= 7 && boost::iequals(varnameOnly.substr(0, 7), "import.")) { // resolve import std::string importName = varnameOnly.substr(7); // detect imports const auto idxDot = importName.find('.'); bool isValidImport = false; std::string_view importError = ""; if (idxDot != std::string_view::npos) { std::string moduleName = importName.substr(0, idxDot); std::string functionName = importName.substr(idxDot + 1); uint32 rplHandle = RPLLoader_GetHandleByModuleName(moduleName.c_str()); if (rplHandle == RPL_INVALID_HANDLE) { importError = " (module not found)"; } else { MPTR exportResult = RPLLoader_FindModuleOrHLEExport(rplHandle, false, functionName.c_str()); if (exportResult) { isValidImport = true; value = (double)exportResult; } else importError = " (function not found)"; } } else importError = " (invalid import syntax)"; // error output if (!isValidImport) { resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) { std::string detailedSymbolName; detailedSymbolName.assign(importName); detailedSymbolName.append(importError); resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName); } return 0.0; } } else { // resolve variable const auto v = resolverState.activePatchContext->map_values.find(varnameOnly); if (v == resolverState.activePatchContext->map_values.end()) { resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, varnameOnly); return 0.0; } value = v->second; } if (hasSuffix) { std::transform(tokenOnly.cbegin(), tokenOnly.cend(), tokenOnly.begin(), tolower); if (tokenOnly == "ha") { value = _expressionFuncHA(value); } else if (tokenOnly == "h" || tokenOnly == "hi") { value = _expressionFuncHI(value); } else if (tokenOnly == "l" || tokenOnly == "lo") { value = _expressionFuncLO(value); } else { // we treat unknown suffixes as unresolveable symbols resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) { std::string detailedSymbolName; detailedSymbolName.assign(varnameOnly); detailedSymbolName.append("@"); detailedSymbolName.append(tokenOnly); detailedSymbolName.append(" (invalid suffix)"); resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName); } return 0.0; } } return value; } double _cbResolveFunction(std::string_view funcname, double input) { std::string funcnameLC(funcname); std::transform(funcnameLC.cbegin(), funcnameLC.cend(), funcnameLC.begin(), tolower); double value = input; if (funcnameLC == "ha" || funcnameLC == "ha16") value = _expressionFuncHA(value); else if (funcnameLC == "hi" || funcnameLC == "hi16") value = _expressionFuncHI(value); else if (funcnameLC == "lo" || funcnameLC == "lo16") value = _expressionFuncLO(value); else if (funcnameLC == "reloc") value = _expressionFuncReloc(value); else { // unresolvable function resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) { std::string detailedSymbolName; detailedSymbolName.assign(funcname); detailedSymbolName.append("() (unknown function)"); resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName); } return 0.0; } return value; } template EXPRESSION_RESOLVE_RESULT _resolveExpression(PatchContext_t& ctx, std::string& expressionString, T& result, sint32 associatedLineNumber = -1) { resolverState.lineNumber = associatedLineNumber; ExpressionParser ep; try { // add all the graphic pack constants ep.AddConstantCallback(_cbResolveConstant); ep.SetFunctionCallback(_cbResolveFunction); resolverState.hasUnknownVariable = false; result = (T)ep.Evaluate(expressionString); if (resolverState.hasUnknownVariable) return EXPRESSION_RESOLVE_RESULT::UNKNOWN_VARIABLE; } catch (const std::exception&) { cemu_assert_debug(false); ctx.errorHandler.printError(nullptr, -1, fmt::format("Unexpected error in expression \"{}\"", expressionString)); return EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR; } return EXPRESSION_RESOLVE_RESULT::AVAILABLE; } PATCH_RESOLVE_RESULT translateExpressionResult(EXPRESSION_RESOLVE_RESULT expressionResult) { if (expressionResult == EXPRESSION_RESOLVE_RESULT::AVAILABLE) return PATCH_RESOLVE_RESULT::RESOLVED; else if (expressionResult == EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR) return PATCH_RESOLVE_RESULT::EXPRESSION_ERROR; else if (expressionResult == EXPRESSION_RESOLVE_RESULT::UNKNOWN_VARIABLE) return PATCH_RESOLVE_RESULT::UNKNOWN_VARIABLE; cemu_assert(false); return PATCH_RESOLVE_RESULT::EXPRESSION_ERROR; } PATCH_RESOLVE_RESULT PatchEntryInstruction::resolveReloc(PatchContext_t& ctx, PPCAssemblerReloc* reloc) { MPTR finalRelocAddr = m_relocatedAddr + reloc->m_byteOffset; if (reloc->m_relocType == PPCASM_RELOC::FLOAT) { // resolve float expression float result; auto r = _resolveExpression(ctx, reloc->m_expression, result, m_lineNumber); if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) { cemu_assert((reloc->m_byteOffset + sizeof(betype)) <= m_length); *(betype*)(m_dataWithRelocs + reloc->m_byteOffset) = result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::FLOAT); return PATCH_RESOLVE_RESULT::RESOLVED; } else return translateExpressionResult(r); } else if (reloc->m_relocType == PPCASM_RELOC::DOUBLE) { // resolve double expression double result; auto r = _resolveExpression(ctx, reloc->m_expression, result, m_lineNumber); if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) { cemu_assert((reloc->m_byteOffset + sizeof(betype)) <= m_length); *(betype*)(m_dataWithRelocs + reloc->m_byteOffset) = result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::DOUBLE); return PATCH_RESOLVE_RESULT::RESOLVED; } else return translateExpressionResult(r); } else { // resolve uint32 expression uint32 result; auto r = _resolveExpression(ctx, reloc->m_expression, result, m_lineNumber); if (r != EXPRESSION_RESOLVE_RESULT::AVAILABLE) return translateExpressionResult(r); if (reloc->m_relocType == PPCASM_RELOC::U32) { cemu_assert((reloc->m_byteOffset + sizeof(betype)) <= m_length); *(betype*)(m_dataWithRelocs + reloc->m_byteOffset) = result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::U32); return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::U16) { cemu_assert((reloc->m_byteOffset + sizeof(betype)) <= m_length); *(betype*)(m_dataWithRelocs + reloc->m_byteOffset) = (uint16)result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::U16); return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::U8) { cemu_assert((reloc->m_byteOffset + sizeof(betype)) <= m_length); *(betype*)(m_dataWithRelocs + reloc->m_byteOffset) = (uint8)result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::U8); return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::U32_MASKED_IMM) { cemu_assert((reloc->m_byteOffset + sizeof(betype)) <= m_length); uint32 opcode = *(betype*)(m_dataWithRelocs + reloc->m_byteOffset); cemu_assert_debug(reloc->m_bitCount != 0); uint32 mask = 0xFFFFFFFF >> (32 - reloc->m_bitCount); mask <<= reloc->m_bitOffset; opcode &= ~mask; opcode |= ((result << reloc->m_bitOffset) & mask); *(betype*)(m_dataWithRelocs + reloc->m_byteOffset) = opcode; return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::BRANCH_S26) { cemu_assert((reloc->m_byteOffset + sizeof(betype)) <= m_length); uint32 opcode = *(betype*)(m_dataWithRelocs + reloc->m_byteOffset); if (opcode & 2) { // absolute if (result >= 0x3FFFFFC) { forceLog_printf("Target \'%s\' for branch at line %d out of range", reloc->m_expression.c_str(), m_lineNumber); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0x3FFFFFC; opcode |= (result & 0x3FFFFFC); } else { // relative uint32 instrAddr = this->getRelocatedAddr() + reloc->m_byteOffset; if (result < instrAddr) { // jump backwards uint32 jumpB = instrAddr - result; if (jumpB > 0x1FFFFFF) { ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0x3FFFFFC; opcode |= ((~jumpB + 1) & 0x3FFFFFC); } else { // jump forwards uint32 jumpF = result - instrAddr; if (jumpF >= 0x1FFFFFF) { ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0x3FFFFFC; opcode |= (jumpF & 0x3FFFFFC); } } *(betype*)(m_dataWithRelocs + reloc->m_byteOffset) = opcode; return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::BRANCH_S16) { cemu_assert((reloc->m_byteOffset + sizeof(betype)) <= m_length); uint32 opcode = *(betype*)(m_dataWithRelocs + reloc->m_byteOffset); uint32 instrAddr = this->getRelocatedAddr() + reloc->m_byteOffset; if (result < instrAddr) { // jump backwards uint32 jumpB = instrAddr - result; if (jumpB > 0x8000) { ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0xFFFC; opcode |= ((~jumpB + 1) & 0xFFFC); } else { // jump forwards uint32 jumpF = result - instrAddr; if (jumpF >= 0x8000) { ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0xFFFC; opcode |= (jumpF & 0xFFFC); } *(betype*)(m_dataWithRelocs + reloc->m_byteOffset) = opcode; return PATCH_RESOLVE_RESULT::RESOLVED; } // *internalCtx.opcode |= (relativeAddr & 0xFFFC); cemu_assert_debug(false); } return PATCH_RESOLVE_RESULT::UNDEFINED_ERROR; } PATCH_RESOLVE_RESULT PatchEntryInstruction::resolve(PatchContext_t& ctx) { // relocate patch address if (!m_addrRelocated) { if (_relocateAddress(resolverState.currentGroup, &ctx, m_addr, m_relocatedAddr) == false) { forceLog_printf("Patches: Address 0x%08x (line %d) is not within code cave or any module section", this->getAddr(), this->m_lineNumber); cemu_assert_debug(false); return PATCH_RESOLVE_RESULT::INVALID_ADDRESS; } m_addrRelocated = true; } // apply relocations to instruction for (auto& itr : this->m_relocs) { if(itr.isApplied()) continue; // evaluate expression and apply reloc to internal buffer auto r = resolveReloc(ctx, &itr); if (r == PATCH_RESOLVE_RESULT::RESOLVED) { itr.setApplied(); continue; } return r; } return PATCH_RESOLVE_RESULT::RESOLVED; } void PatchEntryInstruction::applyPatch() { const uint32 addr = getRelocatedAddr(); if (addr == 0) { cemu_assert_debug(false); return; } uint8* patchAddr = (uint8*)memory_base + addr; memcpy(m_dataBackup, patchAddr, m_length); memcpy(patchAddr, m_dataWithRelocs, m_length); PPCRecompiler_invalidateRange(addr, addr + m_length); } void PatchEntryInstruction::undoPatch() { const uint32 addr = getRelocatedAddr(); if (addr == 0) { cemu_assert_debug(false); return; } uint8* patchAddr = (uint8*)memory_base + addr; memcpy(patchAddr, m_dataBackup, m_length); PPCRecompiler_invalidateRange(addr, addr + m_length); rplSymbolStorage_removeRange(addr, m_length); DebugSymbolStorage::ClearRange(addr, m_length); } // returns true on success, false if variable with same name already exists bool registerU32Variable(PatchContext_t& ctx, std::string& name, uint32 value, PatchGroup* associatedPatchGroup, uint32 associatedLineNumber, bool isAddress) { cemuLog_log(LogType::Patches, "Resolved symbol {} with value 0x{:08x}", name.c_str(), value); if (ctx.map_values.find(name) != ctx.map_values.end()) { return false; } ctx.map_values[name] = value; // keep track of address symbols for the debugger rplSymbolStorage_store(ctx.graphicPack->GetName().data(), name.data(), value); return true; } PATCH_RESOLVE_RESULT PatchEntryCemuhookSymbolValue::resolve(PatchContext_t& ctx) { uint32 addr; auto r = _resolveExpression(ctx, m_expressionString, addr, m_lineNumber); if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) { if (_relocateAddress(resolverState.currentGroup, &ctx, addr, m_resolvedValue)) { m_isResolved = true; // register variable if (!registerU32Variable(ctx, m_symbolName, m_resolvedValue, resolverState.currentGroup, getLineNumber(), true)) { if (resolverState.captureUnresolvedSymbols) ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Symbol {} is already defined", m_symbolName)); return PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT; } return PATCH_RESOLVE_RESULT::RESOLVED; } return PATCH_RESOLVE_RESULT::INVALID_ADDRESS; } return translateExpressionResult(r); } PATCH_RESOLVE_RESULT PatchEntryLabel::resolve(PatchContext_t& ctx) { if (_relocateAddress(resolverState.currentGroup, &ctx, m_address, m_relocatedAddress)) { m_isResolved = true; // register variable if (!registerU32Variable(ctx, m_symbolName, m_relocatedAddress, resolverState.currentGroup, getLineNumber(), true)) { if (resolverState.captureUnresolvedSymbols) ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Label {} is already defined", m_symbolName)); return PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT; } return PATCH_RESOLVE_RESULT::RESOLVED; } if(resolverState.captureUnresolvedSymbols) ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Address {:#08x} of label {} does not point to any module section or code cave", m_address, m_symbolName)); return PATCH_RESOLVE_RESULT::INVALID_ADDRESS; } PATCH_RESOLVE_RESULT PatchEntryVariableValue::resolve(PatchContext_t& ctx) { uint32 v; auto r = _resolveExpression(ctx, m_expressionString, v, m_lineNumber); if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) { // register variable if (!registerU32Variable(ctx, m_symbolName, v, resolverState.currentGroup, getLineNumber(), false)) { if (resolverState.captureUnresolvedSymbols) ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Variable {} is already defined", m_symbolName)); return PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT; } return PATCH_RESOLVE_RESULT::RESOLVED; } return translateExpressionResult(r); } struct UnresolvedPatches_t { PatchGroup* patchGroup; std::vector list_unresolvedPatches; }; // returns number of resolved entries bool _resolverPass(PatchContext_t& patchContext, std::vector& unresolvedPatches, bool captureUnresolvedSymbols = false) { resolverState.captureUnresolvedSymbols = captureUnresolvedSymbols; sint32 numResolvedEntries = 0; for (auto& unresolvedGroup : unresolvedPatches) { resolverState.currentGroup = unresolvedGroup.patchGroup; auto& list_unresolvedPatches = unresolvedGroup.list_unresolvedPatches; for (auto it = list_unresolvedPatches.begin(); it != list_unresolvedPatches.end();) { auto r = (*it)->resolve(patchContext); if (r == PATCH_RESOLVE_RESULT::RESOLVED) { // remove from list it = list_unresolvedPatches.erase(it); numResolvedEntries++; continue; } else if (r == PATCH_RESOLVE_RESULT::UNKNOWN_VARIABLE) { // dependency on other not yet resolved entry, continue iterating it++; continue; } else if (r == PATCH_RESOLVE_RESULT::INVALID_ADDRESS || r == PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT) { // errors handled and printed inside resolve() it++; continue; } else { // unknown error patchContext.errorHandler.printError(resolverState.currentGroup, -1, "Internal error"); it++; } } } return numResolvedEntries; } void GraphicPack2::ApplyPatchGroups(std::vector& groups, const RPLModule* rpl) { // init context information PatchContext_t patchContext{}; patchContext.graphicPack = this; patchContext.matchedModule = rpl; resolverState.activePatchContext = &patchContext; // setup error handler patchContext.errorHandler.setCurrentGraphicPack(this); patchContext.errorHandler.setStage(PatchErrorHandler::STAGE::APPLY); // no group can be applied more than once for (auto patchGroup : groups) { if (patchGroup->isApplied()) { patchContext.errorHandler.printError(patchGroup, -1, "Group already applied to a different module."); return; } } // allocate code cave for every group for (auto patchGroup : groups) { if (patchGroup->codeCaveSize > 0) { auto codeCaveMem = RPLLoader_AllocateCodeCaveMem(256, patchGroup->codeCaveSize); forceLog_printf("Applying patch group \'%s\' (Codecave: %08x-%08x)", patchGroup->name.c_str(), codeCaveMem.GetMPTR(), codeCaveMem.GetMPTR() + patchGroup->codeCaveSize); patchGroup->codeCaveMem = codeCaveMem; } else { forceLog_printf("Applying patch group \'%s\'", patchGroup->name.c_str()); patchGroup->codeCaveMem = nullptr; } } // resolve the patch entries // this means: // - resolving the expressions for variables and registering them // - calculating relocated addresses // - applying relocations to temporary patch buffer // multiple passes may be necessary since forward and backward references are allowed as well as references across group boundaries // create a copy of all the patch references and keep the group association intact std::vector unresolvedPatches; unresolvedPatches.resize(groups.size()); for (size_t i = 0; i < groups.size(); i++) { unresolvedPatches[i].patchGroup = groups[i]; unresolvedPatches[i].list_unresolvedPatches = groups[i]->list_patches; } auto isUnresolvedPatchesEmpty = [&unresolvedPatches]() { for (auto& itr : unresolvedPatches) if (!itr.list_unresolvedPatches.empty()) return false; return true; }; // resolve and relocate for (sint32 pass = 0; pass < 30; pass++) { bool isLastPass = (pass == 29); sint32 numResolvedEntries = _resolverPass(patchContext, unresolvedPatches, false); if (isUnresolvedPatchesEmpty()) break; if (numResolvedEntries == 0 || isLastPass) { // stuck due to reference to undefined variable or unresolvable cross-references // iterate all remaining expressions and output them to log // execute another resolver pass but capture all the unresolved variables this time patchContext.unresolvedSymbols.clear(); _resolverPass(patchContext, unresolvedPatches, true); // generate messages if(isLastPass) patchContext.errorHandler.printError(nullptr, -1, "Some symbols could not be resolved because the dependency chain is too deep"); for (auto& itr : patchContext.unresolvedSymbols) patchContext.errorHandler.printError(itr.patchGroup, itr.lineNumber, fmt::format("Unresolved symbol: {}", itr.symbolName)); patchContext.errorHandler.showStageErrorMessageBox(); return; } } if (!isUnresolvedPatchesEmpty() || patchContext.errorHandler.hasError()) { patchContext.errorHandler.showStageErrorMessageBox(); return; } // apply relocated patches for (auto patchGroup : groups) { for (auto& patch : patchGroup->list_patches) { PatchEntryInstruction* patchInstruction = dynamic_cast(patch); if (patchInstruction == nullptr) continue; patchInstruction->applyPatch(); } } // mark groups as applied for (auto patchGroup : groups) patchGroup->setApplied(); } void GraphicPack2::UndoPatchGroups(std::vector& groups, const RPLModule* rpl) { // restore original data for (auto patchGroup : groups) { if (!patchGroup->isApplied()) continue; for (auto& patch : patchGroup->list_patches) { PatchEntryInstruction* patchInstruction = dynamic_cast(patch); if (patchInstruction == nullptr) continue; patchInstruction->undoPatch(); } } // mark groups as not applied for (auto patchGroup : groups) patchGroup->resetApplied(); } void GraphicPack2::NotifyModuleLoaded(const RPLModule* rpl) { cemuLog_force("Loaded module \'{}\' with checksum 0x{:08x}", rpl->moduleName2, rpl->patchCRC); std::lock_guard lock(mtx_patches); list_modules.emplace_back(rpl); // todo - iterate all active graphic packs and apply any matching patch groups } void GraphicPack2::NotifyModuleUnloaded(const RPLModule* rpl) { std::lock_guard lock(mtx_patches); list_modules.erase(std::remove(list_modules.begin(), list_modules.end(), rpl), list_modules.end()); }