diff --git a/Data/Sys/bootloader.gct b/Data/Sys/bootloader.gct new file mode 100644 index 0000000000..a46c729ff0 Binary files /dev/null and b/Data/Sys/bootloader.gct differ diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index aaf7716ed7..a041c07f05 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -128,6 +128,7 @@ #define WII_SETTING "setting.txt" #define GECKO_CODE_HANDLER "codehandler.bin" +#define GCT_BOOTLOADER "bootloader.gct" // Subdirs in Sys #define GC_SYS_DIR "GC" diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 6a8a4461f8..0adde651bf 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -41,6 +41,7 @@ namespace fs = std::filesystem; #include "Core/Config/SYSCONFSettings.h" #include "Core/ConfigManager.h" #include "Core/FifoPlayer/FifoPlayer.h" +#include "Core/GeckoCode.h" #include "Core/HLE/HLE.h" #include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/EXI/EXI_DeviceIPL.h" @@ -537,6 +538,7 @@ bool CBoot::BootUp(std::unique_ptr boot) PatchEngine::LoadPatches(); HLE::PatchFixedFunctions(); + Gecko::RunCodeHandler(); return true; } diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 0cebb583bd..592c6a6ad0 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -51,6 +51,7 @@ #include "VideoCommon/HiresTextures.h" #include "DiscIO/Enums.h" +#include "DiscIO/Filesystem.h" #include "DiscIO/Volume.h" #include "DiscIO/VolumeWad.h" @@ -959,7 +960,32 @@ bool SConfig::SetPathsAndGameMetadata(const BootParameters& boot) const std::string region_dir = GetDirectoryForRegion(ToGameCubeRegion(m_region)); m_strSRAM = File::GetUserPath(F_GCSRAM_IDX); m_strBootROM = GetBootROMPath(region_dir); - m_strIsoPath = (boot.parameters.index() == 0) ? std::get(boot.parameters).path : ""; + m_strIsoPath = + (boot.parameters.index() == 0) ? std::get(boot.parameters).path : ""; + + std::shared_ptr volume = DiscIO::CreateVolume(m_strIsoPath); + + if (m_game_id == "GALE01" || m_game_id == "GALJ01") + { + m_melee_version = Melee::Version::NTSC; + + if (volume->GetLongNames()[DiscIO::Language::English].find("20XX") != std::string::npos) + m_melee_version = Melee::Version::TwentyXX; + else + { + // check for m-ex based build + if (volume->FileExists("MxDt.dat")) + { + m_melee_version = Melee::Version::MEX; + } + } + } + else if (m_game_id == "GTME01") + { + m_melee_version = Melee::Version::UPTM; + } + + INFO_LOG_FMT(BOOT, "Melee Version: {}", m_melee_version); return true; } diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 4c031c805d..5d7d5a2b88 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -45,6 +46,18 @@ namespace SerialInterface enum SIDevices : int; } // namespace SerialInterface +namespace Melee +{ +enum class Version +{ + NTSC, + TwentyXX, + UPTM, + MEX, + OTHER, +}; +} + struct BootParameters; // DSP Backend Types @@ -67,6 +80,8 @@ enum class GPUDeterminismMode struct SConfig { + // Melee Version + Melee::Version m_melee_version; // Wii Devices bool m_WiiSDCard; bool m_WiiKeyboard; diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 3296af2daa..8412137694 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -19,6 +19,8 @@ #include "Core/PowerPC/MMU.h" #include "Core/PowerPC/PowerPC.h" +#include "VideoCommon/OnScreenDisplay.h" + namespace Gecko { static constexpr u32 CODE_SIZE = 8; @@ -68,6 +70,8 @@ void SetActiveCodes(const std::vector& gcodes) { std::lock_guard lk(s_active_codes_lock); + DEBUG_LOG_FMT(ACTIONREPLAY, "Setting up active codes..."); + s_active_codes.clear(); if (SConfig::GetInstance().bEnableCheats) { @@ -153,69 +157,84 @@ static Installation InstallCodeHandlerLocked() } } - u32 codelist_base_address; - u32 codelist_end_address; - // Let the Gecko codehandler use free space from Melee's tournament mode region - if (SConfig::GetInstance().GetGameID() == "GALE01") + u32 codelist_base_address = INSTALLER_BASE_ADDRESS + static_cast(data.length()) - CODE_SIZE; + u32 codelist_end_address = INSTALLER_END_ADDRESS; + + // Write a magic value to 'gameid' (codehandleronly does not actually read this). + PowerPC::HostWrite_U32(MAGIC_GAMEID, INSTALLER_BASE_ADDRESS); + + // Install the custom bootloader to write gecko codes to the heaps + if (SConfig::GetInstance().m_melee_version == Melee::Version::NTSC || + SConfig::GetInstance().m_melee_version == Melee::Version::MEX) { - INFO_LOG(ACTIONREPLAY, - "Detected GALE01 - using tournament mode region for Gecko codelist"); + // Write GCT loader into memory which will eventually load the real GCT into the heap + std::string bootloaderData; + std::string _bootloaderFilename = File::GetSysDirectory() + GCT_BOOTLOADER; + if (!File::ReadFileToString(_bootloaderFilename, bootloaderData)) + { + OSD::AddMessage("bootloader.gct not found in Sys folder.", 30000, 0xFFFF0000); + ERROR_LOG_FMT(ACTIONREPLAY, "Could not enable cheats because bootloader.gct was missing."); + return Installation::Failed; + } - // Set codelist base to the tournament mode region - codelist_base_address = 0x801910E0; - codelist_end_address = 0x8019AF4C; + if (bootloaderData.length() > codelist_end_address - codelist_base_address) + { + OSD::AddMessage("Gecko bootloader too large.", 30000, 0xFFFF0000); + ERROR_LOG_FMT(SLIPPI, "Gecko bootloader too large"); + return Installation::Failed; + } - // Patch codehandler to use tournament mode region - PowerPC::HostWrite_U32(0x3DE08019, 0x80001904); // lis r15, 0x8019 - PowerPC::HostWrite_U32(0x61EF10E0, 0x80001908); // ori r15, r15, 0x10e0 + // Install bootloader gct + for (size_t i = 0; i < bootloaderData.length(); ++i) + PowerPC::HostWrite_U8(bootloaderData[i], static_cast(codelist_base_address + i)); } else { - codelist_base_address = INSTALLER_BASE_ADDRESS + static_cast(data.size()) - CODE_SIZE; - codelist_end_address = INSTALLER_END_ADDRESS; - } + // Create GCT in memory + PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address); + PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address + 4); - // 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); + // 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; - // 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) + // NOTE: Only active codes are in the list + for (const GeckoCode& active_code : s_active_codes) { - 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; + // 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) + { + OSD::AddMessage( + fmt::format("Ran out of memory applying gecko codes. Too many codes enabled. " + "Need {} bytes, only {} remain.", + active_code.codes.size() * CODE_SIZE, end_address - next_address), + 30000, 0xFFFF0000); + 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; + } } - 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); } - 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); + // Write 0 to trampoline address, not sure why this is necessary PowerPC::HostWrite_U32(0, HLE_TRAMPOLINE_ADDRESS); // Turn on codes @@ -226,6 +245,7 @@ static Installation InstallCodeHandlerLocked() { PowerPC::ppcState.iCache.Invalidate(INSTALLER_BASE_ADDRESS + j); } + return Installation::Installed; } @@ -296,10 +316,75 @@ void RunCodeHandler() } DEBUG_LOG_FMT(ACTIONREPLAY, "GeckoCodes: Initiating phantom branch-and-link. " - "PC = {:#010x}, SP = {:#010x}, SFP = {:#010x}", + "PC = {:#010x}, SP = {:#010x}, SFP = {:#010x}\n", PC, SP, SFP); LR = HLE_TRAMPOLINE_ADDRESS; PC = NPC = ENTRY_POINT; } +u32 GetGctLength() +{ + std::lock_guard lk(s_active_codes_lock); + + int i = 0; + + for (const GeckoCode& active_code : s_active_codes) + { + if (active_code.enabled) + { + i += 8 * static_cast(active_code.codes.size()); + } + } + + return i + 0x10; // 0x10 is the fixed size of the header and terminator +} + +std::vector uint32ToVector(u32 num) +{ + u8 byte0 = num >> 24; + u8 byte1 = (num & 0xFF0000) >> 16; + u8 byte2 = (num & 0xFF00) >> 8; + u8 byte3 = num & 0xFF; + + return std::vector({byte0, byte1, byte2, byte3}); +} + +void appendWordToBuffer(std::vector* buf, u32 word) +{ + auto wordVector = uint32ToVector(word); + buf->insert(buf->end(), wordVector.begin(), wordVector.end()); +} + +std::vector GenerateGct() +{ + std::vector res; + + // Write header + appendWordToBuffer(&res, 0x00d0c0de); + appendWordToBuffer(&res, 0x00d0c0de); + + std::lock_guard lk(s_active_codes_lock); + + int i = 0; + + // Write codes + for (const GeckoCode& active_code : s_active_codes) + { + if (active_code.enabled) + { + for (const GeckoCode::Code& code : active_code.codes) + { + appendWordToBuffer(&res, code.address); + appendWordToBuffer(&res, code.data); + } + } + } + + // Write footer + appendWordToBuffer(&res, 0xff000000); + appendWordToBuffer(&res, 0x00000000); + + return res; +} + } // namespace Gecko diff --git a/Source/Core/Core/GeckoCode.h b/Source/Core/Core/GeckoCode.h index 9c6c7c7a14..9f80ba8951 100644 --- a/Source/Core/Core/GeckoCode.h +++ b/Source/Core/Core/GeckoCode.h @@ -66,4 +66,7 @@ void RunCodeHandler(); void Shutdown(); void DoState(PointerWrap&); +u32 GetGctLength(); +std::vector GenerateGct(); + } // namespace Gecko diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp index e4f91a2e0b..7f955a11db 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp @@ -20,11 +20,13 @@ #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/Debugger/Debugger_SymbolMap.h" +#include "Core/GeckoCode.h" #include "Core/HW/EXI/EXI_DeviceSlippi.h" #include "Core/HW/Memmap.h" #include "Core/HW/SystemTimers.h" #include "Core/Host.h" #include "Core/NetPlayClient.h" +#include "Core/PowerPC/PowerPC.h" #include "Core/Slippi/SlippiPlayback.h" #include "Core/Slippi/SlippiReplayComm.h" #include "Core/State.h" @@ -2316,6 +2318,32 @@ void CEXISlippi::prepareFileLoad(u8* payload) m_read_queue.insert(m_read_queue.end(), buf.begin(), buf.end()); } +void CEXISlippi::prepareGctLength() +{ + m_read_queue.clear(); + + u32 size = Gecko::GetGctLength(); + + INFO_LOG(SLIPPI, "Getting gct size: %d", size); + + // Write size to output + appendWordToBuffer(&m_read_queue, size); +} + +void CEXISlippi::prepareGctLoad(u8* payload) +{ + m_read_queue.clear(); + + auto gct = Gecko::GenerateGct(); + + // This is the address where the codes will be written to + auto address = Common::swap32(&payload[0]); + + INFO_LOG(SLIPPI, "Preparing to write gecko codes at: 0x%X", address); + + m_read_queue.insert(m_read_queue.end(), gct.begin(), gct.end()); +} + void CEXISlippi::handleChatMessage(u8* payload) { int messageId = payload[0]; @@ -2618,6 +2646,12 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize) case CMD_REPORT_GAME: handleReportGame(&memPtr[bufLoc + 1]); break; + case CMD_GCT_LENGTH: + prepareGctLength(); + break; + case CMD_GCT_LOAD: + prepareGctLoad(&memPtr[bufLoc + 1]); + break; default: writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, ""); SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h index c905443ff4..f172579112 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h @@ -10,6 +10,7 @@ #include "Common/File.h" #include "Common/FileUtil.h" #include "Core/Slippi/SlippiGameFileLoader.h" +#include "Core/Slippi/SlippiGameReporter.h" #include "Core/Slippi/SlippiMatchmaking.h" #include "Core/Slippi/SlippiNetplay.h" #include "Core/Slippi/SlippiPlayback.h" @@ -17,7 +18,6 @@ #include "Core/Slippi/SlippiSavestate.h" #include "Core/Slippi/SlippiSpectate.h" #include "Core/Slippi/SlippiUser.h" -#include "Core/Slippi/SlippiGameReporter.h" #include "EXI_Device.h" #define ROLLBACK_MAX_FRAMES 7 @@ -79,6 +79,8 @@ private: CMD_LOG_MESSAGE = 0xD0, CMD_FILE_LENGTH = 0xD1, CMD_FILE_LOAD = 0xD2, + CMD_GCT_LENGTH = 0xD3, + CMD_GCT_LOAD = 0xD4, }; enum @@ -124,6 +126,8 @@ private: {CMD_LOG_MESSAGE, 0xFFFF}, // Variable size... will only work if by itself {CMD_FILE_LENGTH, 0x40}, {CMD_FILE_LOAD, 0x40}, + {CMD_GCT_LENGTH, 0x0}, + {CMD_GCT_LOAD, 0x4}, }; struct WriteMessage @@ -174,7 +178,7 @@ private: void prepareOnlineStatus(); void handleConnectionCleanup(); void prepareNewSeed(); - void handleReportGame(u8 *payload); + void handleReportGame(u8* payload); // replay playback stuff void prepareGameInfo(u8* payload); @@ -185,10 +189,12 @@ private: void prepareIsFileReady(); // misc stuff - void handleChatMessage(u8 *payload); + void handleChatMessage(u8* payload); void logMessageFromGame(u8* payload); void prepareFileLength(u8* payload); void prepareFileLoad(u8* payload); + void prepareGctLength(); + void prepareGctLoad(u8* payload); int getCharColor(u8 charId, u8 teamId); void FileWriteThread(void); @@ -222,7 +228,7 @@ private: std::string forcedError = ""; // Used to determine when to detect when a new session has started - bool is_play_session_active = false; + bool is_play_session_active = false; // Frame skipping variables int framesToSkip = 0; diff --git a/Source/Core/Core/HW/SystemTimers.cpp b/Source/Core/Core/HW/SystemTimers.cpp index c2b05c563c..57309e8921 100644 --- a/Source/Core/Core/HW/SystemTimers.cpp +++ b/Source/Core/Core/HW/SystemTimers.cpp @@ -77,7 +77,7 @@ CoreTiming::EventType* et_AudioDMA; CoreTiming::EventType* et_DSP; CoreTiming::EventType* et_IPC_HLE; // PatchEngine updates every 1/60th of a second by default -CoreTiming::EventType* et_PatchEngine; +// CoreTiming::EventType* et_PatchEngine; CoreTiming::EventType* et_Throttle; u32 s_cpu_core_clock = 486000000u; // 486 mhz (its not 485, stop bugging me!) @@ -163,7 +163,7 @@ void PatchEngineCallback(u64 userdata, s64 cycles_late) cycles_pruned += next_schedule; } - CoreTiming::ScheduleEvent(next_schedule, et_PatchEngine, cycles_pruned); + // CoreTiming::ScheduleEvent(next_schedule, et_PatchEngine, cycles_pruned); } void ThrottleCallback(u64 last_time, s64 cyclesLate) @@ -326,7 +326,7 @@ void Init() et_DSP = CoreTiming::RegisterEvent("DSPCallback", DSPCallback); et_AudioDMA = CoreTiming::RegisterEvent("AudioDMACallback", AudioDMACallback); et_IPC_HLE = CoreTiming::RegisterEvent("IPC_HLE_UpdateCallback", IPC_HLE_UpdateCallback); - et_PatchEngine = CoreTiming::RegisterEvent("PatchEngine", PatchEngineCallback); + // et_PatchEngine = CoreTiming::RegisterEvent("PatchEngine", PatchEngineCallback); et_Throttle = CoreTiming::RegisterEvent("Throttle", ThrottleCallback); CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerHalfLine(), et_VI); @@ -334,7 +334,7 @@ void Init() CoreTiming::ScheduleEvent(s_audio_dma_period, et_AudioDMA); CoreTiming::ScheduleEvent(0, et_Throttle, Common::Timer::GetTimeUs()); - CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerField(), et_PatchEngine); + // CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerField(), et_PatchEngine); if (SConfig::GetInstance().bWii) CoreTiming::ScheduleEvent(s_ipc_hle_period, et_IPC_HLE); diff --git a/Source/Core/DiscIO/Volume.cpp b/Source/Core/DiscIO/Volume.cpp index ab4b0e562b..bcbc21b688 100644 --- a/Source/Core/DiscIO/Volume.cpp +++ b/Source/Core/DiscIO/Volume.cpp @@ -85,6 +85,26 @@ std::map Volume::ReadWiiNames(const std::vector return names; } +bool Volume::FileExists(std::string file_name) +{ + std::vector partitions = this->GetPartitions(); + if (partitions.empty()) + partitions.emplace_back(PARTITION_NONE); + + for (const auto& partition : partitions) + { + const DiscIO::FileInfo& root_dir = this->GetFileSystem(partition)->GetRoot(); + for (const auto& file_info : root_dir) + { + if (file_info.GetName() == file_name) + { + return true; + } + } + } + return false; +} + static std::unique_ptr CreateDisc(std::unique_ptr& reader) { // Check for Wii diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index 0673ab00af..b172bbb772 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -152,6 +152,8 @@ public: // The way the hash is calculated may change with updates to Dolphin. virtual std::array GetSyncHash() const = 0; + bool FileExists(std::string file_name); + protected: template std::string DecodeString(const char (&data)[N]) const