forked from dolphin-emu/dolphin
		
	BreakBeforeBraces: Allman apparently includes all styles, except for AfterUnion (which is false) when using clang-format -dump-config
		
			
				
	
	
		
			955 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			955 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright 2008 Dolphin Emulator Project
 | |
| // Licensed under GPLv2+
 | |
| // Refer to the license.txt file included.
 | |
| 
 | |
| // -----------------------------------------------------------------------------------------
 | |
| // Partial Action Replay code system implementation.
 | |
| // Will never be able to support some AR codes - specifically those that patch the running
 | |
| // Action Replay engine itself - yes they do exist!!!
 | |
| // Action Replay actually is a small virtual machine with a limited number of commands.
 | |
| // It probably is Turing complete - but what does that matter when AR codes can write
 | |
| // actual PowerPC code...
 | |
| // -----------------------------------------------------------------------------------------
 | |
| 
 | |
| // -------------------------------------------------------------------------------------------------------------
 | |
| // Code Types:
 | |
| // (Unconditional) Normal Codes (0): this one has subtypes inside
 | |
| // (Conditional) Normal Codes (1 - 7): these just compare values and set the line skip info
 | |
| // Zero Codes: any code with no address.  These codes are used to do special operations like memory
 | |
| // copy, etc
 | |
| // -------------------------------------------------------------------------------------------------------------
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <atomic>
 | |
| #include <iterator>
 | |
| #include <mutex>
 | |
| #include <string>
 | |
| #include <unordered_set>
 | |
| #include <utility>
 | |
| #include <vector>
 | |
| 
 | |
| #include "Common/CommonTypes.h"
 | |
| #include "Common/IniFile.h"
 | |
| #include "Common/Logging/LogManager.h"
 | |
| #include "Common/MsgHandler.h"
 | |
| #include "Common/StringUtil.h"
 | |
| 
 | |
| #include "Core/ARDecrypt.h"
 | |
| #include "Core/ActionReplay.h"
 | |
| #include "Core/ConfigManager.h"
 | |
| #include "Core/Core.h"
 | |
| #include "Core/PowerPC/PowerPC.h"
 | |
| 
 | |
| namespace ActionReplay
 | |
| {
 | |
| enum
 | |
| {
 | |
|   // Zero Code Types
 | |
|   ZCODE_END = 0x00,
 | |
|   ZCODE_NORM = 0x02,
 | |
|   ZCODE_ROW = 0x03,
 | |
|   ZCODE_04 = 0x04,
 | |
| 
 | |
|   // Conditional Codes
 | |
|   CONDTIONAL_EQUAL = 0x01,
 | |
|   CONDTIONAL_NOT_EQUAL = 0x02,
 | |
|   CONDTIONAL_LESS_THAN_SIGNED = 0x03,
 | |
|   CONDTIONAL_GREATER_THAN_SIGNED = 0x04,
 | |
|   CONDTIONAL_LESS_THAN_UNSIGNED = 0x05,
 | |
|   CONDTIONAL_GREATER_THAN_UNSIGNED = 0x06,
 | |
|   CONDTIONAL_AND = 0x07,  // bitwise AND
 | |
| 
 | |
|   // Conditional Line Counts
 | |
|   CONDTIONAL_ONE_LINE = 0x00,
 | |
|   CONDTIONAL_TWO_LINES = 0x01,
 | |
|   CONDTIONAL_ALL_LINES_UNTIL = 0x02,
 | |
|   CONDTIONAL_ALL_LINES = 0x03,
 | |
| 
 | |
|   // Data Types
 | |
|   DATATYPE_8BIT = 0x00,
 | |
|   DATATYPE_16BIT = 0x01,
 | |
|   DATATYPE_32BIT = 0x02,
 | |
|   DATATYPE_32BIT_FLOAT = 0x03,
 | |
| 
 | |
|   // Normal Code 0 Subtypes
 | |
|   SUB_RAM_WRITE = 0x00,
 | |
|   SUB_WRITE_POINTER = 0x01,
 | |
|   SUB_ADD_CODE = 0x02,
 | |
|   SUB_MASTER_CODE = 0x03,
 | |
| };
 | |
| 
 | |
| // General lock. Protects codes list and internal log.
 | |
| static std::mutex s_lock;
 | |
| static std::vector<ARCode> s_active_codes;
 | |
| static std::vector<std::string> s_internal_log;
 | |
| static std::atomic<bool> s_use_internal_log{false};
 | |
| // pointer to the code currently being run, (used by log messages that include the code name)
 | |
| static const ARCode* s_current_code = nullptr;
 | |
| static bool s_disable_logging = false;
 | |
| 
 | |
| struct ARAddr
 | |
| {
 | |
|   union
 | |
|   {
 | |
|     u32 address;
 | |
|     struct
 | |
|     {
 | |
|       u32 gcaddr : 25;
 | |
|       u32 size : 2;
 | |
|       u32 type : 3;
 | |
|       u32 subtype : 2;
 | |
|     };
 | |
|   };
 | |
| 
 | |
|   ARAddr(const u32 addr) : address(addr) {}
 | |
|   u32 GCAddress() const { return gcaddr | 0x80000000; }
 | |
|   operator u32() const { return address; }
 | |
| };
 | |
| 
 | |
| // ----------------------
 | |
| // AR Remote Functions
 | |
| void ApplyCodes(const std::vector<ARCode>& codes)
 | |
| {
 | |
|   if (!SConfig::GetInstance().bEnableCheats)
 | |
|     return;
 | |
| 
 | |
|   std::lock_guard<std::mutex> guard(s_lock);
 | |
|   s_disable_logging = false;
 | |
|   s_active_codes.clear();
 | |
|   std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_active_codes),
 | |
|                [](const ARCode& code) { return code.active; });
 | |
