// Copyright 2010 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/GeckoCode.h" #include #include #include #include #include #include "Common/ChunkFile.h" #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "Core/ConfigManager.h" #include "Core/PowerPC/MMU.h" #include "Core/PowerPC/PowerPC.h" namespace Gecko { static constexpr u32 CODE_SIZE = 8; bool operator==(const GeckoCode& lhs, const GeckoCode& rhs) { return lhs.codes == rhs.codes; } bool operator!=(const GeckoCode& lhs, const GeckoCode& rhs) { return !operator==(lhs, rhs); } bool operator==(const GeckoCode::Code& lhs, const GeckoCode::Code& rhs) { return std::tie(lhs.address, lhs.data) == std::tie(rhs.address, rhs.data); } bool operator!=(const GeckoCode::Code& lhs, const GeckoCode::Code& rhs) { return !operator==(lhs, rhs); } // return true if a code exists bool GeckoCode::Exist(u32 address, u32 data) const { return std::find_if(codes.begin(), codes.end(), [&](const Code& code) { return code.address == address && code.data == data; }) != codes.end(); } enum class Installation { Uninstalled, Installed, Failed }; static Installation s_code_handler_installed = Installation::Uninstalled; // the currently active codes static std::vector s_active_codes; static std::vector s_synced_codes; static std::mutex s_active_codes_lock; void SetActiveCodes(const std::vector& gcodes) { std::lock_guard lk(s_active_codes_lock); s_active_codes.clear(); if (SConfig::GetInstance().bEnableCheats) { s_active_codes.reserve(gcodes.size()); std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes), [](const GeckoCode& code) { return code.enabled; }); } s_active_codes.shrink_to_fit(); s_code_handler_installed = Installation::Uninstalled; } void SetSyncedCodesAsActive() { s_active_codes.clear(); s_active_codes.reserve(s_synced_codes.size()); s_active_codes = s_synced_codes; } void UpdateSyncedCodes(const std::vector& gcodes) { s_synced_codes.clear(); s_synced_codes.reserve(gcodes.size()); std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_synced_codes), [](const GeckoCode& code) { return code.enabled; }); s_synced_codes.shrink_to_fit(); } std::vector SetAndReturnActiveCodes(const std::vector& gcodes) { std::lock_guard lk(s_active_codes_lock); s_active_codes.clear(); if (SConfig::GetInstance().bEnableCheats) { s_active_codes.reserve(gcodes.size()); std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes), [](const GeckoCode& code) { return code.enabled; }); } s_active_codes.shrink_to_fit(); s_code_handler_installed = Installation::Uninstalled; return s_active_codes; } // Requires s_active_codes_lock // NOTE: Refer to "codehandleronly.s" from Gecko OS. static Installation InstallCodeHandlerLocked() { std::string data; if (!File::ReadFileToString(File::GetSysDirectory() + GECKO_CODE_HANDLER, data)) { ERROR_LOG_FMT(ACTIONREPLAY, "Could not enable cheats because " GECKO_CODE_HANDLER " was missing."); return Installation::Failed; } if (data.size() > INSTALLER_END_ADDRESS - INSTALLER_BASE_ADDRESS - CODE_SIZE) { ERROR_LOG_FMT(ACTIONREPLAY, GECKO_CODE_HANDLER " is too big. The file may be corrupt."); return Installation::Failed; } u8 mmio_addr = 0xCC; if (SConfig::GetInstance().bWii) { mmio_addr = 0xCD; } // Install code handler for (u32 i = 0; i < data.size(); ++i) PowerPC::HostWrite_U8(data[i], INSTALLER_BASE_ADDRESS + i); // Patch the code handler to the current system type (Gamecube/Wii) for (unsigned int h = 0; h < data.length(); h += 4) { // Patch MMIO address if (PowerPC::HostRead_U32(INSTALLER_BASE_ADDRESS + h) == (0x3f000000u | ((mmio_addr ^ 1) << 8))) { NOTICE_LOG_FMT(ACTIONREPLAY, "Patching MMIO access at {:08x}", INSTALLER_BASE_ADDRESS + h); PowerPC::HostWrite_U32(0x3f000000u | mmio_addr << 8, INSTALLER_BASE_ADDRESS + h); } } const u32 codelist_base_address = INSTALLER_BASE_ADDRESS + static_cast(data.size()) - CODE_SIZE; const u32 codelist_end_address = INSTALLER_END_ADDRESS; // Write a magic value to 'gameid' (codehandleronly does not actually read this). // This value will be read back and modified over time by HLE_Misc::GeckoCodeHandlerICacheFlush. PowerPC::HostWrite_U32(MAGIC_GAMEID, INSTALLER_BASE_ADDRESS); // Create GCT in memory PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address); PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address + 4); // Each code is 8 bytes (2 words) wide. There is a starter code and an end code. const u32 start_address = codelist_base_address + CODE_SIZE; const u32 end_address = codelist_end_address - CODE_SIZE; u32 next_address = start_address; // NOTE: Only active codes are in the list for (const GeckoCode& active_code : s_active_codes) { // If the code is not going to fit in the space we have left then we have to skip it if (next_address + active_code.codes.size() * CODE_SIZE > end_address) { NOTICE_LOG_FMT(ACTIONREPLAY, "Too many GeckoCodes! Ran out of storage space in Game RAM. Could " "not write: \"{}\". Need {} bytes, only {} remain.", active_code.name, active_code.codes.size() * CODE_SIZE, end_address - next_address); continue; } for (const GeckoCode::Code& code : active_code.codes) { PowerPC::HostWrite_U32(code.address, next_address); PowerPC::HostWrite_U32(code.data, next_address + 4); next_address += CODE_SIZE; } } WARN_LOG_FMT(ACTIONREPLAY, "GeckoCodes: Using {} of {} bytes", next_address - start_address, end_address - start_address); // Stop code. Tells the handler that this is the end of the list. PowerPC::HostWrite_U32(0xF0000000, next_address); PowerPC::HostWrite_U32(0x00000000, next_address + 4); PowerPC::HostWrite_U32(0, HLE_TRAMPOLINE_ADDRESS); // Turn on codes PowerPC::HostWrite_U8(1, INSTALLER_BASE_ADDRESS + 7); // Invalidate the icache and any asm codes for (unsigned int j = 0; j < (INSTALLER_END_ADDRESS - INSTALLER_BASE_ADDRESS); j += 32) { PowerPC::ppcState.iCache.Invalidate(INSTALLER_BASE_ADDRESS + j); } return Installation::Installed; } // Gecko needs to participate in the savestate system because the handler is embedded within the // game directly. The PC may be inside the code handler in the save state and the codehandler.bin // on the disk may be different resulting in the PC pointing at a different instruction and then // the game malfunctions or crashes. [Also, self-modifying codes will break since the // modifications will be reset] void DoState(PointerWrap& p) { std::lock_guard codes_lock(s_active_codes_lock); p.Do(s_code_handler_installed); // FIXME: The active codes list will disagree with the embedded GCT } void Shutdown() { std::lock_guard codes_lock(s_active_codes_lock); s_active_codes.clear(); s_code_handler_installed = Installation::Uninstalled; } void RunCodeHandler() { if (!SConfig::GetInstance().bEnableCheats) return; // NOTE: Need to release the lock because of GUI deadlocks with PanicAlert in HostWrite_* { std::lock_guard codes_lock(s_active_codes_lock); if (s_code_handler_installed != Installation::Installed) { // Don't spam retry if the install failed. The corrupt / missing disk file is not likely to be // fixed within 1 frame of the last error. if (s_active_codes.empty() || s_code_handler_installed == Installation::Failed) return; s_code_handler_installed = InstallCodeHandlerLocked(); // A warning was already issued for the install failing if (s_code_handler_installed != Installation::Installed) return; } } // We always do this to avoid problems with the stack since we're branching in random locations. // Even with function call return hooks (PC == LR), hand coded assembler won't necessarily // follow the ABI. [Volatile FPR, GPR, CR may not be volatile] // The codehandler will STMW all of the GPR registers, but we need to fix the Stack's Red // Zone, the LR, PC (return address) and the volatile floating point registers. // Build a function call stack frame. u32 SFP = GPR(1); // Stack Frame Pointer GPR(1) -= 256; // Stack's Red Zone GPR(1) -= 16 + 2 * 14 * sizeof(u64); // Our stack frame (HLE_Misc::GeckoReturnTrampoline) GPR(1) -= 8; // Fake stack frame for codehandler GPR(1) &= 0xFFFFFFF0; // Align stack to 16bytes u32 SP = GPR(1); // Stack Pointer PowerPC::HostWrite_U32(SP + 8, SP); // SP + 4 is reserved for the codehandler to save LR to the stack. PowerPC::HostWrite_U32(SFP, SP + 8); // Real stack frame PowerPC::HostWrite_U32(PC, SP + 12); PowerPC::HostWrite_U32(LR, SP + 16); PowerPC::HostWrite_U32(PowerPC::ppcState.cr.Get(), SP + 20); // Registers FPR0->13 are volatile for (int i = 0; i < 14; ++i) { PowerPC::HostWrite_U64(rPS(i).PS0AsU64(), SP + 24 + 2 * i * sizeof(u64)); PowerPC::HostWrite_U64(rPS(i).PS1AsU64(), SP + 24 + (2 * i + 1) * sizeof(u64)); } DEBUG_LOG_FMT(ACTIONREPLAY, "GeckoCodes: Initiating phantom branch-and-link. " "PC = {:#010x}, SP = {:#010x}, SFP = {:#010x}", PC, SP, SFP); LR = HLE_TRAMPOLINE_ADDRESS; PC = NPC = ENTRY_POINT; } } // namespace Gecko