|   s_active_codes.shrink_to_fit();
 | |
| }
 | |
| 
 | |
| void AddCode(ARCode code)
 | |
| {
 | |
|   if (!SConfig::GetInstance().bEnableCheats)
 | |
|     return;
 | |
| 
 | |
|   if (code.active)
 | |
|   {
 | |
|     std::lock_guard<std::mutex> guard(s_lock);
 | |
|     s_disable_logging = false;
 | |
|     s_active_codes.emplace_back(std::move(code));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void LoadAndApplyCodes(const IniFile& global_ini, const IniFile& local_ini)
 | |
| {
 | |
|   ApplyCodes(LoadCodes(global_ini, local_ini));
 | |
| }
 | |
| 
 | |
| // Parses the Action Replay section of a game ini file.
 | |
| std::vector<ARCode> LoadCodes(const IniFile& global_ini, const IniFile& local_ini)
 | |
| {
 | |
|   std::vector<ARCode> codes;
 | |
| 
 | |
|   std::unordered_set<std::string> enabled_names;
 | |
|   {
 | |
|     std::vector<std::string> enabled_lines;
 | |
|     local_ini.GetLines("ActionReplay_Enabled", &enabled_lines);
 | |
|     for (const std::string& line : enabled_lines)
 | |
|     {
 | |
|       if (line.size() != 0 && line[0] == '$')
 | |
|       {
 | |
|         std::string name = line.substr(1, line.size() - 1);
 | |
|         enabled_names.insert(name);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const IniFile* inis[2] = {&global_ini, &local_ini};
 | |
|   for (const IniFile* ini : inis)
 | |
|   {
 | |
|     std::vector<std::string> lines;
 | |
|     std::vector<std::string> encrypted_lines;
 | |
|     ARCode current_code;
 | |
| 
 | |
|     ini->GetLines("ActionReplay", &lines);
 | |
| 
 | |
|     for (const std::string& line : lines)
 | |
|     {
 | |
|       if (line.empty())
 | |
|       {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       std::vector<std::string> pieces;
 | |
| 
 | |
|       // Check if the line is a name of the code
 | |
|       if (line[0] == '$')
 | |
|       {
 | |
|         if (current_code.ops.size())
 | |
|         {
 | |
|           codes.push_back(current_code);
 | |
|           current_code.ops.clear();
 | |
|         }
 | |
|         if (encrypted_lines.size())
 | |
|         {
 | |
|           DecryptARCode(encrypted_lines, ¤t_code.ops);
 | |
|           codes.push_back(current_code);
 | |
|           current_code.ops.clear();
 | |
|           encrypted_lines.clear();
 | |
|         }
 | |
| 
 | |
|         current_code.name = line.substr(1, line.size() - 1);
 | |
|         current_code.active = enabled_names.find(current_code.name) != enabled_names.end();
 | |
|         current_code.user_defined = (ini == &local_ini);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         SplitString(line, ' ', pieces);
 | |
| 
 | |
|         // Check if the AR code is decrypted
 | |
|         if (pieces.size() == 2 && pieces[0].size() == 8 && pieces[1].size() == 8)
 | |
|         {
 | |
|           AREntry op;
 | |
|           bool success_addr = TryParse(std::string("0x") + pieces[0], &op.cmd_addr);
 | |
|           bool success_val = TryParse(std::string("0x") + pieces[1], &op.value);
 | |
| 
 | |
|           if (success_addr && success_val)
 | |
|           {
 | |
|             current_code.ops.push_back(op);
 | |
|           }
 | |
|           else
 | |
|           {
 | |
|             PanicAlertT("Action Replay Error: invalid AR code line: %s", line.c_str());
 | |
| 
 | |
|             if (!success_addr)
 | |
|               PanicAlertT("The address is invalid");
 | |
| 
 | |
|             if (!success_val)
 | |
|               PanicAlertT("The value is invalid");
 | |
|           }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           SplitString(line, '-', pieces);
 | |
|           if (pieces.size() == 3 && pieces[0].size() == 4 && pieces[1].size() == 4 &&
 | |
|               pieces[2].size() == 5)
 | |
|           {
 | |
|             // Encrypted AR code
 | |
|             // Decryption is done in "blocks", so we must push blocks into a vector,
 | |
|             // then send to decrypt when a new block is encountered, or if it's the last block.
 | |
|             encrypted_lines.emplace_back(pieces[0] + pieces[1] + pieces[2]);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Handle the last code correctly.
 | |
|     if (current_code.ops.size())
 | |
|     {
 | |
|       codes.push_back(current_code);
 | |
|     }
 | |
|     if (encrypted_lines.size())
 | |
|     {
 | |
|       DecryptARCode(encrypted_lines, ¤t_code.ops);
 | |
|       codes.push_back(current_code);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return codes;
 | |
| }
 | |
| 
 | |
| void SaveCodes(IniFile* local_ini, const std::vector<ARCode>& codes)
 | |
| {
 | |
|   std::vector<std::string> lines;
 | |
|   std::vector<std::string> enabled_lines;
 | |
|   for (const ActionReplay::ARCode& code : codes)
 | |
|   {
 | |
|     if (code.active)
 | |
|       enabled_lines.emplace_back("$" + code.name);
 | |
| 
 | |
|     if (code.user_defined)
 | |
|     {
 | |
|       lines.emplace_back("$" + code.name);
 | |
|       for (const ActionReplay::AREntry& op : code.ops)
 | |
|       {
 | |
|         lines.emplace_back(StringFromFormat("%08X %08X", op.cmd_addr, op.value));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   local_ini->SetLines("ActionReplay_Enabled", enabled_lines);
 | |
|   local_ini->SetLines("ActionReplay", lines);
 | |
| }
 | |
| 
 | |
| static void LogInfo(const char* format, ...)
 | |
| {
 | |
|   if (s_disable_logging)
 | |
|     return;
 | |
|   bool use_internal_log = s_use_internal_log.load(std::memory_order_relaxed);
 | |
|   if (LogManager::GetMaxLevel() < LogTypes::LINFO && !use_internal_log)
 | |
|     return;
 | |
| 
 | |
|   va_list args;
 | |
|   va_start(args, format);
 | |
|   std::string text = StringFromFormatV(format, args);
 | |
|   va_end(args);
 | |
|   INFO_LOG(ACTIONREPLAY, "%s", text.c_str());
 | |
| 
 | |
|   if (use_internal_log)
 | |
|   {
 | |
|     text += '\n';
 | |
|     s_internal_log.emplace_back(std::move(text));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EnableSelfLogging(bool enable)
 | |
| {
 | |
|   s_use_internal_log.store(enable, std::memory_order_relaxed);
 | |
| }
 | |
| 
 | |
| std::vector<std::string> GetSelfLog()
 | |
| {
 | |
|   std::lock_guard<std::mutex> guard(s_lock);
 | |
|   return s_internal_log;
 | |
| }
 | |
| 
 | |
| void ClearSelfLog()
 | |
| {
 | |
|   std::lock_guard<std::mutex> guard(s_lock);
 | |
|   s_internal_log.clear();
 | |
| }
 | |
| 
 | |
| bool IsSelfLogging()
 | |
| {
 | |
|   return s_use_internal_log.load(std::memory_order_relaxed);
 | |
| }
 | |
| 
 | |
| // ----------------------
 | |
| // Code Functions
 | |
| static bool Subtype_RamWriteAndFill(const ARAddr& addr, const u32 data)
 | |
| {
 | |
|   const u32 new_addr = addr.GCAddress();
 | |
| 
 | |
|   LogInfo("Hardware Address: %08x", new_addr);
 | |
|   LogInfo("Size: %08x", addr.size);
 | |
| 
 | |
|   switch (addr.size)
 | |
|   {
 | |
|   case DATATYPE_8BIT:
 | |
|   {
 | |
|     LogInfo("8-bit Write");
 | |
|     LogInfo("--------");
 | |
|     u32 repeat = data >> 8;
 | |
|     for (u32 i = 0; i <= repeat; ++i)
 | |
|     {
 | |
|       PowerPC::HostWrite_U8(data & 0xFF, new_addr + i);
 | |
|       LogInfo("Wrote %08x to address %08x", data & 0xFF, new_addr + i);
 | |
|     }
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   case DATATYPE_16BIT:
 | |
|   {
 | |
|     LogInfo("16-bit Write");
 | |
|     LogInfo("--------");
 | |
|     u32 repeat = data >> 16;
 | |
|     for (u32 i = 0; i <= repeat; ++i)
 | |
|     {
 | |
|       PowerPC::HostWrite_U16(data & 0xFFFF, new_addr + i * 2);
 | |
|       LogInfo("Wrote %08x to address %08x", data & 0xFFFF, new_addr + i * 2);
 | |
|     }
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   case DATATYPE_32BIT_FLOAT:
 | |
|   case DATATYPE_32BIT:  // Dword write
 | |
|     LogInfo("32-bit Write");
 | |
|     LogInfo("--------");
 | |
|     PowerPC::HostWrite_U32(data, new_addr);
 | |
|     LogInfo("Wrote %08x to address %08x", data, new_addr);
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
| 
 | |
|   default:
 | |
|     LogInfo("Bad Size");
 | |
|     PanicAlertT("Action Replay Error: Invalid size "
 | |
|                 "(%08x : address = %08x) in Ram Write And Fill (%s)",
 | |
|                 addr.size, addr.gcaddr, s_current_code->name.c_str());
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static bool Subtype_WriteToPointer(const ARAddr& addr, const u32 data)
 | |
| {
 | |
|   const u32 new_addr = addr.GCAddress();
 | |
|   const u32 ptr = PowerPC::HostRead_U32(new_addr);
 | |
| 
 | |
|   LogInfo("Hardware Address: %08x", new_addr);
 | |
|   LogInfo("Size: %08x", addr.size);
 | |
| 
 | |
|   switch (addr.size)
 | |
|   {
 | |
|   case DATATYPE_8BIT:
 | |
|   {
 | |
|     LogInfo("Write 8-bit to pointer");
 | |
|     LogInfo("--------");
 | |
|     const u8 thebyte = data & 0xFF;
 | |
|     const u32 offset = data >> 8;
 | |
|     LogInfo("Pointer: %08x", ptr);
 | |
|     LogInfo("Byte: %08x", thebyte);
 | |
|     LogInfo("Offset: %08x", offset);
 | |
|     PowerPC::HostWrite_U8(thebyte, ptr + offset);
 | |
|     LogInfo("Wrote %08x to address %08x", thebyte, ptr + offset);
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   case DATATYPE_16BIT:
 | |
|   {
 | |
|     LogInfo("Write 16-bit to pointer");
 | |
|     LogInfo("--------");
 | |
|     const u16 theshort = data & 0xFFFF;
 | |
|     const u32 offset = (data >> 16) << 1;
 | |
|     LogInfo("Pointer: %08x", ptr);
 | |
|     LogInfo("Byte: %08x", theshort);
 | |
|     LogInfo("Offset: %08x", offset);
 | |
|     PowerPC::HostWrite_U16(theshort, ptr + offset);
 | |
|     LogInfo("Wrote %08x to address %08x", theshort, ptr + offset);
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   case DATATYPE_32BIT_FLOAT:
 | |
|   case DATATYPE_32BIT:
 | |
|     LogInfo("Write 32-bit to pointer");
 | |
|     LogInfo("--------");
 | |
|     PowerPC::HostWrite_U32(data, ptr);
 | |
|     LogInfo("Wrote %08x to address %08x", data, ptr);
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
| 
 | |
|   default:
 | |
|     LogInfo("Bad Size");
 | |
|     PanicAlertT("Action Replay Error: Invalid size "
 | |
|                 "(%08x : address = %08x) in Write To Pointer (%s)",
 | |
|                 addr.size, addr.gcaddr, s_current_code->name.c_str());
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static bool Subtype_AddCode(const ARAddr& addr, const u32 data)
 | |
| {
 | |
|   // Used to increment/decrement a value in memory
 | |
|   const u32 new_addr = addr.GCAddress();
 | |
| 
 | |
|   LogInfo("Hardware Address: %08x", new_addr);
 | |
|   LogInfo("Size: %08x", addr.size);
 | |
| 
 | |
|   switch (addr.size)
 | |
|   {
 | |
|   case DATATYPE_8BIT:
 | |
|     LogInfo("8-bit Add");
 | |
|     LogInfo("--------");
 | |
|     PowerPC::HostWrite_U8(PowerPC::HostRead_U8(new_addr) + data, new_addr);
 | |
|     LogInfo("Wrote %08x to address %08x", PowerPC::HostRead_U8(new_addr) + (data & 0xFF), new_addr);
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
| 
 | |
|   case DATATYPE_16BIT:
 | |
|     LogInfo("16-bit Add");
 | |
|     LogInfo("--------");
 | |
|     PowerPC::HostWrite_U16(PowerPC::HostRead_U16(new_addr) + data, new_addr);
 | |
|     LogInfo("Wrote %08x to address %08x", PowerPC::HostRead_U16(new_addr) + (data & 0xFFFF),
 | |
|             new_addr);
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
| 
 | |
|   case DATATYPE_32BIT:
 | |
|     LogInfo("32-bit Add");
 | |
|     LogInfo("--------");
 | |
|     PowerPC::HostWrite_U32(PowerPC::HostRead_U32(new_addr) + data, new_addr);
 | |
|     LogInfo("Wrote %08x to address %08x", PowerPC::HostRead_U32(new_addr) + data, new_addr);
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
| 
 | |
|   case DATATYPE_32BIT_FLOAT:
 | |
|   {
 | |
|     LogInfo("32-bit floating Add");
 | |
|     LogInfo("--------");
 | |
| 
 | |
|     const u32 read = PowerPC::HostRead_U32(new_addr);
 | |
|     const float read_float = reinterpret_cast<const float&>(read);
 | |
|     // data contains an (unsigned?) integer value
 | |
|     const float fread = read_float + static_cast<float>(data);
 | |
|     const u32 newval = reinterpret_cast<const u32&>(fread);
 | |
|     PowerPC::HostWrite_U32(newval, new_addr);
 | |
|     LogInfo("Old Value %08x", read);
 | |
|     LogInfo("Increment %08x", data);
 | |
|     LogInfo("New value %08x", newval);
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   default:
 | |
|     LogInfo("Bad Size");
 | |
|     PanicAlertT("Action Replay Error: Invalid size "
 | |
|                 "(%08x : address = %08x) in Add Code (%s)",
 | |
|                 addr.size, addr.gcaddr, s_current_code->name.c_str());
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static bool Subtype_MasterCodeAndWriteToCCXXXXXX(const ARAddr& addr, const u32 data)
 | |
| {
 | |
|   // code not yet implemented - TODO
 | |
|   // u32 new_addr = (addr & 0x01FFFFFF) | 0x80000000;
 | |
|   // u8  mcode_type = (data & 0xFF0000) >> 16;
 | |
|   // u8  mcode_count = (data & 0xFF00) >> 8;
 | |
|   // u8  mcode_number = data & 0xFF;
 | |
|   PanicAlertT("Action Replay Error: Master Code and Write To CCXXXXXX not implemented (%s)\n"
 | |
|               "Master codes are not needed. Do not use master codes.",
 | |
|               s_current_code->name.c_str());
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // This needs more testing
 | |
| static bool ZeroCode_FillAndSlide(const u32 val_last, const ARAddr& addr, const u32 data)
 | |
| {
 | |
|   const u32 new_addr = ARAddr(val_last).GCAddress();
 | |
|   const u8 size = ARAddr(val_last).size;
 | |
| 
 | |
|   const s16 addr_incr = static_cast<s16>(data & 0xFFFF);
 | |
|   const s8 val_incr = static_cast<s8>(data >> 24);
 | |
|   const u8 write_num = static_cast<u8>((data & 0xFF0000) >> 16);
 | |
| 
 | |
|   u32 val = addr;
 | |
|   u32 curr_addr = new_addr;
 | |
| 
 | |
|   LogInfo("Current Hardware Address: %08x", new_addr);
 | |
|   LogInfo("Size: %08x", addr.size);
 | |
|   LogInfo("Write Num: %08x", write_num);
 | |
|   LogInfo("Address Increment: %i", addr_incr);
 | |
|   LogInfo("Value Increment: %i", val_incr);
 | |
| 
 | |
|   switch (size)
 | |
|   {
 | |
|   case DATATYPE_8BIT:
 | |
|     LogInfo("8-bit Write");
 | |
|     LogInfo("--------");
 | |
|     for (int i = 0; i < write_num; ++i)
 | |
|     {
 | |
|       PowerPC::HostWrite_U8(val & 0xFF, curr_addr);
 | |
|       curr_addr += addr_incr;
 | |
|       val += val_incr;
 | |
|       LogInfo("Write %08x to address %08x", val & 0xFF, curr_addr);
 | |
| 
 | |
|       LogInfo("Value Update: %08x", val);
 | |
|       LogInfo("Current Hardware Address Update: %08x", curr_addr);
 | |
|     }
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
| 
 | |
|   case DATATYPE_16BIT:
 | |
|     LogInfo("16-bit Write");
 | |
|     LogInfo("--------");
 | |
|     for (int i = 0; i < write_num; ++i)
 | |
|     {
 | |
|       PowerPC::HostWrite_U16(val & 0xFFFF, curr_addr);
 | |
|       LogInfo("Write %08x to address %08x", val & 0xFFFF, curr_addr);
 | |
|       curr_addr += addr_incr * 2;
 | |
|       val += val_incr;
 | |
|       LogInfo("Value Update: %08x", val);
 | |
|       LogInfo("Current Hardware Address Update: %08x", curr_addr);
 | |
|     }
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
| 
 | |
|   case DATATYPE_32BIT:
 | |
|     LogInfo("32-bit Write");
 | |
|     LogInfo("--------");
 | |
|     for (int i = 0; i < write_num; ++i)
 | |
|     {
 | |
|       PowerPC::HostWrite_U32(val, curr_addr);
 | |
|       LogInfo("Write %08x to address %08x", val, curr_addr);
 | |
|       curr_addr += addr_incr * 4;
 | |
|       val += val_incr;
 | |
|       LogInfo("Value Update: %08x", val);
 | |
|       LogInfo("Current Hardware Address Update: %08x", curr_addr);
 | |
|     }
 | |
|     LogInfo("--------");
 | |
|     break;
 | |
| 
 | |
|   default:
 | |
|     LogInfo("Bad Size");
 | |
|     PanicAlertT("Action Replay Error: Invalid size (%08x : address = %08x) in Fill and Slide (%s)",
 | |
|                 size, new_addr, s_current_code->name.c_str());
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // Looks like this is new?? - untested
 | |
| static bool ZeroCode_MemoryCopy(const u32 val_last, const ARAddr& addr, const u32 data)
 | |
| {
 | |
|   const u32 addr_dest = val_last | 0x06000000;
 | |
|   const u32 addr_src = addr.GCAddress();
 | |
| 
 | |
|   const u8 num_bytes = data & 0x7FFF;
 | |
| 
 | |
|   LogInfo("Dest Address: %08x", addr_dest);
 | |
|   LogInfo("Src Address: %08x", addr_src);
 | |
|   LogInfo("Size: %08x", num_bytes);
 | |
| 
 | |
|   if ((data & ~0x7FFF) == 0x0000)
 | |
|   {
 | |
|     if ((data >> 24) != 0x0)
 | |
|     {  // Memory Copy With Pointers Support
 | |
|       LogInfo("Memory Copy With Pointers Support");
 | |
|       LogInfo("--------");
 | |
|       for (int i = 0; i < 138; ++i)
 | |
|       {
 | |
|         PowerPC::HostWrite_U8(PowerPC::HostRead_U8(addr_src + i), addr_dest + i);
 | |
|         LogInfo("Wrote %08x to address %08x", PowerPC::HostRead_U8(addr_src + i), addr_dest + i);
 | |
|       }
 | |
|       LogInfo("--------");
 | |
|     }
 | |
|     else
 | |
|     {  // Memory Copy Without Pointer Support
 | |
|       LogInfo("Memory Copy Without Pointers Support");
 | |
|       LogInfo("--------");
 | |
|       for (int i = 0; i < num_bytes; ++i)
 | |
|       {
 | |
|         PowerPC::HostWrite_U8(PowerPC::HostRead_U8(addr_src + i), addr_dest + i);
 | |
|         LogInfo("Wrote %08x to address %08x", PowerPC::HostRead_U8(addr_src + i), addr_dest + i);
 | |
|       }
 | |
|       LogInfo("--------");
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     LogInfo("Bad Value");
 | |
|     PanicAlertT("Action Replay Error: Invalid value (%08x) in Memory Copy (%s)", (data & ~0x7FFF),
 | |
|                 s_current_code->name.c_str());
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static bool NormalCode(const ARAddr& addr, const u32 data)
 | |
| {
 | |
|   switch (addr.subtype)
 | |
|   {
 | |
|   case SUB_RAM_WRITE:  // Ram write (and fill)
 | |
|     LogInfo("Doing Ram Write And Fill");
 | |
|     if (!Subtype_RamWriteAndFill(addr, data))
 | |
|       return false;
 | |
|     break;
 | |
| 
 | |
|   case SUB_WRITE_POINTER:  // Write to pointer
 | |
|     LogInfo("Doing Write To Pointer");
 | |
|     if (!Subtype_WriteToPointer(addr, data))
 | |
|       return false;
 | |
|     break;
 | |
| 
 | |
|   case SUB_ADD_CODE:  // Increment Value
 | |
|     LogInfo("Doing Add Code");
 | |
|     if (!Subtype_AddCode(addr, data))
 | |
|       return false;
 | |
|     break;
 | |
| 
 | |
|   case SUB_MASTER_CODE:  // Master Code & Write to CCXXXXXX
 | |
|     LogInfo("Doing Master Code And Write to CCXXXXXX (ncode not supported)");
 | |
|     if (!Subtype_MasterCodeAndWriteToCCXXXXXX(addr, data))
 | |
|       return false;
 | |
|     break;
 | |
| 
 | |
|   default:
 | |
|     LogInfo("Bad Subtype");
 | |
|     PanicAlertT("Action Replay: Normal Code 0: Invalid Subtype %08x (%s)", addr.subtype,
 | |
|                 s_current_code->name.c_str());
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static bool CompareValues(const u32 val1, const u32 val2, const int type)
 | |
| {
 | |
|   switch (type)
 | |
|   {
 | |
|   case CONDTIONAL_EQUAL:
 | |
|     LogInfo("Type 1: If Equal");
 | |
|     return val1 == val2;
 | |
| 
 | |
|   case CONDTIONAL_NOT_EQUAL:
 | |
|     LogInfo("Type 2: If Not Equal");
 | |
|     return val1 != val2;
 | |
| 
 | |
|   case CONDTIONAL_LESS_THAN_SIGNED:
 | |
|     LogInfo("Type 3: If Less Than (Signed)");
 | |
|     return static_cast<s32>(val1) < static_cast<s32>(val2);
 | |
| 
 | |
|   case CONDTIONAL_GREATER_THAN_SIGNED:
 | |
|     LogInfo("Type 4: If Greater Than (Signed)");
 | |
|     return static_cast<s32>(val1) > static_cast<s32>(val2);
 | |
| 
 | |
|   case CONDTIONAL_LESS_THAN_UNSIGNED:
 | |
|     LogInfo("Type 5: If Less Than (Unsigned)");
 | |
|     return val1 < val2;
 | |
| 
 | |
|   case CONDTIONAL_GREATER_THAN_UNSIGNED:
 | |
|     LogInfo("Type 6: If Greater Than (Unsigned)");
 | |
|     return val1 > val2;
 | |
| 
 | |
|   case CONDTIONAL_AND:
 | |
|     LogInfo("Type 7: If And");
 | |
|     return !!(val1 & val2);  // bitwise AND
 | |
| 
 | |
|   default:
 | |
|     LogInfo("Unknown Compare type");
 | |
|     PanicAlertT("Action Replay: Invalid Normal Code Type %08x (%s)", type,
 | |
|                 s_current_code->name.c_str());
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool ConditionalCode(const ARAddr& addr, const u32 data, int* const pSkipCount)
 | |
| {
 | |
|   const u32 new_addr = addr.GCAddress();
 | |
| 
 | |
|   LogInfo("Size: %08x", addr.size);
 | |
|   LogInfo("Hardware Address: %08x", new_addr);
 | |
| 
 | |
|   bool result = true;
 | |
| 
 | |
|   switch (addr.size)
 | |
|   {
 | |
|   case DATATYPE_8BIT:
 | |
|     result = CompareValues(PowerPC::HostRead_U8(new_addr), (data & 0xFF), addr.type);
 | |
|     break;
 | |
| 
 | |
|   case DATATYPE_16BIT:
 | |
|     result = CompareValues(PowerPC::HostRead_U16(new_addr), (data & 0xFFFF), addr.type);
 | |
|     break;
 | |
| 
 | |
|   case DATATYPE_32BIT_FLOAT:
 | |
|   case DATATYPE_32BIT:
 | |
|     result = CompareValues(PowerPC::HostRead_U32(new_addr), data, addr.type);
 | |
|     break;
 | |
| 
 | |
|   default:
 | |
|     LogInfo("Bad Size");
 | |
|     PanicAlertT("Action Replay: Conditional Code: Invalid Size %08x (%s)", addr.size,
 | |
|                 s_current_code->name.c_str());
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // if the comparison failed we need to skip some lines
 | |
|   if (false == result)
 | |
|   {
 | |
|     switch (addr.subtype)
 | |
|     {
 | |
|     case CONDTIONAL_ONE_LINE:
 | |
|     case CONDTIONAL_TWO_LINES:
 | |
|       *pSkipCount = addr.subtype + 1;  // Skip 1 or 2 lines
 | |
|       break;
 | |
| 
 | |
|     // Skip all lines,
 | |
|     // Skip lines until a "00000000 40000000" line is reached
 | |
|     case CONDTIONAL_ALL_LINES:
 | |
|     case CONDTIONAL_ALL_LINES_UNTIL:
 | |
|       *pSkipCount = -static_cast<int>(addr.subtype);
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       LogInfo("Bad Subtype");
 | |
|       PanicAlertT("Action Replay: Normal Code %i: Invalid subtype %08x (%s)", 1, addr.subtype,
 | |
|                   s_current_code->name.c_str());
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // NOTE: Lock needed to give mutual exclusion to s_current_code and LogInfo
 | |
| static bool RunCodeLocked(const ARCode& arcode)
 | |
| {
 | |
|   // The mechanism is different than what the real AR uses, so there may be compatibility problems.
 | |
| 
 | |
|   bool do_fill_and_slide = false;
 | |
|   bool do_memory_copy = false;
 | |
| 
 | |
|   // used for conditional codes
 | |
|   int skip_count = 0;
 | |
| 
 | |
|   u32 val_last = 0;
 | |
| 
 | |
|   s_current_code = &arcode;
 | |
| 
 | |
|   LogInfo("Code Name: %s", arcode.name.c_str());
 | |
|   LogInfo("Number of codes: %zu", arcode.ops.size());
 | |
| 
 | |
|   for (const AREntry& entry : arcode.ops)
 | |
|   {
 | |
|     const ARAddr addr(entry.cmd_addr);
 | |
|     const u32 data = entry.value;
 | |
| 
 | |
|     // after a conditional code, skip lines if needed
 | |
|     if (skip_count)
 | |
|     {
 | |
|       if (skip_count > 0)  // skip x lines
 | |
|       {
 | |
|         LogInfo("Line skipped");
 | |
|         --skip_count;
 | |
|       }
 | |
|       else if (-CONDTIONAL_ALL_LINES == skip_count)
 | |
|       {
 | |
|         // skip all lines
 | |
|         LogInfo("All Lines skipped");
 | |
|         return true;  // don't need to iterate through the rest of the ops
 | |
|       }
 | |
|       else if (-CONDTIONAL_ALL_LINES_UNTIL == skip_count)
 | |
|       {
 | |
|         // skip until a "00000000 40000000" line is reached
 | |
|         LogInfo("Line skipped");
 | |
|         if (addr == 0 && 0x40000000 == data)  // check for an endif line
 | |
|           skip_count = 0;
 | |
|       }
 | |
| 
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     LogInfo("--- Running Code: %08x %08x ---", addr.address, data);
 | |
|     // LogInfo("Command: %08x", cmd);
 | |
| 
 | |
|     // Do Fill & Slide
 | |
|     if (do_fill_and_slide)
 | |
|     {
 | |
|       do_fill_and_slide = false;
 | |
|       LogInfo("Doing Fill And Slide");
 | |
|       if (false == ZeroCode_FillAndSlide(val_last, addr, data))
 | |
|         return false;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // Memory Copy
 | |
|     if (do_memory_copy)
 | |
|     {
 | |
|       do_memory_copy = false;
 | |
|       LogInfo("Doing Memory Copy");
 | |
|       if (false == ZeroCode_MemoryCopy(val_last, addr, data))
 | |
|         return false;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // ActionReplay program self modification codes
 | |
|     if (addr >= 0x00002000 && addr < 0x00003000)
 | |
|     {
 | |
|       LogInfo(
 | |
|           "This action replay simulator does not support codes that modify Action Replay itself.");
 | |
|       PanicAlertT(
 | |
|           "This action replay simulator does not support codes that modify Action Replay itself.");
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // skip these weird init lines
 | |
|     // TODO: Where are the "weird init lines"?
 | |
|     // if (iter == code.ops.begin() && cmd == 1)
 | |
|     // continue;
 | |
| 
 | |
|     // Zero codes
 | |
|     if (0x0 == addr)  // Check if the code is a zero code
 | |
|     {
 | |
|       const u8 zcode = data >> 29;
 | |
| 
 | |
|       LogInfo("Doing Zero Code %08x", zcode);
 | |
| 
 | |
|       switch (zcode)
 | |
|       {
 | |
|       case ZCODE_END:  // END OF CODES
 | |
|         LogInfo("ZCode: End Of Codes");
 | |
|         return true;
 | |
| 
 | |
|       // TODO: the "00000000 40000000"(end if) codes fall into this case, I don't think that is
 | |
|       // correct
 | |
|       case ZCODE_NORM:  // Normal execution of codes
 | |
|         // Todo: Set register 1BB4 to 0
 | |
|         LogInfo("ZCode: Normal execution of codes, set register 1BB4 to 0 (zcode not supported)");
 | |
|         break;
 | |
| 
 | |
|       case ZCODE_ROW:  // Executes all codes in the same row
 | |
|         // Todo: Set register 1BB4 to 1
 | |
|         LogInfo("ZCode: Executes all codes in the same row, Set register 1BB4 to 1 (zcode not "
 | |
|                 "supported)");
 | |
|         PanicAlertT("Zero 3 code not supported");
 | |
|         return false;
 | |
| 
 | |
|       case ZCODE_04:  // Fill & Slide or Memory Copy
 | |
|         if (0x3 == ((data >> 25) & 0x03))
 | |
|         {
 | |
|           LogInfo("ZCode: Memory Copy");
 | |
|           do_memory_copy = true;
 | |
|           val_last = data;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           LogInfo("ZCode: Fill And Slide");
 | |
|           do_fill_and_slide = true;
 | |
|           val_last = data;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       default:
 | |
|         LogInfo("ZCode: Unknown");
 | |
|         PanicAlertT("Zero code unknown to Dolphin: %08x", zcode);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       // done handling zero codes
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // Normal codes
 | |
|     LogInfo("Doing Normal Code %08x", addr.type);
 | |
|     LogInfo("Subtype: %08x", addr.subtype);
 | |
| 
 | |
|     switch (addr.type)
 | |
|     {
 | |
|     case 0x00:
 | |
|       if (false == NormalCode(addr, data))
 | |
|         return false;
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       LogInfo("This Normal Code is a Conditional Code");
 | |
|       if (false == ConditionalCode(addr, data, &skip_count))
 | |
|         return false;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void RunAllActive()
 | |
| {
 | |
|   if (!SConfig::GetInstance().bEnableCheats)
 | |
|     return;
 | |
| 
 | |
|   // If the mutex is idle then acquiring it should be cheap, fast mutexes
 | |
|   // are only atomic ops unless contested. It should be rare for this to
 | |
|   // be contested.
 | |
|   std::lock_guard<std::mutex> guard(s_lock);
 | |
|   s_active_codes.erase(std::remove_if(s_active_codes.begin(), s_active_codes.end(),
 | |
|                                       [](const ARCode& code) {
 | |
|                                         bool success = RunCodeLocked(code);
 | |
|                                         LogInfo("\n");
 | |
|                                         return !success;
 | |
|                                       }),
 | |
|                        s_active_codes.end());
 | |
|   s_disable_logging = true;
 | |
| }
 | |
| 
 | |
| }  // namespace ActionReplay
 |