2020-09-09 12:11:28 +00:00
|
|
|
#include "cheats.h"
|
2020-10-21 11:43:51 +00:00
|
|
|
#include "bus.h"
|
2020-10-19 15:14:49 +00:00
|
|
|
#include "common/assert.h"
|
2020-11-29 06:42:49 +00:00
|
|
|
#include "common/byte_stream.h"
|
2020-09-09 12:11:28 +00:00
|
|
|
#include "common/file_system.h"
|
|
|
|
#include "common/log.h"
|
|
|
|
#include "common/string.h"
|
|
|
|
#include "common/string_util.h"
|
2020-12-06 05:47:00 +00:00
|
|
|
#include "controller.h"
|
2020-10-22 01:09:11 +00:00
|
|
|
#include "cpu_code_cache.h"
|
2020-09-09 12:11:28 +00:00
|
|
|
#include "cpu_core.h"
|
2020-10-19 15:14:49 +00:00
|
|
|
#include "host_interface.h"
|
2020-12-06 05:47:00 +00:00
|
|
|
#include "system.h"
|
2020-09-09 12:11:28 +00:00
|
|
|
#include <cctype>
|
2020-10-19 15:14:49 +00:00
|
|
|
#include <iomanip>
|
|
|
|
#include <sstream>
|
2020-09-09 12:11:28 +00:00
|
|
|
Log_SetChannel(Cheats);
|
Added new cheat cheat types: C3, C4, C5, C6, D7&52
C3-C7 are variants of C0
D7 is a BIT based joker to rule them all. It includes the analog sticks (@ggrtk thanks for adding the analog reading capability). Also added the facility of making dual single key joker by varying the amount of time a button is held down.
51 is a complicated beast that I still need to document
* C3XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is less than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C4XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is greater than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C5XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is less than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C6XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is greater than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* D7PQRRRR TTYYYYYY - 24-Bit Universal BIT Joker, OR the hex values to
combine into a multi-button joker. Because it is BIT
based it is better than D4, D5, D6 or using a D0 joker as you
do not need to worry about any other buttons being
pressed at the same time and you get both analog
sticks for extra functionality. Note if you want to use it
just as a enhanced joker just use D7000000 00YYYYYY when
the buttons/directions are pressed or D7100000 00YYYYYY
when you want to ensure they are not all pressed.
QRRRR TT provides the capability of only activating the
following codes after the keys have been held in for a set
amount of frames. 003C = 60 Frames = 1 Second at 100% Speed
YYYYYY = 000001 L2 Button
YYYYYY = 000002 R2 Button
YYYYYY = 000004 L1 Button
YYYYYY = 000008 R1 Button
YYYYYY = 000010 Triangle Button
YYYYYY = 000020 Circle Button
YYYYYY = 000040 X Button
YYYYYY = 000080 Square Button
YYYYYY = 000100 Select Button
YYYYYY = 000200 L3 Button
YYYYYY = 000400 R3 Button
YYYYYY = 000800 Start Button
YYYYYY = 001000 Up (Digital)
YYYYYY = 002000 Right (Digital)
YYYYYY = 004000 Down (Digital)
YYYYYY = 008000 Left (Digital)
YYYYYY = 010000 Up (Right Thumb)
YYYYYY = 020000 Right (Right Thumb)
YYYYYY = 040000 Down (Right Thumb)
YYYYYY = 080000 Left (Right Thumb)
YYYYYY = 100000 Up (Left Thumb)
YYYYYY = 200000 Right (Left Thumb)
YYYYYY = 400000 Down (Left Thumb)
YYYYYY = 800000 Left (Left Thumb)
NOTE: The 0s in the code are reserved for possible
future use.
TT=Temp Internal Variable 00-FF, 00 will mean it wont be
used, if it's not 00 do not use the same value for
jokers using different keypress combinations for
the same game.
P = 0 or 1. 0 = Check ALL YYYYYY Bits are ON
1 = Check ALL YYYYYY Bits are OFF
Q = Frame Comparison 0 = Dont do any comparison
1 = Check that the button combination
has been held down for exactly
RRRR frames.
2 = Check that the button combination
has been held down for more than
RRRR frames.
3 = Check that the button combination
has been held down for less than
RRRR frames.
4 = Check that the button combination
has been held down for anything
but RRRR frames.
RRRR = 0000 to FFFF, Frame Comparison Value
It will then poke all following codes for rest of cheat
00000000 FFFF or until it reaches the 00000000 FFFF line.
2021-03-03 23:06:08 +00:00
|
|
|
static std::array<u32, 256> temp_variable; //Used for D7 ,51 & 52 cheat types
|
2020-09-09 12:11:28 +00:00
|
|
|
|
|
|
|
using KeyValuePairVector = std::vector<std::pair<std::string, std::string>>;
|
|
|
|
|
2020-10-21 12:01:50 +00:00
|
|
|
static bool IsValidScanAddress(PhysicalMemoryAddress address)
|
|
|
|
{
|
|
|
|
if ((address & CPU::DCACHE_LOCATION_MASK) == CPU::DCACHE_LOCATION &&
|
|
|
|
(address & CPU::DCACHE_OFFSET_MASK) < CPU::DCACHE_SIZE)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
address &= CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
|
|
|
|
|
|
|
|
if (address < Bus::RAM_MIRROR_END)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (address >= Bus::BIOS_BASE && address < (Bus::BIOS_BASE + Bus::BIOS_SIZE))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
static T DoMemoryRead(PhysicalMemoryAddress address)
|
|
|
|
{
|
|
|
|
T result;
|
|
|
|
|
|
|
|
if ((address & CPU::DCACHE_LOCATION_MASK) == CPU::DCACHE_LOCATION &&
|
|
|
|
(address & CPU::DCACHE_OFFSET_MASK) < CPU::DCACHE_SIZE)
|
|
|
|
{
|
|
|
|
std::memcpy(&result, &CPU::g_state.dcache[address & CPU::DCACHE_OFFSET_MASK], sizeof(result));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
address &= CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
|
|
|
|
|
|
|
|
if (address < Bus::RAM_MIRROR_END)
|
|
|
|
{
|
|
|
|
std::memcpy(&result, &Bus::g_ram[address & Bus::RAM_MASK], sizeof(result));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (address >= Bus::BIOS_BASE && address < (Bus::BIOS_BASE + Bus::BIOS_SIZE))
|
|
|
|
{
|
|
|
|
std::memcpy(&result, &Bus::g_bios[address & Bus::BIOS_MASK], sizeof(result));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = static_cast<T>(0);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
static void DoMemoryWrite(PhysicalMemoryAddress address, T value)
|
|
|
|
{
|
|
|
|
if ((address & CPU::DCACHE_LOCATION_MASK) == CPU::DCACHE_LOCATION &&
|
|
|
|
(address & CPU::DCACHE_OFFSET_MASK) < CPU::DCACHE_SIZE)
|
|
|
|
{
|
|
|
|
std::memcpy(&CPU::g_state.dcache[address & CPU::DCACHE_OFFSET_MASK], &value, sizeof(value));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
address &= CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
|
|
|
|
|
|
|
|
if (address < Bus::RAM_MIRROR_END)
|
|
|
|
{
|
2020-10-22 01:09:11 +00:00
|
|
|
// Only invalidate code when it changes.
|
|
|
|
T old_value;
|
|
|
|
std::memcpy(&old_value, &Bus::g_ram[address & Bus::RAM_MASK], sizeof(old_value));
|
|
|
|
if (old_value != value)
|
|
|
|
{
|
|
|
|
std::memcpy(&Bus::g_ram[address & Bus::RAM_MASK], &value, sizeof(value));
|
|
|
|
|
|
|
|
const u32 code_page_index = Bus::GetRAMCodePageIndex(address & Bus::RAM_MASK);
|
|
|
|
if (Bus::IsRAMCodePage(code_page_index))
|
|
|
|
CPU::CodeCache::InvalidateBlocksWithPageIndex(code_page_index);
|
|
|
|
}
|
|
|
|
|
2020-10-21 12:01:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-06 05:47:00 +00:00
|
|
|
static u32 GetControllerButtonBits()
|
|
|
|
{
|
|
|
|
static constexpr std::array<u16, 16> button_mapping = {{
|
|
|
|
0x0100, // Select
|
|
|
|
0x0200, // L3
|
|
|
|
0x0400, // R3
|
|
|
|
0x0800, // Start
|
|
|
|
0x1000, // Up
|
|
|
|
0x2000, // Right
|
|
|
|
0x4000, // Down
|
|
|
|
0x8000, // Left
|
|
|
|
0x0001, // L2
|
|
|
|
0x0002, // R2
|
|
|
|
0x0004, // L1
|
|
|
|
0x0008, // R1
|
|
|
|
0x0010, // Triangle
|
|
|
|
0x0020, // Circle
|
|
|
|
0x0040, // Cross
|
|
|
|
0x0080, // Square
|
|
|
|
}};
|
|
|
|
|
|
|
|
u32 bits = 0;
|
|
|
|
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
|
|
|
{
|
|
|
|
Controller* controller = System::GetController(i);
|
|
|
|
if (!controller)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
bits |= controller->GetButtonStateBits();
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 translated_bits = 0;
|
|
|
|
for (u32 i = 0, bit = 1; i < static_cast<u32>(button_mapping.size()); i++, bit <<= 1)
|
|
|
|
{
|
|
|
|
if (bits & bit)
|
|
|
|
translated_bits |= button_mapping[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return translated_bits;
|
|
|
|
}
|
|
|
|
|
Added new cheat cheat types: C3, C4, C5, C6, D7&52
C3-C7 are variants of C0
D7 is a BIT based joker to rule them all. It includes the analog sticks (@ggrtk thanks for adding the analog reading capability). Also added the facility of making dual single key joker by varying the amount of time a button is held down.
51 is a complicated beast that I still need to document
* C3XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is less than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C4XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is greater than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C5XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is less than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C6XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is greater than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* D7PQRRRR TTYYYYYY - 24-Bit Universal BIT Joker, OR the hex values to
combine into a multi-button joker. Because it is BIT
based it is better than D4, D5, D6 or using a D0 joker as you
do not need to worry about any other buttons being
pressed at the same time and you get both analog
sticks for extra functionality. Note if you want to use it
just as a enhanced joker just use D7000000 00YYYYYY when
the buttons/directions are pressed or D7100000 00YYYYYY
when you want to ensure they are not all pressed.
QRRRR TT provides the capability of only activating the
following codes after the keys have been held in for a set
amount of frames. 003C = 60 Frames = 1 Second at 100% Speed
YYYYYY = 000001 L2 Button
YYYYYY = 000002 R2 Button
YYYYYY = 000004 L1 Button
YYYYYY = 000008 R1 Button
YYYYYY = 000010 Triangle Button
YYYYYY = 000020 Circle Button
YYYYYY = 000040 X Button
YYYYYY = 000080 Square Button
YYYYYY = 000100 Select Button
YYYYYY = 000200 L3 Button
YYYYYY = 000400 R3 Button
YYYYYY = 000800 Start Button
YYYYYY = 001000 Up (Digital)
YYYYYY = 002000 Right (Digital)
YYYYYY = 004000 Down (Digital)
YYYYYY = 008000 Left (Digital)
YYYYYY = 010000 Up (Right Thumb)
YYYYYY = 020000 Right (Right Thumb)
YYYYYY = 040000 Down (Right Thumb)
YYYYYY = 080000 Left (Right Thumb)
YYYYYY = 100000 Up (Left Thumb)
YYYYYY = 200000 Right (Left Thumb)
YYYYYY = 400000 Down (Left Thumb)
YYYYYY = 800000 Left (Left Thumb)
NOTE: The 0s in the code are reserved for possible
future use.
TT=Temp Internal Variable 00-FF, 00 will mean it wont be
used, if it's not 00 do not use the same value for
jokers using different keypress combinations for
the same game.
P = 0 or 1. 0 = Check ALL YYYYYY Bits are ON
1 = Check ALL YYYYYY Bits are OFF
Q = Frame Comparison 0 = Dont do any comparison
1 = Check that the button combination
has been held down for exactly
RRRR frames.
2 = Check that the button combination
has been held down for more than
RRRR frames.
3 = Check that the button combination
has been held down for less than
RRRR frames.
4 = Check that the button combination
has been held down for anything
but RRRR frames.
RRRR = 0000 to FFFF, Frame Comparison Value
It will then poke all following codes for rest of cheat
00000000 FFFF or until it reaches the 00000000 FFFF line.
2021-03-03 23:06:08 +00:00
|
|
|
static u32 GetControllerAnalogBits()
|
|
|
|
{
|
|
|
|
// 0x010000 - Right Thumb Up
|
|
|
|
// 0x020000 - Right Thumb Right
|
|
|
|
// 0x040000 - Right Thumb Down
|
|
|
|
// 0x080000 - Right Thumb Left
|
|
|
|
// 0x100000 - Left Thumb Up
|
|
|
|
// 0x200000 - Left Thumb Right
|
|
|
|
// 0x400000 - Left Thumb Down
|
|
|
|
// 0x800000 - Left Thumb Left
|
|
|
|
|
|
|
|
u32 bits = 0;
|
|
|
|
u8 l_ypos = 0;
|
|
|
|
u8 l_xpos = 0;
|
|
|
|
u8 r_ypos = 0;
|
|
|
|
u8 r_xpos = 0;
|
|
|
|
|
|
|
|
std::optional<u32> analog = 0;
|
|
|
|
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
|
|
|
{
|
|
|
|
Controller* controller = System::GetController(i);
|
|
|
|
if (!controller)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
analog = controller->GetAnalogInputBytes();
|
|
|
|
if (analog.has_value())
|
|
|
|
{
|
|
|
|
l_ypos = Truncate8((analog.value() & 0xFF000000u) >> 24);
|
|
|
|
l_xpos = Truncate8((analog.value() & 0x00FF0000u) >> 16);
|
|
|
|
r_ypos = Truncate8((analog.value() & 0x0000FF00u) >> 8);
|
|
|
|
r_xpos = Truncate8(analog.value() & 0x000000FFu);
|
|
|
|
if (l_ypos < 0x50)
|
|
|
|
bits |= 0x100000;
|
|
|
|
else if (l_ypos > 0xA0)
|
|
|
|
bits |= 0x400000;
|
|
|
|
if (l_xpos < 0x50)
|
|
|
|
bits |= 0x800000;
|
|
|
|
else if (l_xpos > 0xA0)
|
|
|
|
bits |= 0x200000;
|
|
|
|
if (r_ypos < 0x50)
|
|
|
|
bits |= 0x10000;
|
|
|
|
else if (r_ypos > 0xA0)
|
|
|
|
bits |= 0x40000;
|
|
|
|
if (r_xpos < 0x50)
|
|
|
|
bits |= 0x80000;
|
|
|
|
else if (r_xpos > 0xA0)
|
|
|
|
bits |= 0x20000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return bits;
|
|
|
|
}
|
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
CheatList::CheatList() = default;
|
|
|
|
|
|
|
|
CheatList::~CheatList() = default;
|
|
|
|
|
|
|
|
static bool IsHexCharacter(char c)
|
|
|
|
{
|
|
|
|
return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
|
|
|
|
}
|
|
|
|
|
2020-12-26 02:58:18 +00:00
|
|
|
static int SignedCharToInt(char ch)
|
|
|
|
{
|
|
|
|
return static_cast<int>(static_cast<unsigned char>(ch));
|
|
|
|
}
|
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
static const std::string* FindKey(const KeyValuePairVector& kvp, const char* search)
|
|
|
|
{
|
|
|
|
for (const auto& it : kvp)
|
|
|
|
{
|
|
|
|
if (StringUtil::Strcasecmp(it.first.c_str(), search) == 0)
|
|
|
|
return &it.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatList::LoadFromPCSXRFile(const char* filename)
|
|
|
|
{
|
2020-11-07 10:36:30 +00:00
|
|
|
std::optional<std::string> str = FileSystem::ReadFileToString(filename);
|
|
|
|
if (!str.has_value() || str->empty())
|
2020-09-09 12:11:28 +00:00
|
|
|
return false;
|
|
|
|
|
2020-11-07 10:36:30 +00:00
|
|
|
return LoadFromPCSXRString(str.value());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatList::LoadFromPCSXRString(const std::string& str)
|
|
|
|
{
|
|
|
|
std::istringstream iss(str);
|
|
|
|
|
|
|
|
std::string line;
|
2020-10-21 10:41:44 +00:00
|
|
|
std::string comments;
|
2020-10-21 10:51:36 +00:00
|
|
|
std::string group;
|
|
|
|
CheatCode::Type type = CheatCode::Type::Gameshark;
|
|
|
|
CheatCode::Activation activation = CheatCode::Activation::EndFrame;
|
2020-09-09 12:11:28 +00:00
|
|
|
CheatCode current_code;
|
2020-11-07 10:36:30 +00:00
|
|
|
while (std::getline(iss, line))
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
2020-11-07 10:36:30 +00:00
|
|
|
char* start = line.data();
|
2020-12-26 02:58:18 +00:00
|
|
|
while (*start != '\0' && std::isspace(SignedCharToInt(*start)))
|
2020-09-09 12:11:28 +00:00
|
|
|
start++;
|
|
|
|
|
|
|
|
// skip empty lines
|
|
|
|
if (*start == '\0')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end = start + std::strlen(start) - 1;
|
2020-12-26 02:58:18 +00:00
|
|
|
while (end > start && std::isspace(SignedCharToInt(*end)))
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
|
|
|
*end = '\0';
|
|
|
|
end--;
|
|
|
|
}
|
|
|
|
|
2020-10-21 10:51:36 +00:00
|
|
|
// DuckStation metadata
|
|
|
|
if (StringUtil::Strncasecmp(start, "#group=", 7) == 0)
|
|
|
|
{
|
|
|
|
group = start + 7;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (StringUtil::Strncasecmp(start, "#type=", 6) == 0)
|
|
|
|
{
|
|
|
|
type = CheatCode::ParseTypeName(start + 6).value_or(CheatCode::Type::Gameshark);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (StringUtil::Strncasecmp(start, "#activation=", 12) == 0)
|
|
|
|
{
|
|
|
|
activation = CheatCode::ParseActivationName(start + 12).value_or(CheatCode::Activation::EndFrame);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
// skip comments and empty line
|
|
|
|
if (*start == '#' || *start == ';' || *start == '/' || *start == '\"')
|
2020-10-21 10:41:44 +00:00
|
|
|
{
|
|
|
|
comments.append(start);
|
|
|
|
comments += '\n';
|
2020-09-09 12:11:28 +00:00
|
|
|
continue;
|
2020-10-21 10:41:44 +00:00
|
|
|
}
|
2020-09-09 12:11:28 +00:00
|
|
|
|
|
|
|
if (*start == '[' && *end == ']')
|
|
|
|
{
|
|
|
|
start++;
|
|
|
|
*end = '\0';
|
|
|
|
|
|
|
|
// new cheat
|
|
|
|
if (current_code.Valid())
|
|
|
|
m_codes.push_back(std::move(current_code));
|
|
|
|
|
2020-10-19 15:14:49 +00:00
|
|
|
current_code = CheatCode();
|
2020-10-21 10:51:36 +00:00
|
|
|
if (group.empty())
|
|
|
|
group = "Ungrouped";
|
|
|
|
|
|
|
|
current_code.group = std::move(group);
|
|
|
|
group = std::string();
|
2020-10-21 10:41:44 +00:00
|
|
|
current_code.comments = std::move(comments);
|
|
|
|
comments = std::string();
|
2020-10-21 10:51:36 +00:00
|
|
|
current_code.type = type;
|
|
|
|
type = CheatCode::Type::Gameshark;
|
|
|
|
current_code.activation = activation;
|
|
|
|
activation = CheatCode::Activation::EndFrame;
|
2020-10-19 15:14:49 +00:00
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
if (*start == '*')
|
|
|
|
{
|
|
|
|
current_code.enabled = true;
|
|
|
|
start++;
|
|
|
|
}
|
|
|
|
|
|
|
|
current_code.description.append(start);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!IsHexCharacter(*start) && start != end)
|
|
|
|
start++;
|
|
|
|
if (start == end)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end_ptr;
|
|
|
|
CheatCode::Instruction inst;
|
|
|
|
inst.first = static_cast<u32>(std::strtoul(start, &end_ptr, 16));
|
|
|
|
inst.second = 0;
|
|
|
|
if (end_ptr)
|
|
|
|
{
|
|
|
|
while (!IsHexCharacter(*end_ptr) && end_ptr != end)
|
|
|
|
end_ptr++;
|
|
|
|
if (end_ptr != end)
|
|
|
|
inst.second = static_cast<u32>(std::strtoul(end_ptr, nullptr, 16));
|
|
|
|
}
|
|
|
|
current_code.instructions.push_back(inst);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current_code.Valid())
|
2020-10-21 10:41:44 +00:00
|
|
|
{
|
2020-10-21 10:51:36 +00:00
|
|
|
// technically this isn't the place for end of file
|
2020-10-21 10:41:44 +00:00
|
|
|
if (!comments.empty())
|
|
|
|
current_code.comments += comments;
|
2020-09-09 12:11:28 +00:00
|
|
|
m_codes.push_back(std::move(current_code));
|
2020-10-21 10:41:44 +00:00
|
|
|
}
|
2020-09-09 12:11:28 +00:00
|
|
|
|
2020-11-07 10:36:30 +00:00
|
|
|
Log_InfoPrintf("Loaded %zu cheats (PCSXR format)", m_codes.size());
|
2020-09-09 12:11:28 +00:00
|
|
|
return !m_codes.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatList::LoadFromLibretroFile(const char* filename)
|
|
|
|
{
|
2020-11-07 10:36:30 +00:00
|
|
|
std::optional<std::string> str = FileSystem::ReadFileToString(filename);
|
|
|
|
if (!str.has_value() || str->empty())
|
2020-09-09 12:11:28 +00:00
|
|
|
return false;
|
|
|
|
|
2020-11-07 10:36:30 +00:00
|
|
|
return LoadFromLibretroString(str.value());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatList::LoadFromLibretroString(const std::string& str)
|
|
|
|
{
|
|
|
|
std::istringstream iss(str);
|
|
|
|
std::string line;
|
2020-09-09 12:11:28 +00:00
|
|
|
KeyValuePairVector kvp;
|
2020-11-07 10:36:30 +00:00
|
|
|
while (std::getline(iss, line))
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
2020-11-07 10:36:30 +00:00
|
|
|
char* start = line.data();
|
2020-12-26 02:58:18 +00:00
|
|
|
while (*start != '\0' && std::isspace(SignedCharToInt(*start)))
|
2020-09-09 12:11:28 +00:00
|
|
|
start++;
|
|
|
|
|
|
|
|
// skip empty lines
|
|
|
|
if (*start == '\0' || *start == '=')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end = start + std::strlen(start) - 1;
|
2020-12-26 02:58:18 +00:00
|
|
|
while (end > start && std::isspace(SignedCharToInt(*end)))
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
|
|
|
*end = '\0';
|
|
|
|
end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
char* equals = start;
|
|
|
|
while (*equals != '=' && equals != end)
|
|
|
|
equals++;
|
|
|
|
if (equals == end)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
*equals = '\0';
|
|
|
|
|
|
|
|
char* key_end = equals - 1;
|
2020-12-26 02:58:18 +00:00
|
|
|
while (key_end > start && std::isspace(SignedCharToInt(*key_end)))
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
|
|
|
*key_end = '\0';
|
|
|
|
key_end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
char* value_start = equals + 1;
|
2020-12-26 02:58:18 +00:00
|
|
|
while (*value_start != '\0' && std::isspace(SignedCharToInt(*value_start)))
|
2020-09-09 12:11:28 +00:00
|
|
|
value_start++;
|
|
|
|
|
2020-09-25 12:00:34 +00:00
|
|
|
if (*value_start == '\0')
|
2020-09-09 12:11:28 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
char* value_end = value_start + std::strlen(value_start) - 1;
|
2020-12-26 02:58:18 +00:00
|
|
|
while (value_end > value_start && std::isspace(SignedCharToInt(*value_end)))
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
|
|
|
*value_end = '\0';
|
|
|
|
value_end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*value_start == '\"')
|
|
|
|
{
|
|
|
|
if (*value_end != '\"')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
value_start++;
|
|
|
|
*value_end = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
kvp.emplace_back(start, value_start);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (kvp.empty())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const std::string* num_cheats_value = FindKey(kvp, "cheats");
|
2020-09-25 12:00:34 +00:00
|
|
|
const u32 num_cheats = num_cheats_value ? StringUtil::FromChars<u32>(*num_cheats_value).value_or(0) : 0;
|
2020-09-09 12:11:28 +00:00
|
|
|
if (num_cheats == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (u32 i = 0; i < num_cheats; i++)
|
|
|
|
{
|
|
|
|
const std::string* desc = FindKey(kvp, TinyString::FromFormat("cheat%u_desc", i));
|
|
|
|
const std::string* code = FindKey(kvp, TinyString::FromFormat("cheat%u_code", i));
|
|
|
|
const std::string* enable = FindKey(kvp, TinyString::FromFormat("cheat%u_enable", i));
|
|
|
|
if (!desc || !code || !enable)
|
|
|
|
{
|
2020-11-07 10:36:30 +00:00
|
|
|
Log_WarningPrintf("Missing desc/code/enable for cheat %u", i);
|
2020-09-09 12:11:28 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
CheatCode cc;
|
2020-10-19 15:14:49 +00:00
|
|
|
cc.group = "Ungrouped";
|
2020-09-09 12:11:28 +00:00
|
|
|
cc.description = *desc;
|
|
|
|
cc.enabled = StringUtil::FromChars<bool>(*enable).value_or(false);
|
2020-09-25 11:16:33 +00:00
|
|
|
if (ParseLibretroCheat(&cc, code->c_str()))
|
|
|
|
m_codes.push_back(std::move(cc));
|
|
|
|
}
|
|
|
|
|
2020-11-07 10:36:30 +00:00
|
|
|
Log_InfoPrintf("Loaded %zu cheats (libretro format)", m_codes.size());
|
2020-09-25 11:16:33 +00:00
|
|
|
return !m_codes.empty();
|
|
|
|
}
|
2020-09-09 12:11:28 +00:00
|
|
|
|
2020-12-26 02:58:30 +00:00
|
|
|
bool CheatList::LoadFromEPSXeString(const std::string& str)
|
|
|
|
{
|
|
|
|
std::istringstream iss(str);
|
|
|
|
|
|
|
|
std::string line;
|
|
|
|
std::string group;
|
|
|
|
CheatCode::Type type = CheatCode::Type::Gameshark;
|
|
|
|
CheatCode::Activation activation = CheatCode::Activation::EndFrame;
|
|
|
|
CheatCode current_code;
|
|
|
|
while (std::getline(iss, line))
|
|
|
|
{
|
|
|
|
char* start = line.data();
|
|
|
|
while (*start != '\0' && std::isspace(SignedCharToInt(*start)))
|
|
|
|
start++;
|
|
|
|
|
|
|
|
// skip empty lines
|
|
|
|
if (*start == '\0')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end = start + std::strlen(start) - 1;
|
|
|
|
while (end > start && std::isspace(SignedCharToInt(*end)))
|
|
|
|
{
|
|
|
|
*end = '\0';
|
|
|
|
end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip comments and empty line
|
|
|
|
if (*start == ';' || *start == '\0')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (*start == '#')
|
|
|
|
{
|
|
|
|
start++;
|
|
|
|
|
|
|
|
// new cheat
|
|
|
|
if (current_code.Valid())
|
|
|
|
m_codes.push_back(std::move(current_code));
|
|
|
|
|
|
|
|
current_code = CheatCode();
|
|
|
|
if (group.empty())
|
|
|
|
group = "Ungrouped";
|
|
|
|
|
|
|
|
current_code.group = std::move(group);
|
|
|
|
group = std::string();
|
|
|
|
current_code.type = type;
|
|
|
|
type = CheatCode::Type::Gameshark;
|
|
|
|
current_code.activation = activation;
|
|
|
|
activation = CheatCode::Activation::EndFrame;
|
|
|
|
|
|
|
|
char* separator = std::strchr(start, '\\');
|
|
|
|
if (separator)
|
|
|
|
{
|
|
|
|
*separator = 0;
|
|
|
|
current_code.group = start;
|
|
|
|
start = separator + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
current_code.description.append(start);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!IsHexCharacter(*start) && start != end)
|
|
|
|
start++;
|
|
|
|
if (start == end)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end_ptr;
|
|
|
|
CheatCode::Instruction inst;
|
|
|
|
inst.first = static_cast<u32>(std::strtoul(start, &end_ptr, 16));
|
|
|
|
inst.second = 0;
|
|
|
|
if (end_ptr)
|
|
|
|
{
|
|
|
|
while (!IsHexCharacter(*end_ptr) && end_ptr != end)
|
|
|
|
end_ptr++;
|
|
|
|
if (end_ptr != end)
|
|
|
|
inst.second = static_cast<u32>(std::strtoul(end_ptr, nullptr, 16));
|
|
|
|
}
|
|
|
|
current_code.instructions.push_back(inst);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current_code.Valid())
|
|
|
|
m_codes.push_back(std::move(current_code));
|
|
|
|
|
|
|
|
Log_InfoPrintf("Loaded %zu cheats (EPSXe format)", m_codes.size());
|
|
|
|
return !m_codes.empty();
|
|
|
|
}
|
|
|
|
|
2020-09-25 12:00:34 +00:00
|
|
|
static bool IsLibretroSeparator(char ch)
|
|
|
|
{
|
|
|
|
return (ch == ' ' || ch == '-' || ch == ':' || ch == '+');
|
|
|
|
}
|
|
|
|
|
2020-09-25 11:16:33 +00:00
|
|
|
bool CheatList::ParseLibretroCheat(CheatCode* cc, const char* line)
|
|
|
|
{
|
|
|
|
const char* current_ptr = line;
|
|
|
|
while (current_ptr)
|
|
|
|
{
|
|
|
|
char* end_ptr;
|
|
|
|
CheatCode::Instruction inst;
|
|
|
|
inst.first = static_cast<u32>(std::strtoul(current_ptr, &end_ptr, 16));
|
|
|
|
current_ptr = end_ptr;
|
|
|
|
if (end_ptr)
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
2020-09-25 12:00:34 +00:00
|
|
|
if (!IsLibretroSeparator(*end_ptr))
|
2020-09-25 11:16:33 +00:00
|
|
|
{
|
|
|
|
Log_WarningPrintf("Malformed code '%s'", line);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
end_ptr++;
|
|
|
|
inst.second = static_cast<u32>(std::strtoul(current_ptr, &end_ptr, 16));
|
|
|
|
if (end_ptr && *end_ptr == '\0')
|
|
|
|
end_ptr = nullptr;
|
|
|
|
|
2020-09-25 12:00:34 +00:00
|
|
|
if (end_ptr && *end_ptr != '\0')
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
2020-09-25 12:00:34 +00:00
|
|
|
if (!IsLibretroSeparator(*end_ptr))
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
2020-09-25 11:16:33 +00:00
|
|
|
Log_WarningPrintf("Malformed code '%s'", line);
|
2020-09-09 12:11:28 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
end_ptr++;
|
|
|
|
}
|
|
|
|
|
2020-09-25 11:16:33 +00:00
|
|
|
current_ptr = end_ptr;
|
|
|
|
cc->instructions.push_back(inst);
|
|
|
|
}
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 11:16:33 +00:00
|
|
|
return !cc->instructions.empty();
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CheatList::Apply()
|
|
|
|
{
|
2021-03-04 14:53:20 +00:00
|
|
|
if (!m_master_enable)
|
|
|
|
return;
|
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
for (const CheatCode& code : m_codes)
|
|
|
|
{
|
|
|
|
if (code.enabled)
|
|
|
|
code.Apply();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatList::AddCode(CheatCode cc)
|
|
|
|
{
|
|
|
|
m_codes.push_back(std::move(cc));
|
|
|
|
}
|
|
|
|
|
2020-09-25 11:16:33 +00:00
|
|
|
void CheatList::SetCode(u32 index, CheatCode cc)
|
|
|
|
{
|
|
|
|
if (index > m_codes.size())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (index == m_codes.size())
|
|
|
|
{
|
|
|
|
m_codes.push_back(std::move(cc));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_codes[index] = std::move(cc);
|
|
|
|
}
|
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
void CheatList::RemoveCode(u32 i)
|
|
|
|
{
|
|
|
|
m_codes.erase(m_codes.begin() + i);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<CheatList::Format> CheatList::DetectFileFormat(const char* filename)
|
|
|
|
{
|
2020-11-07 10:36:30 +00:00
|
|
|
std::optional<std::string> str = FileSystem::ReadFileToString(filename);
|
|
|
|
if (!str.has_value() || str->empty())
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
return DetectFileFormat(str.value());
|
|
|
|
}
|
2020-09-09 12:11:28 +00:00
|
|
|
|
2020-11-07 10:36:30 +00:00
|
|
|
CheatList::Format CheatList::DetectFileFormat(const std::string& str)
|
|
|
|
{
|
|
|
|
std::istringstream iss(str);
|
|
|
|
std::string line;
|
|
|
|
while (std::getline(iss, line))
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
2020-11-07 10:36:30 +00:00
|
|
|
char* start = line.data();
|
2020-12-26 02:58:18 +00:00
|
|
|
while (*start != '\0' && std::isspace(SignedCharToInt(*start)))
|
2020-09-09 12:11:28 +00:00
|
|
|
start++;
|
|
|
|
|
|
|
|
// skip empty lines
|
2020-12-26 02:58:30 +00:00
|
|
|
if (*start == '\0')
|
2020-09-09 12:11:28 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end = start + std::strlen(start) - 1;
|
2020-12-26 02:58:18 +00:00
|
|
|
while (end > start && std::isspace(SignedCharToInt(*end)))
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
|
|
|
*end = '\0';
|
|
|
|
end--;
|
|
|
|
}
|
|
|
|
|
2020-12-26 02:58:30 +00:00
|
|
|
// eat comments
|
|
|
|
if (start[0] == '#' || start[0] == ';')
|
|
|
|
continue;
|
|
|
|
|
2020-11-07 10:36:30 +00:00
|
|
|
if (std::strncmp(line.data(), "cheats", 6) == 0)
|
2020-09-09 12:11:28 +00:00
|
|
|
return Format::Libretro;
|
2020-12-26 02:58:30 +00:00
|
|
|
|
|
|
|
// pcsxr if we see brackets
|
|
|
|
if (start[0] == '[')
|
2020-09-09 12:11:28 +00:00
|
|
|
return Format::PCSXR;
|
2020-12-26 02:58:30 +00:00
|
|
|
|
|
|
|
// otherwise if it's a code, it's probably epsxe
|
|
|
|
if (std::isdigit(start[0]))
|
|
|
|
return Format::EPSXe;
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return Format::Count;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatList::LoadFromFile(const char* filename, Format format)
|
2020-11-07 10:36:30 +00:00
|
|
|
{
|
2021-02-11 14:20:04 +00:00
|
|
|
if (!FileSystem::FileExists(filename))
|
|
|
|
return false;
|
|
|
|
|
2020-11-07 10:36:30 +00:00
|
|
|
std::optional<std::string> str = FileSystem::ReadFileToString(filename);
|
2021-02-11 14:20:04 +00:00
|
|
|
if (!str.has_value())
|
2020-11-07 10:36:30 +00:00
|
|
|
return false;
|
|
|
|
|
2021-02-11 14:20:04 +00:00
|
|
|
if (str->empty())
|
|
|
|
return true;
|
|
|
|
|
2020-11-07 10:36:30 +00:00
|
|
|
return LoadFromString(str.value(), format);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatList::LoadFromString(const std::string& str, Format format)
|
2020-09-09 12:11:28 +00:00
|
|
|
{
|
|
|
|
if (format == Format::Autodetect)
|
2020-11-07 10:36:30 +00:00
|
|
|
format = DetectFileFormat(str);
|
2020-09-09 12:11:28 +00:00
|
|
|
|
|
|
|
if (format == Format::PCSXR)
|
2020-11-07 10:36:30 +00:00
|
|
|
return LoadFromPCSXRString(str);
|
2020-09-09 12:11:28 +00:00
|
|
|
else if (format == Format::Libretro)
|
2020-11-07 10:36:30 +00:00
|
|
|
return LoadFromLibretroString(str);
|
Added new cheat cheat types: C3, C4, C5, C6, D7&52
C3-C7 are variants of C0
D7 is a BIT based joker to rule them all. It includes the analog sticks (@ggrtk thanks for adding the analog reading capability). Also added the facility of making dual single key joker by varying the amount of time a button is held down.
51 is a complicated beast that I still need to document
* C3XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is less than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C4XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is greater than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C5XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is less than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C6XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is greater than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* D7PQRRRR TTYYYYYY - 24-Bit Universal BIT Joker, OR the hex values to
combine into a multi-button joker. Because it is BIT
based it is better than D4, D5, D6 or using a D0 joker as you
do not need to worry about any other buttons being
pressed at the same time and you get both analog
sticks for extra functionality. Note if you want to use it
just as a enhanced joker just use D7000000 00YYYYYY when
the buttons/directions are pressed or D7100000 00YYYYYY
when you want to ensure they are not all pressed.
QRRRR TT provides the capability of only activating the
following codes after the keys have been held in for a set
amount of frames. 003C = 60 Frames = 1 Second at 100% Speed
YYYYYY = 000001 L2 Button
YYYYYY = 000002 R2 Button
YYYYYY = 000004 L1 Button
YYYYYY = 000008 R1 Button
YYYYYY = 000010 Triangle Button
YYYYYY = 000020 Circle Button
YYYYYY = 000040 X Button
YYYYYY = 000080 Square Button
YYYYYY = 000100 Select Button
YYYYYY = 000200 L3 Button
YYYYYY = 000400 R3 Button
YYYYYY = 000800 Start Button
YYYYYY = 001000 Up (Digital)
YYYYYY = 002000 Right (Digital)
YYYYYY = 004000 Down (Digital)
YYYYYY = 008000 Left (Digital)
YYYYYY = 010000 Up (Right Thumb)
YYYYYY = 020000 Right (Right Thumb)
YYYYYY = 040000 Down (Right Thumb)
YYYYYY = 080000 Left (Right Thumb)
YYYYYY = 100000 Up (Left Thumb)
YYYYYY = 200000 Right (Left Thumb)
YYYYYY = 400000 Down (Left Thumb)
YYYYYY = 800000 Left (Left Thumb)
NOTE: The 0s in the code are reserved for possible
future use.
TT=Temp Internal Variable 00-FF, 00 will mean it wont be
used, if it's not 00 do not use the same value for
jokers using different keypress combinations for
the same game.
P = 0 or 1. 0 = Check ALL YYYYYY Bits are ON
1 = Check ALL YYYYYY Bits are OFF
Q = Frame Comparison 0 = Dont do any comparison
1 = Check that the button combination
has been held down for exactly
RRRR frames.
2 = Check that the button combination
has been held down for more than
RRRR frames.
3 = Check that the button combination
has been held down for less than
RRRR frames.
4 = Check that the button combination
has been held down for anything
but RRRR frames.
RRRR = 0000 to FFFF, Frame Comparison Value
It will then poke all following codes for rest of cheat
00000000 FFFF or until it reaches the 00000000 FFFF line.
2021-03-03 23:06:08 +00:00
|
|
|
format = Format::EPSXe;
|
|
|
|
return LoadFromEPSXeString(str);
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatList::SaveToPCSXRFile(const char* filename)
|
|
|
|
{
|
|
|
|
auto fp = FileSystem::OpenManagedCFile(filename, "wb");
|
|
|
|
if (!fp)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (const CheatCode& cc : m_codes)
|
|
|
|
{
|
2020-10-21 10:41:44 +00:00
|
|
|
if (!cc.comments.empty())
|
|
|
|
std::fputs(cc.comments.c_str(), fp.get());
|
2020-10-21 10:51:36 +00:00
|
|
|
std::fprintf(fp.get(), "#group=%s\n", cc.group.c_str());
|
|
|
|
std::fprintf(fp.get(), "#type=%s\n", CheatCode::GetTypeName(cc.type));
|
|
|
|
std::fprintf(fp.get(), "#activation=%s\n", CheatCode::GetActivationName(cc.activation));
|
2020-09-09 12:11:28 +00:00
|
|
|
std::fprintf(fp.get(), "[%s%s]\n", cc.enabled ? "*" : "", cc.description.c_str());
|
|
|
|
for (const CheatCode::Instruction& i : cc.instructions)
|
|
|
|
std::fprintf(fp.get(), "%08X %04X\n", i.first, i.second);
|
|
|
|
std::fprintf(fp.get(), "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
std::fflush(fp.get());
|
|
|
|
return (std::ferror(fp.get()) == 0);
|
|
|
|
}
|
|
|
|
|
2020-11-29 06:42:49 +00:00
|
|
|
bool CheatList::LoadFromPackage(const std::string& game_code)
|
|
|
|
{
|
|
|
|
std::unique_ptr<ByteStream> stream =
|
|
|
|
g_host_interface->OpenPackageFile("database/chtdb.txt", BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED);
|
|
|
|
if (!stream)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
std::string db_string = FileSystem::ReadStreamToString(stream.get());
|
|
|
|
stream.reset();
|
|
|
|
if (db_string.empty())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
std::istringstream iss(db_string);
|
|
|
|
std::string line;
|
|
|
|
while (std::getline(iss, line))
|
|
|
|
{
|
|
|
|
char* start = line.data();
|
2020-12-26 02:58:18 +00:00
|
|
|
while (*start != '\0' && std::isspace(SignedCharToInt(*start)))
|
2020-11-29 06:42:49 +00:00
|
|
|
start++;
|
|
|
|
|
|
|
|
// skip empty lines
|
|
|
|
if (*start == '\0' || *start == ';')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end = start + std::strlen(start) - 1;
|
2020-12-26 02:58:18 +00:00
|
|
|
while (end > start && std::isspace(SignedCharToInt(*end)))
|
2020-11-29 06:42:49 +00:00
|
|
|
{
|
|
|
|
*end = '\0';
|
|
|
|
end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (start == end)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (start[0] != ':' || std::strcmp(&start[1], game_code.c_str()) != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// game code match
|
|
|
|
CheatCode current_code;
|
|
|
|
while (std::getline(iss, line))
|
|
|
|
{
|
|
|
|
start = line.data();
|
2020-12-26 02:58:18 +00:00
|
|
|
while (*start != '\0' && std::isspace(SignedCharToInt(*start)))
|
2020-11-29 06:42:49 +00:00
|
|
|
start++;
|
|
|
|
|
|
|
|
// skip empty lines
|
|
|
|
if (*start == '\0' || *start == ';')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
end = start + std::strlen(start) - 1;
|
2020-12-26 02:58:18 +00:00
|
|
|
while (end > start && std::isspace(SignedCharToInt(*end)))
|
2020-11-29 06:42:49 +00:00
|
|
|
{
|
|
|
|
*end = '\0';
|
|
|
|
end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (start == end)
|
|
|
|
continue;
|
|
|
|
|
2021-03-12 17:04:32 +00:00
|
|
|
// stop adding codes when we hit a different game
|
|
|
|
if (start[0] == ':' && (!m_codes.empty() || current_code.Valid()))
|
2020-11-29 06:42:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
if (start[0] == '#')
|
|
|
|
{
|
|
|
|
start++;
|
|
|
|
|
|
|
|
if (current_code.Valid())
|
|
|
|
{
|
|
|
|
m_codes.push_back(std::move(current_code));
|
|
|
|
current_code = CheatCode();
|
|
|
|
}
|
|
|
|
|
|
|
|
// new code
|
|
|
|
char* slash = std::strrchr(start, '\\');
|
|
|
|
if (slash)
|
|
|
|
{
|
|
|
|
*slash = '\0';
|
|
|
|
current_code.group = start;
|
|
|
|
start = slash + 1;
|
|
|
|
}
|
|
|
|
if (current_code.group.empty())
|
|
|
|
current_code.group = "Ungrouped";
|
|
|
|
|
|
|
|
current_code.description = start;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!IsHexCharacter(*start) && start != end)
|
|
|
|
start++;
|
|
|
|
if (start == end)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end_ptr;
|
|
|
|
CheatCode::Instruction inst;
|
|
|
|
inst.first = static_cast<u32>(std::strtoul(start, &end_ptr, 16));
|
|
|
|
inst.second = 0;
|
|
|
|
if (end_ptr)
|
|
|
|
{
|
|
|
|
while (!IsHexCharacter(*end_ptr) && end_ptr != end)
|
|
|
|
end_ptr++;
|
|
|
|
if (end_ptr != end)
|
|
|
|
inst.second = static_cast<u32>(std::strtoul(end_ptr, nullptr, 16));
|
|
|
|
}
|
|
|
|
current_code.instructions.push_back(inst);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current_code.Valid())
|
|
|
|
m_codes.push_back(std::move(current_code));
|
|
|
|
|
|
|
|
Log_InfoPrintf("Loaded %zu codes from package for %s", m_codes.size(), game_code.c_str());
|
|
|
|
return !m_codes.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
Log_WarningPrintf("No codes found in package for %s", game_code.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-09-09 13:44:02 +00:00
|
|
|
u32 CheatList::GetEnabledCodeCount() const
|
|
|
|
{
|
|
|
|
u32 count = 0;
|
|
|
|
for (const CheatCode& cc : m_codes)
|
|
|
|
{
|
|
|
|
if (cc.enabled)
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2020-10-19 15:14:49 +00:00
|
|
|
std::vector<std::string> CheatList::GetCodeGroups() const
|
|
|
|
{
|
|
|
|
std::vector<std::string> groups;
|
|
|
|
for (const CheatCode& cc : m_codes)
|
|
|
|
{
|
|
|
|
if (std::any_of(groups.begin(), groups.end(), [cc](const std::string& group) { return (group == cc.group); }))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
groups.emplace_back(cc.group);
|
|
|
|
}
|
|
|
|
|
|
|
|
return groups;
|
|
|
|
}
|
|
|
|
|
2020-09-09 13:44:02 +00:00
|
|
|
void CheatList::SetCodeEnabled(u32 index, bool state)
|
|
|
|
{
|
2020-12-25 04:21:43 +00:00
|
|
|
if (index >= m_codes.size() || m_codes[index].enabled == state)
|
2020-09-09 13:44:02 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
m_codes[index].enabled = state;
|
2020-12-25 04:21:43 +00:00
|
|
|
if (!state)
|
|
|
|
m_codes[index].ApplyOnDisable();
|
2020-09-09 13:44:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CheatList::EnableCode(u32 index)
|
|
|
|
{
|
|
|
|
SetCodeEnabled(index, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatList::DisableCode(u32 index)
|
|
|
|
{
|
|
|
|
SetCodeEnabled(index, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatList::ApplyCode(u32 index)
|
|
|
|
{
|
|
|
|
if (index >= m_codes.size())
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_codes[index].Apply();
|
|
|
|
}
|
|
|
|
|
2020-11-07 09:58:59 +00:00
|
|
|
const CheatCode* CheatList::FindCode(const char* name) const
|
|
|
|
{
|
|
|
|
for (const CheatCode& cc : m_codes)
|
|
|
|
{
|
|
|
|
if (cc.description == name)
|
|
|
|
return &cc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
const CheatCode* CheatList::FindCode(const char* group, const char* name) const
|
|
|
|
{
|
|
|
|
for (const CheatCode& cc : m_codes)
|
|
|
|
{
|
|
|
|
if (cc.group == group && cc.description == name)
|
|
|
|
return &cc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatList::MergeList(const CheatList& cl)
|
|
|
|
{
|
|
|
|
for (const CheatCode& cc : cl.m_codes)
|
|
|
|
{
|
|
|
|
if (!FindCode(cc.group.c_str(), cc.description.c_str()))
|
|
|
|
AddCode(cc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-19 15:14:49 +00:00
|
|
|
std::string CheatCode::GetInstructionsAsString() const
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
|
|
|
|
for (const Instruction& inst : instructions)
|
|
|
|
{
|
|
|
|
ss << std::hex << std::uppercase << std::setw(8) << std::setfill('0') << inst.first;
|
|
|
|
ss << " ";
|
|
|
|
ss << std::hex << std::uppercase << std::setw(8) << std::setfill('0') << inst.second;
|
|
|
|
ss << '\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatCode::SetInstructionsFromString(const std::string& str)
|
|
|
|
{
|
|
|
|
std::vector<Instruction> new_instructions;
|
|
|
|
std::istringstream ss(str);
|
|
|
|
|
|
|
|
for (std::string line; std::getline(ss, line);)
|
|
|
|
{
|
|
|
|
char* start = line.data();
|
2020-12-26 02:58:18 +00:00
|
|
|
while (*start != '\0' && std::isspace(SignedCharToInt(*start)))
|
2020-10-19 15:14:49 +00:00
|
|
|
start++;
|
|
|
|
|
|
|
|
// skip empty lines
|
|
|
|
if (*start == '\0')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end = start + std::strlen(start) - 1;
|
2020-12-26 02:58:18 +00:00
|
|
|
while (end > start && std::isspace(SignedCharToInt(*end)))
|
2020-10-19 15:14:49 +00:00
|
|
|
{
|
|
|
|
*end = '\0';
|
|
|
|
end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip comments and empty line
|
|
|
|
if (*start == '#' || *start == ';' || *start == '/' || *start == '\"')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
while (!IsHexCharacter(*start) && start != end)
|
|
|
|
start++;
|
|
|
|
if (start == end)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char* end_ptr;
|
|
|
|
CheatCode::Instruction inst;
|
|
|
|
inst.first = static_cast<u32>(std::strtoul(start, &end_ptr, 16));
|
|
|
|
inst.second = 0;
|
|
|
|
if (end_ptr)
|
|
|
|
{
|
|
|
|
while (!IsHexCharacter(*end_ptr) && end_ptr != end)
|
|
|
|
end_ptr++;
|
|
|
|
if (end_ptr != end)
|
|
|
|
inst.second = static_cast<u32>(std::strtoul(end_ptr, nullptr, 16));
|
|
|
|
}
|
|
|
|
new_instructions.push_back(inst);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (new_instructions.empty())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
instructions = std::move(new_instructions);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-09 10:22:43 +00:00
|
|
|
static bool IsConditionalInstruction(CheatCode::InstructionCode code)
|
|
|
|
{
|
|
|
|
switch (code)
|
|
|
|
{
|
|
|
|
case CheatCode::InstructionCode::CompareEqual16: // D0
|
|
|
|
case CheatCode::InstructionCode::CompareNotEqual16: // D1
|
|
|
|
case CheatCode::InstructionCode::CompareLess16: // D2
|
|
|
|
case CheatCode::InstructionCode::CompareGreater16: // D3
|
|
|
|
case CheatCode::InstructionCode::CompareEqual8: // E0
|
|
|
|
case CheatCode::InstructionCode::CompareNotEqual8: // E1
|
|
|
|
case CheatCode::InstructionCode::CompareLess8: // E2
|
|
|
|
case CheatCode::InstructionCode::CompareGreater8: // E3
|
|
|
|
case CheatCode::InstructionCode::CompareButtons: // D4
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 CheatCode::GetNextNonConditionalInstruction(u32 index) const
|
|
|
|
{
|
|
|
|
const u32 count = static_cast<u32>(instructions.size());
|
|
|
|
for (; index < count; index++)
|
|
|
|
{
|
|
|
|
if (!IsConditionalInstruction(instructions[index].code))
|
|
|
|
{
|
|
|
|
// we've found the first non conditional instruction in the chain, so skip over the instruction following it
|
|
|
|
return index + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
void CheatCode::Apply() const
|
|
|
|
{
|
|
|
|
const u32 count = static_cast<u32>(instructions.size());
|
|
|
|
u32 index = 0;
|
|
|
|
for (; index < count;)
|
|
|
|
{
|
|
|
|
const Instruction& inst = instructions[index];
|
|
|
|
switch (inst.code)
|
|
|
|
{
|
2020-09-28 10:09:14 +00:00
|
|
|
case InstructionCode::Nop:
|
|
|
|
{
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
case InstructionCode::ConstantWrite8:
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u8>(inst.address, inst.value8);
|
2020-09-09 12:11:28 +00:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ConstantWrite16:
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u16>(inst.address, inst.value16);
|
2020-09-09 12:11:28 +00:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-12-17 13:33:00 +00:00
|
|
|
case InstructionCode::ExtConstantWrite32:
|
|
|
|
{
|
|
|
|
DoMemoryWrite<u32>(inst.address, inst.value32);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-12-25 04:21:43 +00:00
|
|
|
case InstructionCode::ExtConstantBitSet8:
|
|
|
|
{
|
|
|
|
const u8 value = DoMemoryRead<u8>(inst.address) | inst.value8;
|
|
|
|
DoMemoryWrite<u8>(inst.address, value);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantBitSet16:
|
|
|
|
{
|
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address) | inst.value16;
|
|
|
|
DoMemoryWrite<u16>(inst.address, value);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantBitSet32:
|
|
|
|
{
|
|
|
|
const u32 value = DoMemoryRead<u32>(inst.address) | inst.value32;
|
|
|
|
DoMemoryWrite<u32>(inst.address, value);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantBitClear8:
|
|
|
|
{
|
|
|
|
const u8 value = DoMemoryRead<u8>(inst.address) & ~inst.value8;
|
|
|
|
DoMemoryWrite<u8>(inst.address, value);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantBitClear16:
|
|
|
|
{
|
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address) & ~inst.value16;
|
|
|
|
DoMemoryWrite<u16>(inst.address, value);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantBitClear32:
|
|
|
|
{
|
|
|
|
const u32 value = DoMemoryRead<u32>(inst.address) & ~inst.value32;
|
|
|
|
DoMemoryWrite<u32>(inst.address, value);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-09-28 11:18:08 +00:00
|
|
|
case InstructionCode::ScratchpadWrite16:
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u16>(CPU::DCACHE_LOCATION | (inst.address & CPU::DCACHE_OFFSET_MASK), inst.value16);
|
2020-09-28 11:18:08 +00:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-12-17 13:33:00 +00:00
|
|
|
case InstructionCode::ExtScratchpadWrite32:
|
|
|
|
{
|
|
|
|
DoMemoryWrite<u32>(CPU::DCACHE_LOCATION | (inst.address & CPU::DCACHE_OFFSET_MASK), inst.value32);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtIncrement32:
|
|
|
|
{
|
|
|
|
const u32 value = DoMemoryRead<u32>(inst.address);
|
|
|
|
DoMemoryWrite<u32>(inst.address, value + inst.value32);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtDecrement32:
|
|
|
|
{
|
|
|
|
const u32 value = DoMemoryRead<u32>(inst.address);
|
|
|
|
DoMemoryWrite<u32>(inst.address, value - inst.value32);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
case InstructionCode::Increment16:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u16>(inst.address, value + inst.value16);
|
2020-09-09 12:11:28 +00:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::Decrement16:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u16>(inst.address, value - inst.value16);
|
2020-09-09 12:11:28 +00:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::Increment8:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u8 value = DoMemoryRead<u8>(inst.address);
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u8>(inst.address, value + inst.value8);
|
2020-09-09 12:11:28 +00:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::Decrement8:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u8 value = DoMemoryRead<u8>(inst.address);
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u8>(inst.address, value - inst.value8);
|
2020-09-09 12:11:28 +00:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-12-17 13:33:00 +00:00
|
|
|
case InstructionCode::ExtCompareEqual32:
|
|
|
|
{
|
|
|
|
const u32 value = DoMemoryRead<u32>(inst.address);
|
|
|
|
if (value == inst.value32)
|
|
|
|
index++;
|
|
|
|
else
|
|
|
|
index = GetNextNonConditionalInstruction(index);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtCompareNotEqual32:
|
|
|
|
{
|
|
|
|
const u32 value = DoMemoryRead<u32>(inst.address);
|
|
|
|
if (value != inst.value32)
|
|
|
|
index++;
|
|
|
|
else
|
|
|
|
index = GetNextNonConditionalInstruction(index);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtCompareLess32:
|
|
|
|
{
|
|
|
|
const u32 value = DoMemoryRead<u32>(inst.address);
|
|
|
|
if (value < inst.value32)
|
|
|
|
index++;
|
|
|
|
else
|
|
|
|
index = GetNextNonConditionalInstruction(index);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtCompareGreater32:
|
|
|
|
{
|
|
|
|
const u32 value = DoMemoryRead<u32>(inst.address);
|
|
|
|
if (value > inst.value32)
|
|
|
|
index++;
|
|
|
|
else
|
|
|
|
index = GetNextNonConditionalInstruction(index);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-12-25 04:21:43 +00:00
|
|
|
case InstructionCode::ExtConstantWriteIfMatch16:
|
|
|
|
case InstructionCode::ExtConstantWriteIfMatchWithRestore16:
|
|
|
|
{
|
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
|
|
|
const u16 comparevalue = Truncate16(inst.value32 >> 16);
|
|
|
|
const u16 newvalue = Truncate16(inst.value32 & 0xFFFFu);
|
|
|
|
if (value == comparevalue)
|
|
|
|
DoMemoryWrite<u16>(inst.address, newvalue);
|
|
|
|
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantForceRange8:
|
|
|
|
{
|
|
|
|
const u8 value = DoMemoryRead<u8>(inst.address);
|
|
|
|
const u8 min = Truncate8(inst.value32 & 0x000000FFu);
|
|
|
|
const u8 max = Truncate8((inst.value32 & 0x0000FF00u) >> 8);
|
|
|
|
const u8 overmin = Truncate8((inst.value32 & 0x00FF0000u) >> 16);
|
|
|
|
const u8 overmax = Truncate8((inst.value32 & 0xFF000000u) >> 24);
|
|
|
|
if ((value < min) || (value < min && min == 0x00u && max < 0xFEu))
|
|
|
|
DoMemoryWrite<u8>(inst.address, overmin); // also handles a min value of 0x00
|
|
|
|
else if (value > max)
|
|
|
|
DoMemoryWrite<u8>(inst.address, overmax);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantForceRangeLimits16:
|
|
|
|
{
|
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
|
|
|
const u16 min = Truncate16(inst.value32 & 0x0000FFFFu);
|
|
|
|
const u16 max = Truncate16((inst.value32 & 0xFFFF0000u) >> 16);
|
|
|
|
if ((value < min) || (value < min && min == 0x0000u && max < 0xFFFEu))
|
|
|
|
DoMemoryWrite<u16>(inst.address, min); // also handles a min value of 0x0000
|
|
|
|
else if (value > max)
|
|
|
|
DoMemoryWrite<u16>(inst.address, max);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantForceRangeRollRound16:
|
|
|
|
{
|
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
|
|
|
const u16 min = Truncate16(inst.value32 & 0x0000FFFFu);
|
|
|
|
const u16 max = Truncate16((inst.value32 & 0xFFFF0000u) >> 16);
|
|
|
|
if ((value < min) || (value < min && min == 0x0000u && max < 0xFFFEu))
|
|
|
|
DoMemoryWrite<u16>(inst.address, max); // also handles a min value of 0x0000
|
|
|
|
else if (value > max)
|
|
|
|
DoMemoryWrite<u16>(inst.address, min);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantForceRange16:
|
|
|
|
{
|
|
|
|
const u16 min = Truncate16(inst.value32 & 0x0000FFFFu);
|
|
|
|
const u16 max = Truncate16((inst.value32 & 0xFFFF0000u) >> 16);
|
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
|
|
|
const Instruction& inst2 = instructions[index + 1];
|
|
|
|
const u16 overmin = Truncate16(inst2.value32 & 0x0000FFFFu);
|
|
|
|
const u16 overmax = Truncate16((inst2.value32 & 0xFFFF0000u) >> 16);
|
|
|
|
|
|
|
|
if ((value < min) || (value < min && min == 0x0000u && max < 0xFFFEu))
|
|
|
|
DoMemoryWrite<u16>(inst.address, overmin); // also handles a min value of 0x0000
|
|
|
|
else if (value > max)
|
|
|
|
DoMemoryWrite<u16>(inst.address, overmax);
|
|
|
|
index += 2;
|
|
|
|
}
|
|
|
|
break;
|
2021-02-06 09:19:55 +00:00
|
|
|
|
2020-12-29 14:55:49 +00:00
|
|
|
case InstructionCode::ExtFindAndReplace:
|
|
|
|
{
|
2021-02-06 09:19:55 +00:00
|
|
|
|
2020-12-29 14:55:49 +00:00
|
|
|
if ((index + 4) >= instructions.size())
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Incomplete find/replace instruction");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const Instruction& inst2 = instructions[index + 1];
|
|
|
|
const Instruction& inst3 = instructions[index + 2];
|
|
|
|
const Instruction& inst4 = instructions[index + 3];
|
2021-02-06 09:19:55 +00:00
|
|
|
const Instruction& inst5 = instructions[index + 4];
|
|
|
|
|
2020-12-29 14:55:49 +00:00
|
|
|
const u32 offset = Truncate16(inst.value32 & 0x0000FFFFu) << 1;
|
|
|
|
const u8 wildcard = Truncate8((inst.value32 & 0x00FF0000u) >> 16);
|
|
|
|
const u32 minaddress = inst.address - offset;
|
|
|
|
const u32 maxaddress = inst.address + offset;
|
2021-02-06 09:19:55 +00:00
|
|
|
const u8 f1 = Truncate8((inst2.first & 0xFF000000u) >> 24);
|
|
|
|
const u8 f2 = Truncate8((inst2.first & 0x00FF0000u) >> 16);
|
|
|
|
const u8 f3 = Truncate8((inst2.first & 0x0000FF00u) >> 8);
|
|
|
|
const u8 f4 = Truncate8(inst2.first & 0x000000FFu);
|
|
|
|
const u8 f5 = Truncate8((inst2.value32 & 0xFF000000u) >> 24);
|
|
|
|
const u8 f6 = Truncate8((inst2.value32 & 0x00FF0000u) >> 16);
|
|
|
|
const u8 f7 = Truncate8((inst2.value32 & 0x0000FF00u) >> 8);
|
|
|
|
const u8 f8 = Truncate8(inst2.value32 & 0x000000FFu);
|
|
|
|
const u8 f9 = Truncate8((inst3.first & 0xFF000000u) >> 24);
|
2020-12-29 14:55:49 +00:00
|
|
|
const u8 f10 = Truncate8((inst3.first & 0x00FF0000u) >> 16);
|
|
|
|
const u8 f11 = Truncate8((inst3.first & 0x0000FF00u) >> 8);
|
2021-02-06 09:19:55 +00:00
|
|
|
const u8 f12 = Truncate8(inst3.first & 0x000000FFu);
|
2020-12-29 14:55:49 +00:00
|
|
|
const u8 f13 = Truncate8((inst3.value32 & 0xFF000000u) >> 24);
|
|
|
|
const u8 f14 = Truncate8((inst3.value32 & 0x00FF0000u) >> 16);
|
|
|
|
const u8 f15 = Truncate8((inst3.value32 & 0x0000FF00u) >> 8);
|
2021-02-06 09:19:55 +00:00
|
|
|
const u8 f16 = Truncate8(inst3.value32 & 0x000000FFu);
|
|
|
|
const u8 r1 = Truncate8((inst4.first & 0xFF000000u) >> 24);
|
|
|
|
const u8 r2 = Truncate8((inst4.first & 0x00FF0000u) >> 16);
|
|
|
|
const u8 r3 = Truncate8((inst4.first & 0x0000FF00u) >> 8);
|
|
|
|
const u8 r4 = Truncate8(inst4.first & 0x000000FFu);
|
|
|
|
const u8 r5 = Truncate8((inst4.value32 & 0xFF000000u) >> 24);
|
|
|
|
const u8 r6 = Truncate8((inst4.value32 & 0x00FF0000u) >> 16);
|
|
|
|
const u8 r7 = Truncate8((inst4.value32 & 0x0000FF00u) >> 8);
|
|
|
|
const u8 r8 = Truncate8(inst4.value32 & 0x000000FFu);
|
|
|
|
const u8 r9 = Truncate8((inst5.first & 0xFF000000u) >> 24);
|
2020-12-29 14:55:49 +00:00
|
|
|
const u8 r10 = Truncate8((inst5.first & 0x00FF0000u) >> 16);
|
|
|
|
const u8 r11 = Truncate8((inst5.first & 0x0000FF00u) >> 8);
|
2021-02-06 09:19:55 +00:00
|
|
|
const u8 r12 = Truncate8(inst5.first & 0x000000FFu);
|
2020-12-29 14:55:49 +00:00
|
|
|
const u8 r13 = Truncate8((inst5.value32 & 0xFF000000u) >> 24);
|
|
|
|
const u8 r14 = Truncate8((inst5.value32 & 0x00FF0000u) >> 16);
|
|
|
|
const u8 r15 = Truncate8((inst5.value32 & 0x0000FF00u) >> 8);
|
2021-02-06 09:19:55 +00:00
|
|
|
const u8 r16 = Truncate8(inst5.value32 & 0x000000FFu);
|
|
|
|
|
|
|
|
for (u32 address = minaddress; address <= maxaddress; address += 2)
|
2020-12-29 14:55:49 +00:00
|
|
|
{
|
2021-02-06 09:19:55 +00:00
|
|
|
if ((DoMemoryRead<u8>(address) == f1 || f1 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 1) == f2 || f2 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 2) == f3 || f3 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 3) == f4 || f4 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 4) == f5 || f5 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 5) == f6 || f6 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 6) == f7 || f7 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 7) == f8 || f8 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 8) == f9 || f9 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 9) == f10 || f10 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 10) == f11 || f11 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 11) == f12 || f12 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 12) == f13 || f13 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 13) == f14 || f14 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 14) == f15 || f15 == wildcard) &&
|
|
|
|
(DoMemoryRead<u8>(address + 15) == f16 || f16 == wildcard))
|
2020-12-29 14:55:49 +00:00
|
|
|
{
|
2021-02-06 09:19:55 +00:00
|
|
|
if (r1 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address, r1);
|
|
|
|
if (r2 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 1, r2);
|
|
|
|
if (r3 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 2, r3);
|
|
|
|
if (r4 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 3, r4);
|
|
|
|
if (r5 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 4, r5);
|
|
|
|
if (r6 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 5, r6);
|
|
|
|
if (r7 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 6, r7);
|
|
|
|
if (r8 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 7, r8);
|
|
|
|
if (r9 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 8, r9);
|
|
|
|
if (r10 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 9, r10);
|
|
|
|
if (r11 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 10, r11);
|
|
|
|
if (r12 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 11, r12);
|
|
|
|
if (r13 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 12, r13);
|
|
|
|
if (r14 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 13, r14);
|
|
|
|
if (r15 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 14, r15);
|
|
|
|
if (r16 != wildcard)
|
|
|
|
DoMemoryWrite<u8>(address + 15, r16);
|
|
|
|
address = address + 15;
|
|
|
|
}
|
2020-12-29 14:55:49 +00:00
|
|
|
}
|
|
|
|
index += 5;
|
|
|
|
}
|
|
|
|
break;
|
2020-12-25 04:21:43 +00:00
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
case InstructionCode::CompareEqual16:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
2020-09-09 12:11:28 +00:00
|
|
|
if (value == inst.value16)
|
|
|
|
index++;
|
|
|
|
else
|
2020-12-09 10:22:43 +00:00
|
|
|
index = GetNextNonConditionalInstruction(index);
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::CompareNotEqual16:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
2020-09-09 12:11:28 +00:00
|
|
|
if (value != inst.value16)
|
|
|
|
index++;
|
|
|
|
else
|
2020-12-09 10:22:43 +00:00
|
|
|
index = GetNextNonConditionalInstruction(index);
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::CompareLess16:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
2020-09-09 12:11:28 +00:00
|
|
|
if (value < inst.value16)
|
|
|
|
index++;
|
|
|
|
else
|
2020-12-09 10:22:43 +00:00
|
|
|
index = GetNextNonConditionalInstruction(index);
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::CompareGreater16:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
2020-09-09 12:11:28 +00:00
|
|
|
if (value > inst.value16)
|
|
|
|
index++;
|
|
|
|
else
|
2020-12-09 10:22:43 +00:00
|
|
|
index = GetNextNonConditionalInstruction(index);
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::CompareEqual8:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u8 value = DoMemoryRead<u8>(inst.address);
|
2020-09-09 12:11:28 +00:00
|
|
|
if (value == inst.value8)
|
|
|
|
index++;
|
|
|
|
else
|
2020-12-09 10:22:43 +00:00
|
|
|
index = GetNextNonConditionalInstruction(index);
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::CompareNotEqual8:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u8 value = DoMemoryRead<u8>(inst.address);
|
2020-09-09 12:11:28 +00:00
|
|
|
if (value != inst.value8)
|
|
|
|
index++;
|
|
|
|
else
|
2020-12-09 10:22:43 +00:00
|
|
|
index = GetNextNonConditionalInstruction(index);
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::CompareLess8:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u8 value = DoMemoryRead<u8>(inst.address);
|
2020-09-09 12:11:28 +00:00
|
|
|
if (value < inst.value8)
|
|
|
|
index++;
|
|
|
|
else
|
2020-12-09 10:22:43 +00:00
|
|
|
index = GetNextNonConditionalInstruction(index);
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::CompareGreater8:
|
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
const u8 value = DoMemoryRead<u8>(inst.address);
|
2020-09-09 12:11:28 +00:00
|
|
|
if (value > inst.value8)
|
|
|
|
index++;
|
|
|
|
else
|
2020-12-09 10:22:43 +00:00
|
|
|
index = GetNextNonConditionalInstruction(index);
|
2020-09-09 12:11:28 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-12-06 05:47:00 +00:00
|
|
|
case InstructionCode::CompareButtons: // D4
|
|
|
|
{
|
|
|
|
if (inst.value16 == GetControllerButtonBits())
|
|
|
|
index++;
|
|
|
|
else
|
2020-12-09 10:22:43 +00:00
|
|
|
index = GetNextNonConditionalInstruction(index);
|
2020-12-06 05:47:00 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
Added new cheat cheat types: C3, C4, C5, C6, D7&52
C3-C7 are variants of C0
D7 is a BIT based joker to rule them all. It includes the analog sticks (@ggrtk thanks for adding the analog reading capability). Also added the facility of making dual single key joker by varying the amount of time a button is held down.
51 is a complicated beast that I still need to document
* C3XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is less than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C4XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is greater than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C5XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is less than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C6XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is greater than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* D7PQRRRR TTYYYYYY - 24-Bit Universal BIT Joker, OR the hex values to
combine into a multi-button joker. Because it is BIT
based it is better than D4, D5, D6 or using a D0 joker as you
do not need to worry about any other buttons being
pressed at the same time and you get both analog
sticks for extra functionality. Note if you want to use it
just as a enhanced joker just use D7000000 00YYYYYY when
the buttons/directions are pressed or D7100000 00YYYYYY
when you want to ensure they are not all pressed.
QRRRR TT provides the capability of only activating the
following codes after the keys have been held in for a set
amount of frames. 003C = 60 Frames = 1 Second at 100% Speed
YYYYYY = 000001 L2 Button
YYYYYY = 000002 R2 Button
YYYYYY = 000004 L1 Button
YYYYYY = 000008 R1 Button
YYYYYY = 000010 Triangle Button
YYYYYY = 000020 Circle Button
YYYYYY = 000040 X Button
YYYYYY = 000080 Square Button
YYYYYY = 000100 Select Button
YYYYYY = 000200 L3 Button
YYYYYY = 000400 R3 Button
YYYYYY = 000800 Start Button
YYYYYY = 001000 Up (Digital)
YYYYYY = 002000 Right (Digital)
YYYYYY = 004000 Down (Digital)
YYYYYY = 008000 Left (Digital)
YYYYYY = 010000 Up (Right Thumb)
YYYYYY = 020000 Right (Right Thumb)
YYYYYY = 040000 Down (Right Thumb)
YYYYYY = 080000 Left (Right Thumb)
YYYYYY = 100000 Up (Left Thumb)
YYYYYY = 200000 Right (Left Thumb)
YYYYYY = 400000 Down (Left Thumb)
YYYYYY = 800000 Left (Left Thumb)
NOTE: The 0s in the code are reserved for possible
future use.
TT=Temp Internal Variable 00-FF, 00 will mean it wont be
used, if it's not 00 do not use the same value for
jokers using different keypress combinations for
the same game.
P = 0 or 1. 0 = Check ALL YYYYYY Bits are ON
1 = Check ALL YYYYYY Bits are OFF
Q = Frame Comparison 0 = Dont do any comparison
1 = Check that the button combination
has been held down for exactly
RRRR frames.
2 = Check that the button combination
has been held down for more than
RRRR frames.
3 = Check that the button combination
has been held down for less than
RRRR frames.
4 = Check that the button combination
has been held down for anything
but RRRR frames.
RRRR = 0000 to FFFF, Frame Comparison Value
It will then poke all following codes for rest of cheat
00000000 FFFF or until it reaches the 00000000 FFFF line.
2021-03-03 23:06:08 +00:00
|
|
|
case InstructionCode::ExtTempVariable: // 51
|
|
|
|
{
|
|
|
|
const u32 poke_value = inst.address;
|
|
|
|
const u8 temp_variable_number1 = Truncate8(inst.value32 & 0xFFu);
|
|
|
|
const u8 temp_variable_number2 = Truncate8((inst.value32 & 0xFF00u) >> 8);
|
|
|
|
const u8 sub_type = Truncate8((inst.value32 & 0xFF0000u) >> 16);
|
|
|
|
|
|
|
|
switch (sub_type)
|
|
|
|
{
|
|
|
|
case 0x00: // Write the u8 from temp_variable[temp_variable_number1] to address
|
|
|
|
DoMemoryWrite<u8>(inst.address, Truncate8(temp_variable[temp_variable_number1]));
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x01: // Read the u8 from address to temp_variable[temp_variable_number1]
|
|
|
|
temp_variable[temp_variable_number1] = DoMemoryRead<u8>(inst.address);
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x02: // Write the u8 from address field to the address stored in temp_variable[temp_variable_number1]
|
|
|
|
DoMemoryWrite<u8>(temp_variable[temp_variable_number1], Truncate8(poke_value));
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x10: // Write the u16 from temp_variable[temp_variable_number1] to address
|
|
|
|
DoMemoryWrite<u16>(inst.address, Truncate16(temp_variable[temp_variable_number1]));
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x11: // Read the u16 from address to temp_variable[temp_variable_number1]
|
|
|
|
temp_variable[temp_variable_number1] = DoMemoryRead<u16>(inst.address);
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x12: // Write the u16 from address field to the address stored in temp_variable[temp_variable_number1]
|
|
|
|
DoMemoryWrite<u16>(temp_variable[temp_variable_number1], Truncate16(poke_value));
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x30: // Write the u32 from temp_variable[temp_variable_number1] to address
|
|
|
|
DoMemoryWrite<u32>(inst.address, temp_variable[temp_variable_number1]);
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x31: // Read the u32 from address to temp_variable[temp_variable_number1]
|
|
|
|
temp_variable[temp_variable_number1] = DoMemoryRead<u32>(inst.address);
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x32: // Write the u16 from address field to the address stored in temp_variable[temp_variable_number]
|
|
|
|
DoMemoryWrite<u32>(temp_variable[temp_variable_number1], poke_value);
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x04: // Write the u8 from temp_variable[temp_variable_number2] to the address stored in
|
|
|
|
// temp_variable[temp_variable_number1]
|
|
|
|
DoMemoryWrite<u8>(temp_variable[temp_variable_number1], Truncate8(temp_variable[temp_variable_number2]));
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x14: // Write the u16 from temp_variable[temp_variable_number2] to the address stored in
|
|
|
|
// temp_variable[temp_variable_number1]
|
|
|
|
DoMemoryWrite<u16>(temp_variable[temp_variable_number1], Truncate16(temp_variable[temp_variable_number2]));
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x24: // Write the u32 from temp_variable[temp_variable_number2] to the address stored in
|
|
|
|
// temp_variable[temp_variable_number1]
|
|
|
|
DoMemoryWrite<u32>(temp_variable[temp_variable_number1], temp_variable[temp_variable_number2]);
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x34: // Write the u16 from address field to the address stored in temp_variable[temp_variable_number]
|
|
|
|
DoMemoryWrite<u32>(temp_variable[temp_variable_number1], poke_value);
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x05: // Write the u8 from the address stored in temp_variable[temp_variable_number2] to the address
|
|
|
|
// stored in temp_variable[temp_variable_number1]
|
|
|
|
DoMemoryWrite<u8>(temp_variable[temp_variable_number1],
|
|
|
|
DoMemoryRead<u8>(temp_variable[temp_variable_number2]));
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x15: // Write the u16 from the address stored in temp_variable[temp_variable_number2] to the address
|
|
|
|
// stored in temp_variable[temp_variable_number1]
|
|
|
|
DoMemoryWrite<u16>(temp_variable[temp_variable_number1],
|
|
|
|
DoMemoryRead<u16>(temp_variable[temp_variable_number2]));
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
case 0x25: // Write the u32 from the address stored in temp_variable[temp_variable_number2] to the address
|
|
|
|
// stored in temp_variable[temp_variable_number1]
|
|
|
|
DoMemoryWrite<u32>(temp_variable[temp_variable_number1],
|
|
|
|
DoMemoryRead<u32>(temp_variable[temp_variable_number2]));
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Lots of options exist for expanding into this space
|
|
|
|
default:
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-12-06 05:47:00 +00:00
|
|
|
case InstructionCode::SkipIfNotEqual16: // C0
|
2020-12-17 13:33:00 +00:00
|
|
|
case InstructionCode::ExtSkipIfNotEqual32: // A4
|
2020-12-06 05:47:00 +00:00
|
|
|
case InstructionCode::SkipIfButtonsNotEqual: // D5
|
|
|
|
case InstructionCode::SkipIfButtonsEqual: // D6
|
Added new cheat cheat types: C3, C4, C5, C6, D7&52
C3-C7 are variants of C0
D7 is a BIT based joker to rule them all. It includes the analog sticks (@ggrtk thanks for adding the analog reading capability). Also added the facility of making dual single key joker by varying the amount of time a button is held down.
51 is a complicated beast that I still need to document
* C3XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is less than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C4XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is greater than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C5XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is less than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C6XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is greater than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* D7PQRRRR TTYYYYYY - 24-Bit Universal BIT Joker, OR the hex values to
combine into a multi-button joker. Because it is BIT
based it is better than D4, D5, D6 or using a D0 joker as you
do not need to worry about any other buttons being
pressed at the same time and you get both analog
sticks for extra functionality. Note if you want to use it
just as a enhanced joker just use D7000000 00YYYYYY when
the buttons/directions are pressed or D7100000 00YYYYYY
when you want to ensure they are not all pressed.
QRRRR TT provides the capability of only activating the
following codes after the keys have been held in for a set
amount of frames. 003C = 60 Frames = 1 Second at 100% Speed
YYYYYY = 000001 L2 Button
YYYYYY = 000002 R2 Button
YYYYYY = 000004 L1 Button
YYYYYY = 000008 R1 Button
YYYYYY = 000010 Triangle Button
YYYYYY = 000020 Circle Button
YYYYYY = 000040 X Button
YYYYYY = 000080 Square Button
YYYYYY = 000100 Select Button
YYYYYY = 000200 L3 Button
YYYYYY = 000400 R3 Button
YYYYYY = 000800 Start Button
YYYYYY = 001000 Up (Digital)
YYYYYY = 002000 Right (Digital)
YYYYYY = 004000 Down (Digital)
YYYYYY = 008000 Left (Digital)
YYYYYY = 010000 Up (Right Thumb)
YYYYYY = 020000 Right (Right Thumb)
YYYYYY = 040000 Down (Right Thumb)
YYYYYY = 080000 Left (Right Thumb)
YYYYYY = 100000 Up (Left Thumb)
YYYYYY = 200000 Right (Left Thumb)
YYYYYY = 400000 Down (Left Thumb)
YYYYYY = 800000 Left (Left Thumb)
NOTE: The 0s in the code are reserved for possible
future use.
TT=Temp Internal Variable 00-FF, 00 will mean it wont be
used, if it's not 00 do not use the same value for
jokers using different keypress combinations for
the same game.
P = 0 or 1. 0 = Check ALL YYYYYY Bits are ON
1 = Check ALL YYYYYY Bits are OFF
Q = Frame Comparison 0 = Dont do any comparison
1 = Check that the button combination
has been held down for exactly
RRRR frames.
2 = Check that the button combination
has been held down for more than
RRRR frames.
3 = Check that the button combination
has been held down for less than
RRRR frames.
4 = Check that the button combination
has been held down for anything
but RRRR frames.
RRRR = 0000 to FFFF, Frame Comparison Value
It will then poke all following codes for rest of cheat
00000000 FFFF or until it reaches the 00000000 FFFF line.
2021-03-03 23:06:08 +00:00
|
|
|
case InstructionCode::ExtSkipIfNotLess8: // C3
|
|
|
|
case InstructionCode::ExtSkipIfNotGreater8: // C4
|
|
|
|
case InstructionCode::ExtSkipIfNotLess16: // C5
|
|
|
|
case InstructionCode::ExtSkipIfNotGreater16: // C6
|
2020-12-02 12:50:36 +00:00
|
|
|
{
|
2020-12-04 16:00:37 +00:00
|
|
|
index++;
|
|
|
|
|
2020-12-06 05:47:00 +00:00
|
|
|
bool activate_codes;
|
|
|
|
switch (inst.code)
|
|
|
|
{
|
|
|
|
case InstructionCode::SkipIfNotEqual16: // C0
|
|
|
|
activate_codes = (DoMemoryRead<u16>(inst.address) == inst.value16);
|
|
|
|
break;
|
2020-12-17 13:33:00 +00:00
|
|
|
case InstructionCode::ExtSkipIfNotEqual32: // A4
|
|
|
|
activate_codes = (DoMemoryRead<u32>(inst.address) == inst.value32);
|
|
|
|
break;
|
2020-12-06 05:47:00 +00:00
|
|
|
case InstructionCode::SkipIfButtonsNotEqual: // D5
|
|
|
|
activate_codes = (GetControllerButtonBits() == inst.value16);
|
|
|
|
break;
|
|
|
|
case InstructionCode::SkipIfButtonsEqual: // D6
|
|
|
|
activate_codes = (GetControllerButtonBits() != inst.value16);
|
|
|
|
break;
|
Added new cheat cheat types: C3, C4, C5, C6, D7&52
C3-C7 are variants of C0
D7 is a BIT based joker to rule them all. It includes the analog sticks (@ggrtk thanks for adding the analog reading capability). Also added the facility of making dual single key joker by varying the amount of time a button is held down.
51 is a complicated beast that I still need to document
* C3XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is less than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C4XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is greater than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C5XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is less than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C6XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is greater than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* D7PQRRRR TTYYYYYY - 24-Bit Universal BIT Joker, OR the hex values to
combine into a multi-button joker. Because it is BIT
based it is better than D4, D5, D6 or using a D0 joker as you
do not need to worry about any other buttons being
pressed at the same time and you get both analog
sticks for extra functionality. Note if you want to use it
just as a enhanced joker just use D7000000 00YYYYYY when
the buttons/directions are pressed or D7100000 00YYYYYY
when you want to ensure they are not all pressed.
QRRRR TT provides the capability of only activating the
following codes after the keys have been held in for a set
amount of frames. 003C = 60 Frames = 1 Second at 100% Speed
YYYYYY = 000001 L2 Button
YYYYYY = 000002 R2 Button
YYYYYY = 000004 L1 Button
YYYYYY = 000008 R1 Button
YYYYYY = 000010 Triangle Button
YYYYYY = 000020 Circle Button
YYYYYY = 000040 X Button
YYYYYY = 000080 Square Button
YYYYYY = 000100 Select Button
YYYYYY = 000200 L3 Button
YYYYYY = 000400 R3 Button
YYYYYY = 000800 Start Button
YYYYYY = 001000 Up (Digital)
YYYYYY = 002000 Right (Digital)
YYYYYY = 004000 Down (Digital)
YYYYYY = 008000 Left (Digital)
YYYYYY = 010000 Up (Right Thumb)
YYYYYY = 020000 Right (Right Thumb)
YYYYYY = 040000 Down (Right Thumb)
YYYYYY = 080000 Left (Right Thumb)
YYYYYY = 100000 Up (Left Thumb)
YYYYYY = 200000 Right (Left Thumb)
YYYYYY = 400000 Down (Left Thumb)
YYYYYY = 800000 Left (Left Thumb)
NOTE: The 0s in the code are reserved for possible
future use.
TT=Temp Internal Variable 00-FF, 00 will mean it wont be
used, if it's not 00 do not use the same value for
jokers using different keypress combinations for
the same game.
P = 0 or 1. 0 = Check ALL YYYYYY Bits are ON
1 = Check ALL YYYYYY Bits are OFF
Q = Frame Comparison 0 = Dont do any comparison
1 = Check that the button combination
has been held down for exactly
RRRR frames.
2 = Check that the button combination
has been held down for more than
RRRR frames.
3 = Check that the button combination
has been held down for less than
RRRR frames.
4 = Check that the button combination
has been held down for anything
but RRRR frames.
RRRR = 0000 to FFFF, Frame Comparison Value
It will then poke all following codes for rest of cheat
00000000 FFFF or until it reaches the 00000000 FFFF line.
2021-03-03 23:06:08 +00:00
|
|
|
case InstructionCode::ExtSkipIfNotLess8: // C3
|
|
|
|
activate_codes = (DoMemoryRead<u8>(inst.address) < inst.value8);
|
|
|
|
break;
|
|
|
|
case InstructionCode::ExtSkipIfNotGreater8: // C4
|
|
|
|
activate_codes = (DoMemoryRead<u8>(inst.address) > inst.value8);
|
|
|
|
break;
|
|
|
|
case InstructionCode::ExtSkipIfNotLess16: // C5
|
|
|
|
activate_codes = (DoMemoryRead<u16>(inst.address) < inst.value16);
|
|
|
|
break;
|
|
|
|
case InstructionCode::ExtSkipIfNotGreater16: // C6
|
|
|
|
activate_codes = (DoMemoryRead<u16>(inst.address) > inst.value16);
|
|
|
|
break;
|
2020-12-06 05:47:00 +00:00
|
|
|
default:
|
|
|
|
activate_codes = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (activate_codes)
|
2020-12-04 16:00:37 +00:00
|
|
|
{
|
|
|
|
// execute following instructions
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip to the next separator (00000000 FFFF), or end
|
|
|
|
constexpr u64 separator_value = UINT64_C(0x000000000000FFFF);
|
|
|
|
while (index < count)
|
|
|
|
{
|
|
|
|
// we don't want to execute the separator instruction
|
|
|
|
const u64 bits = instructions[index++].bits;
|
|
|
|
if (bits == separator_value)
|
|
|
|
break;
|
|
|
|
}
|
2020-12-02 12:50:36 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
Added new cheat cheat types: C3, C4, C5, C6, D7&52
C3-C7 are variants of C0
D7 is a BIT based joker to rule them all. It includes the analog sticks (@ggrtk thanks for adding the analog reading capability). Also added the facility of making dual single key joker by varying the amount of time a button is held down.
51 is a complicated beast that I still need to document
* C3XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is less than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C4XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is greater than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C5XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is less than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C6XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is greater than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* D7PQRRRR TTYYYYYY - 24-Bit Universal BIT Joker, OR the hex values to
combine into a multi-button joker. Because it is BIT
based it is better than D4, D5, D6 or using a D0 joker as you
do not need to worry about any other buttons being
pressed at the same time and you get both analog
sticks for extra functionality. Note if you want to use it
just as a enhanced joker just use D7000000 00YYYYYY when
the buttons/directions are pressed or D7100000 00YYYYYY
when you want to ensure they are not all pressed.
QRRRR TT provides the capability of only activating the
following codes after the keys have been held in for a set
amount of frames. 003C = 60 Frames = 1 Second at 100% Speed
YYYYYY = 000001 L2 Button
YYYYYY = 000002 R2 Button
YYYYYY = 000004 L1 Button
YYYYYY = 000008 R1 Button
YYYYYY = 000010 Triangle Button
YYYYYY = 000020 Circle Button
YYYYYY = 000040 X Button
YYYYYY = 000080 Square Button
YYYYYY = 000100 Select Button
YYYYYY = 000200 L3 Button
YYYYYY = 000400 R3 Button
YYYYYY = 000800 Start Button
YYYYYY = 001000 Up (Digital)
YYYYYY = 002000 Right (Digital)
YYYYYY = 004000 Down (Digital)
YYYYYY = 008000 Left (Digital)
YYYYYY = 010000 Up (Right Thumb)
YYYYYY = 020000 Right (Right Thumb)
YYYYYY = 040000 Down (Right Thumb)
YYYYYY = 080000 Left (Right Thumb)
YYYYYY = 100000 Up (Left Thumb)
YYYYYY = 200000 Right (Left Thumb)
YYYYYY = 400000 Down (Left Thumb)
YYYYYY = 800000 Left (Left Thumb)
NOTE: The 0s in the code are reserved for possible
future use.
TT=Temp Internal Variable 00-FF, 00 will mean it wont be
used, if it's not 00 do not use the same value for
jokers using different keypress combinations for
the same game.
P = 0 or 1. 0 = Check ALL YYYYYY Bits are ON
1 = Check ALL YYYYYY Bits are OFF
Q = Frame Comparison 0 = Dont do any comparison
1 = Check that the button combination
has been held down for exactly
RRRR frames.
2 = Check that the button combination
has been held down for more than
RRRR frames.
3 = Check that the button combination
has been held down for less than
RRRR frames.
4 = Check that the button combination
has been held down for anything
but RRRR frames.
RRRR = 0000 to FFFF, Frame Comparison Value
It will then poke all following codes for rest of cheat
00000000 FFFF or until it reaches the 00000000 FFFF line.
2021-03-03 23:06:08 +00:00
|
|
|
case InstructionCode::ExtBitCompareButtons: // D7
|
|
|
|
{
|
|
|
|
index++;
|
|
|
|
bool activate_codes;
|
|
|
|
const u32 frame_compare_value = inst.address & 0xFFFFu;
|
|
|
|
const u8 temp_variable_number = Truncate8((inst.value32 & 0xFF000000u)>>24);
|
|
|
|
const bool bit_comparison_type = ((inst.address & 0x100000u) >>20);
|
|
|
|
const u8 frame_comparison = Truncate8((inst.address & 0xF0000u) >>16);
|
|
|
|
const u32 check_value = (inst.value32 & 0xFFFFFFu);
|
|
|
|
const u32 value1 = GetControllerButtonBits();
|
|
|
|
const u32 value2 = GetControllerAnalogBits();
|
|
|
|
u32 value = value1 | value2;
|
|
|
|
|
|
|
|
if ((bit_comparison_type==false && check_value==(value & check_value))//Check Bits are set
|
|
|
|
|| (bit_comparison_type==true && check_value!=(value & check_value))) //Check Bits are clear
|
|
|
|
{
|
|
|
|
temp_variable[temp_variable_number]+=1;
|
|
|
|
switch (frame_comparison)
|
|
|
|
{
|
|
|
|
case 0x0: // No comparison on frame count, just do it
|
|
|
|
activate_codes = true;
|
|
|
|
break;
|
|
|
|
case 0x1: // Check if frame_compare_value == current count
|
|
|
|
activate_codes = (temp_variable[temp_variable_number] == frame_compare_value);
|
|
|
|
break;
|
|
|
|
case 0x2: // Check if frame_compare_value < current count
|
|
|
|
activate_codes = (temp_variable[temp_variable_number] < frame_compare_value);
|
|
|
|
break;
|
|
|
|
case 0x3: // Check if frame_compare_value > current count
|
|
|
|
activate_codes = (temp_variable[temp_variable_number] > frame_compare_value);
|
|
|
|
break;
|
|
|
|
case 0x4: // Check if frame_compare_value != current count
|
|
|
|
activate_codes = (temp_variable[temp_variable_number] != frame_compare_value);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
activate_codes = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
temp_variable[temp_variable_number]=0;
|
|
|
|
activate_codes = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (activate_codes)
|
|
|
|
{
|
|
|
|
// execute following instructions
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip to the next separator (00000000 FFFF), or end
|
|
|
|
constexpr u64 separator_value = UINT64_C(0x000000000000FFFF);
|
|
|
|
while (index < count)
|
|
|
|
{
|
|
|
|
// we don't want to execute the separator instruction
|
|
|
|
const u64 bits = instructions[index++].bits;
|
|
|
|
if (bits == separator_value)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-12-06 05:50:58 +00:00
|
|
|
case InstructionCode::DelayActivation: // C1
|
|
|
|
{
|
|
|
|
// A value of around 4000 or 5000 will usually give you a good 20-30 second delay before codes are activated.
|
|
|
|
// Frame number * 0.3 -> (20 * 60) * 10 / 3 => 4000
|
|
|
|
const u32 comp_value = (System::GetFrameNumber() * 10) / 3;
|
|
|
|
if (comp_value < inst.value16)
|
|
|
|
index = count;
|
|
|
|
else
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-09-28 10:06:01 +00:00
|
|
|
case InstructionCode::Slide:
|
|
|
|
{
|
|
|
|
if ((index + 1) >= instructions.size())
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Incomplete slide instruction");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const u32 slide_count = (inst.first >> 8) & 0xFFu;
|
2020-12-24 00:17:09 +00:00
|
|
|
const u32 address_increment = inst.first & 0xFFu;
|
|
|
|
const u16 value_increment = Truncate16(inst.second);
|
2020-09-28 10:06:01 +00:00
|
|
|
const Instruction& inst2 = instructions[index + 1];
|
|
|
|
const InstructionCode write_type = inst2.code;
|
|
|
|
u32 address = inst2.address;
|
|
|
|
u16 value = inst2.value16;
|
|
|
|
|
|
|
|
if (write_type == InstructionCode::ConstantWrite8)
|
|
|
|
{
|
|
|
|
for (u32 i = 0; i < slide_count; i++)
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u8>(address, Truncate8(value));
|
2020-09-28 10:06:01 +00:00
|
|
|
address += address_increment;
|
|
|
|
value += value_increment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (write_type == InstructionCode::ConstantWrite16)
|
|
|
|
{
|
|
|
|
for (u32 i = 0; i < slide_count; i++)
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u16>(address, value);
|
2020-09-28 10:06:01 +00:00
|
|
|
address += address_increment;
|
|
|
|
value += value_increment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-02-06 09:19:55 +00:00
|
|
|
Log_ErrorPrintf("Invalid command in second slide parameter 0x%02X", static_cast<unsigned>(write_type));
|
2020-09-28 10:06:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
index += 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-09-28 10:24:58 +00:00
|
|
|
case InstructionCode::MemoryCopy:
|
|
|
|
{
|
|
|
|
if ((index + 1) >= instructions.size())
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Incomplete memory copy instruction");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const Instruction& inst2 = instructions[index + 1];
|
|
|
|
const u32 byte_count = inst.value16;
|
|
|
|
u32 src_address = inst.address;
|
|
|
|
u32 dst_address = inst2.address;
|
|
|
|
|
|
|
|
for (u32 i = 0; i < byte_count; i++)
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
u8 value = DoMemoryRead<u8>(src_address);
|
|
|
|
DoMemoryWrite<u8>(dst_address, value);
|
2020-09-28 10:24:58 +00:00
|
|
|
src_address++;
|
|
|
|
dst_address++;
|
|
|
|
}
|
|
|
|
|
|
|
|
index += 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-09-09 12:11:28 +00:00
|
|
|
default:
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Unhandled instruction code 0x%02X (%08X %08X)", static_cast<u8>(inst.code.GetValue()),
|
|
|
|
inst.first, inst.second);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-19 15:14:49 +00:00
|
|
|
|
2020-12-25 04:21:43 +00:00
|
|
|
void CheatCode::ApplyOnDisable() const
|
|
|
|
{
|
|
|
|
const u32 count = static_cast<u32>(instructions.size());
|
|
|
|
u32 index = 0;
|
|
|
|
for (; index < count;)
|
|
|
|
{
|
|
|
|
const Instruction& inst = instructions[index];
|
|
|
|
switch (inst.code)
|
|
|
|
{
|
|
|
|
case InstructionCode::Nop:
|
|
|
|
case InstructionCode::ConstantWrite8:
|
|
|
|
case InstructionCode::ConstantWrite16:
|
|
|
|
case InstructionCode::ExtConstantWrite32:
|
|
|
|
case InstructionCode::ExtConstantBitSet8:
|
|
|
|
case InstructionCode::ExtConstantBitSet16:
|
|
|
|
case InstructionCode::ExtConstantBitSet32:
|
|
|
|
case InstructionCode::ExtConstantBitClear8:
|
|
|
|
case InstructionCode::ExtConstantBitClear16:
|
|
|
|
case InstructionCode::ExtConstantBitClear32:
|
|
|
|
case InstructionCode::ScratchpadWrite16:
|
|
|
|
case InstructionCode::ExtScratchpadWrite32:
|
|
|
|
case InstructionCode::ExtIncrement32:
|
|
|
|
case InstructionCode::ExtDecrement32:
|
|
|
|
case InstructionCode::Increment16:
|
|
|
|
case InstructionCode::Decrement16:
|
|
|
|
case InstructionCode::Increment8:
|
|
|
|
case InstructionCode::Decrement8:
|
|
|
|
case InstructionCode::ExtConstantForceRange8:
|
|
|
|
case InstructionCode::ExtConstantForceRangeLimits16:
|
|
|
|
case InstructionCode::ExtConstantForceRangeRollRound16:
|
|
|
|
case InstructionCode::DelayActivation: // C1
|
|
|
|
case InstructionCode::ExtConstantWriteIfMatch16:
|
Added new cheat cheat types: C3, C4, C5, C6, D7&52
C3-C7 are variants of C0
D7 is a BIT based joker to rule them all. It includes the analog sticks (@ggrtk thanks for adding the analog reading capability). Also added the facility of making dual single key joker by varying the amount of time a button is held down.
51 is a complicated beast that I still need to document
* C3XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is less than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C4XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is greater than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C5XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is less than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C6XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is greater than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* D7PQRRRR TTYYYYYY - 24-Bit Universal BIT Joker, OR the hex values to
combine into a multi-button joker. Because it is BIT
based it is better than D4, D5, D6 or using a D0 joker as you
do not need to worry about any other buttons being
pressed at the same time and you get both analog
sticks for extra functionality. Note if you want to use it
just as a enhanced joker just use D7000000 00YYYYYY when
the buttons/directions are pressed or D7100000 00YYYYYY
when you want to ensure they are not all pressed.
QRRRR TT provides the capability of only activating the
following codes after the keys have been held in for a set
amount of frames. 003C = 60 Frames = 1 Second at 100% Speed
YYYYYY = 000001 L2 Button
YYYYYY = 000002 R2 Button
YYYYYY = 000004 L1 Button
YYYYYY = 000008 R1 Button
YYYYYY = 000010 Triangle Button
YYYYYY = 000020 Circle Button
YYYYYY = 000040 X Button
YYYYYY = 000080 Square Button
YYYYYY = 000100 Select Button
YYYYYY = 000200 L3 Button
YYYYYY = 000400 R3 Button
YYYYYY = 000800 Start Button
YYYYYY = 001000 Up (Digital)
YYYYYY = 002000 Right (Digital)
YYYYYY = 004000 Down (Digital)
YYYYYY = 008000 Left (Digital)
YYYYYY = 010000 Up (Right Thumb)
YYYYYY = 020000 Right (Right Thumb)
YYYYYY = 040000 Down (Right Thumb)
YYYYYY = 080000 Left (Right Thumb)
YYYYYY = 100000 Up (Left Thumb)
YYYYYY = 200000 Right (Left Thumb)
YYYYYY = 400000 Down (Left Thumb)
YYYYYY = 800000 Left (Left Thumb)
NOTE: The 0s in the code are reserved for possible
future use.
TT=Temp Internal Variable 00-FF, 00 will mean it wont be
used, if it's not 00 do not use the same value for
jokers using different keypress combinations for
the same game.
P = 0 or 1. 0 = Check ALL YYYYYY Bits are ON
1 = Check ALL YYYYYY Bits are OFF
Q = Frame Comparison 0 = Dont do any comparison
1 = Check that the button combination
has been held down for exactly
RRRR frames.
2 = Check that the button combination
has been held down for more than
RRRR frames.
3 = Check that the button combination
has been held down for less than
RRRR frames.
4 = Check that the button combination
has been held down for anything
but RRRR frames.
RRRR = 0000 to FFFF, Frame Comparison Value
It will then poke all following codes for rest of cheat
00000000 FFFF or until it reaches the 00000000 FFFF line.
2021-03-03 23:06:08 +00:00
|
|
|
case InstructionCode::ExtTempVariable:
|
2020-12-25 04:21:43 +00:00
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantForceRange16:
|
|
|
|
case InstructionCode::Slide:
|
|
|
|
case InstructionCode::MemoryCopy:
|
|
|
|
index += 2;
|
|
|
|
break;
|
2021-02-06 09:19:55 +00:00
|
|
|
case InstructionCode::ExtFindAndReplace:
|
|
|
|
index += 5;
|
2020-12-29 14:55:49 +00:00
|
|
|
break;
|
2020-12-25 04:21:43 +00:00
|
|
|
// for conditionals, we don't want to skip over in case it changed at some point
|
|
|
|
case InstructionCode::ExtCompareEqual32:
|
|
|
|
case InstructionCode::ExtCompareNotEqual32:
|
|
|
|
case InstructionCode::ExtCompareLess32:
|
|
|
|
case InstructionCode::ExtCompareGreater32:
|
|
|
|
case InstructionCode::CompareEqual16:
|
|
|
|
case InstructionCode::CompareNotEqual16:
|
|
|
|
case InstructionCode::CompareLess16:
|
|
|
|
case InstructionCode::CompareGreater16:
|
|
|
|
case InstructionCode::CompareEqual8:
|
|
|
|
case InstructionCode::CompareNotEqual8:
|
|
|
|
case InstructionCode::CompareLess8:
|
|
|
|
case InstructionCode::CompareGreater8:
|
|
|
|
case InstructionCode::CompareButtons: // D4
|
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// same deal for block conditionals
|
|
|
|
case InstructionCode::SkipIfNotEqual16: // C0
|
|
|
|
case InstructionCode::ExtSkipIfNotEqual32: // A4
|
|
|
|
case InstructionCode::SkipIfButtonsNotEqual: // D5
|
|
|
|
case InstructionCode::SkipIfButtonsEqual: // D6
|
Added new cheat cheat types: C3, C4, C5, C6, D7&52
C3-C7 are variants of C0
D7 is a BIT based joker to rule them all. It includes the analog sticks (@ggrtk thanks for adding the analog reading capability). Also added the facility of making dual single key joker by varying the amount of time a button is held down.
51 is a complicated beast that I still need to document
* C3XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is less than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C4XXXXXX 00YY - 8-Bit Master Code, if ($XXXXXX) is greater than 0xYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C5XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is less than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* C6XXXXXX YYYY - 16-Bit Master Code, if ($XXXXXX) is greater than 0xYYYY poke all
00000000 FFFF following codes for rest of the cheat or until it reaches the
00000000 FFFF line.
* D7PQRRRR TTYYYYYY - 24-Bit Universal BIT Joker, OR the hex values to
combine into a multi-button joker. Because it is BIT
based it is better than D4, D5, D6 or using a D0 joker as you
do not need to worry about any other buttons being
pressed at the same time and you get both analog
sticks for extra functionality. Note if you want to use it
just as a enhanced joker just use D7000000 00YYYYYY when
the buttons/directions are pressed or D7100000 00YYYYYY
when you want to ensure they are not all pressed.
QRRRR TT provides the capability of only activating the
following codes after the keys have been held in for a set
amount of frames. 003C = 60 Frames = 1 Second at 100% Speed
YYYYYY = 000001 L2 Button
YYYYYY = 000002 R2 Button
YYYYYY = 000004 L1 Button
YYYYYY = 000008 R1 Button
YYYYYY = 000010 Triangle Button
YYYYYY = 000020 Circle Button
YYYYYY = 000040 X Button
YYYYYY = 000080 Square Button
YYYYYY = 000100 Select Button
YYYYYY = 000200 L3 Button
YYYYYY = 000400 R3 Button
YYYYYY = 000800 Start Button
YYYYYY = 001000 Up (Digital)
YYYYYY = 002000 Right (Digital)
YYYYYY = 004000 Down (Digital)
YYYYYY = 008000 Left (Digital)
YYYYYY = 010000 Up (Right Thumb)
YYYYYY = 020000 Right (Right Thumb)
YYYYYY = 040000 Down (Right Thumb)
YYYYYY = 080000 Left (Right Thumb)
YYYYYY = 100000 Up (Left Thumb)
YYYYYY = 200000 Right (Left Thumb)
YYYYYY = 400000 Down (Left Thumb)
YYYYYY = 800000 Left (Left Thumb)
NOTE: The 0s in the code are reserved for possible
future use.
TT=Temp Internal Variable 00-FF, 00 will mean it wont be
used, if it's not 00 do not use the same value for
jokers using different keypress combinations for
the same game.
P = 0 or 1. 0 = Check ALL YYYYYY Bits are ON
1 = Check ALL YYYYYY Bits are OFF
Q = Frame Comparison 0 = Dont do any comparison
1 = Check that the button combination
has been held down for exactly
RRRR frames.
2 = Check that the button combination
has been held down for more than
RRRR frames.
3 = Check that the button combination
has been held down for less than
RRRR frames.
4 = Check that the button combination
has been held down for anything
but RRRR frames.
RRRR = 0000 to FFFF, Frame Comparison Value
It will then poke all following codes for rest of cheat
00000000 FFFF or until it reaches the 00000000 FFFF line.
2021-03-03 23:06:08 +00:00
|
|
|
case InstructionCode::ExtBitCompareButtons: // D7
|
|
|
|
case InstructionCode::ExtSkipIfNotLess8: // C3
|
|
|
|
case InstructionCode::ExtSkipIfNotGreater8: // C4
|
|
|
|
case InstructionCode::ExtSkipIfNotLess16: // C5
|
|
|
|
case InstructionCode::ExtSkipIfNotGreater16: // C6
|
2020-12-25 04:21:43 +00:00
|
|
|
index++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case InstructionCode::ExtConstantWriteIfMatchWithRestore16:
|
|
|
|
{
|
|
|
|
const u16 value = DoMemoryRead<u16>(inst.address);
|
|
|
|
const u16 comparevalue = Truncate16(inst.value32 >> 16);
|
|
|
|
const u16 newvalue = Truncate16(inst.value32 & 0xFFFFu);
|
|
|
|
if (value == newvalue)
|
|
|
|
DoMemoryWrite<u16>(inst.address, comparevalue);
|
|
|
|
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Unhandled instruction code 0x%02X (%08X %08X)", static_cast<u8>(inst.code.GetValue()),
|
|
|
|
inst.first, inst.second);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-19 15:14:49 +00:00
|
|
|
static std::array<const char*, 1> s_cheat_code_type_names = {{"Gameshark"}};
|
|
|
|
static std::array<const char*, 1> s_cheat_code_type_display_names{{TRANSLATABLE("Cheats", "Gameshark")}};
|
|
|
|
|
|
|
|
const char* CheatCode::GetTypeName(Type type)
|
|
|
|
{
|
|
|
|
return s_cheat_code_type_names[static_cast<u32>(type)];
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* CheatCode::GetTypeDisplayName(Type type)
|
|
|
|
{
|
|
|
|
return s_cheat_code_type_display_names[static_cast<u32>(type)];
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<CheatCode::Type> CheatCode::ParseTypeName(const char* str)
|
|
|
|
{
|
|
|
|
for (u32 i = 0; i < static_cast<u32>(s_cheat_code_type_names.size()); i++)
|
|
|
|
{
|
|
|
|
if (std::strcmp(s_cheat_code_type_names[i], str) == 0)
|
|
|
|
return static_cast<Type>(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::array<const char*, 2> s_cheat_code_activation_names = {{"Manual", "EndFrame"}};
|
|
|
|
static std::array<const char*, 2> s_cheat_code_activation_display_names{
|
|
|
|
{TRANSLATABLE("Cheats", "Manual"), TRANSLATABLE("Cheats", "Automatic (Frame End)")}};
|
|
|
|
|
|
|
|
const char* CheatCode::GetActivationName(Activation activation)
|
|
|
|
{
|
|
|
|
return s_cheat_code_activation_names[static_cast<u32>(activation)];
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* CheatCode::GetActivationDisplayName(Activation activation)
|
|
|
|
{
|
|
|
|
return s_cheat_code_activation_display_names[static_cast<u32>(activation)];
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<CheatCode::Activation> CheatCode::ParseActivationName(const char* str)
|
|
|
|
{
|
|
|
|
for (u32 i = 0; i < static_cast<u32>(s_cheat_code_activation_names.size()); i++)
|
|
|
|
{
|
|
|
|
if (std::strcmp(s_cheat_code_activation_names[i], str) == 0)
|
|
|
|
return static_cast<Activation>(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
MemoryScan::MemoryScan() = default;
|
|
|
|
|
|
|
|
MemoryScan::~MemoryScan() = default;
|
|
|
|
|
|
|
|
void MemoryScan::ResetSearch()
|
|
|
|
{
|
|
|
|
m_results.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryScan::Search()
|
|
|
|
{
|
|
|
|
m_results.clear();
|
|
|
|
|
|
|
|
switch (m_size)
|
|
|
|
{
|
|
|
|
case MemoryAccessSize::Byte:
|
|
|
|
SearchBytes();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::HalfWord:
|
|
|
|
SearchHalfwords();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::Word:
|
|
|
|
SearchWords();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryScan::SearchBytes()
|
|
|
|
{
|
|
|
|
for (PhysicalMemoryAddress address = m_start_address; address < m_end_address; address++)
|
|
|
|
{
|
2020-10-21 11:43:51 +00:00
|
|
|
if (!IsValidScanAddress(address))
|
|
|
|
continue;
|
|
|
|
|
2020-10-21 12:01:50 +00:00
|
|
|
const u8 bvalue = DoMemoryRead<u8>(address);
|
2020-10-19 15:14:49 +00:00
|
|
|
|
|
|
|
Result res;
|
|
|
|
res.address = address;
|
|
|
|
res.value = m_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
|
|
|
|
res.last_value = res.value;
|
|
|
|
res.value_changed = false;
|
|
|
|
|
|
|
|
if (res.Filter(m_operator, m_value, m_signed))
|
|
|
|
m_results.push_back(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryScan::SearchHalfwords()
|
|
|
|
{
|
|
|
|
for (PhysicalMemoryAddress address = m_start_address; address < m_end_address; address += 2)
|
|
|
|
{
|
2020-10-21 11:43:51 +00:00
|
|
|
if (!IsValidScanAddress(address))
|
|
|
|
continue;
|
|
|
|
|
2020-10-21 12:01:50 +00:00
|
|
|
const u16 bvalue = DoMemoryRead<u16>(address);
|
2020-10-19 15:14:49 +00:00
|
|
|
|
|
|
|
Result res;
|
|
|
|
res.address = address;
|
|
|
|
res.value = m_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
|
|
|
|
res.last_value = res.value;
|
|
|
|
res.value_changed = false;
|
|
|
|
|
|
|
|
if (res.Filter(m_operator, m_value, m_signed))
|
|
|
|
m_results.push_back(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryScan::SearchWords()
|
|
|
|
{
|
|
|
|
for (PhysicalMemoryAddress address = m_start_address; address < m_end_address; address += 4)
|
|
|
|
{
|
2020-10-21 11:43:51 +00:00
|
|
|
if (!IsValidScanAddress(address))
|
|
|
|
continue;
|
|
|
|
|
2020-10-19 15:14:49 +00:00
|
|
|
Result res;
|
|
|
|
res.address = address;
|
2020-10-21 12:01:50 +00:00
|
|
|
res.value = DoMemoryRead<u32>(address);
|
2020-10-19 15:14:49 +00:00
|
|
|
res.last_value = res.value;
|
|
|
|
res.value_changed = false;
|
|
|
|
|
|
|
|
if (res.Filter(m_operator, m_value, m_signed))
|
|
|
|
m_results.push_back(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryScan::SearchAgain()
|
|
|
|
{
|
|
|
|
ResultVector new_results;
|
|
|
|
new_results.reserve(m_results.size());
|
|
|
|
for (Result& res : m_results)
|
|
|
|
{
|
|
|
|
res.UpdateValue(m_size, m_signed);
|
|
|
|
|
|
|
|
if (res.Filter(m_operator, m_value, m_signed))
|
|
|
|
{
|
|
|
|
res.last_value = res.value;
|
|
|
|
new_results.push_back(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_results.swap(new_results);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryScan::UpdateResultsValues()
|
|
|
|
{
|
|
|
|
for (Result& res : m_results)
|
|
|
|
res.UpdateValue(m_size, m_signed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryScan::SetResultValue(u32 index, u32 value)
|
|
|
|
{
|
|
|
|
if (index >= m_results.size())
|
|
|
|
return;
|
|
|
|
|
|
|
|
Result& res = m_results[index];
|
|
|
|
if (res.value == value)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (m_size)
|
|
|
|
{
|
|
|
|
case MemoryAccessSize::Byte:
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u8>(res.address, Truncate8(value));
|
2020-10-19 15:14:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::HalfWord:
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u16>(res.address, Truncate16(value));
|
2020-10-19 15:14:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::Word:
|
|
|
|
CPU::SafeWriteMemoryWord(res.address, value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
res.value = value;
|
|
|
|
res.value_changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MemoryScan::Result::Filter(Operator op, u32 comp_value, bool is_signed) const
|
|
|
|
{
|
|
|
|
switch (op)
|
|
|
|
{
|
|
|
|
case Operator::Equal:
|
|
|
|
{
|
|
|
|
return (value == comp_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::NotEqual:
|
|
|
|
{
|
|
|
|
return (value != comp_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::GreaterThan:
|
|
|
|
{
|
|
|
|
return is_signed ? (static_cast<s32>(value) > static_cast<s32>(comp_value)) : (value > comp_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::GreaterEqual:
|
|
|
|
{
|
|
|
|
return is_signed ? (static_cast<s32>(value) >= static_cast<s32>(comp_value)) : (value >= comp_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::LessThan:
|
|
|
|
{
|
|
|
|
return is_signed ? (static_cast<s32>(value) < static_cast<s32>(comp_value)) : (value < comp_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::LessEqual:
|
|
|
|
{
|
|
|
|
return is_signed ? (static_cast<s32>(value) <= static_cast<s32>(comp_value)) : (value <= comp_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::IncreasedBy:
|
|
|
|
{
|
|
|
|
return is_signed ? ((static_cast<s32>(value) - static_cast<s32>(last_value)) == static_cast<s32>(comp_value)) :
|
|
|
|
((value - last_value) == comp_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::DecreasedBy:
|
|
|
|
{
|
|
|
|
return is_signed ? ((static_cast<s32>(last_value) - static_cast<s32>(value)) == static_cast<s32>(comp_value)) :
|
|
|
|
((last_value - value) == comp_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::ChangedBy:
|
|
|
|
{
|
|
|
|
if (is_signed)
|
|
|
|
return (std::abs(static_cast<s32>(last_value) - static_cast<s32>(value)) == static_cast<s32>(comp_value));
|
|
|
|
else
|
|
|
|
return ((last_value > value) ? (last_value - value) : (value - last_value)) == comp_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::EqualLast:
|
|
|
|
{
|
|
|
|
return (value == last_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::NotEqualLast:
|
|
|
|
{
|
|
|
|
return (value != last_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::GreaterThanLast:
|
|
|
|
{
|
|
|
|
return is_signed ? (static_cast<s32>(value) > static_cast<s32>(last_value)) : (value > last_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::GreaterEqualLast:
|
|
|
|
{
|
|
|
|
return is_signed ? (static_cast<s32>(value) >= static_cast<s32>(last_value)) : (value >= last_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::LessThanLast:
|
|
|
|
{
|
|
|
|
return is_signed ? (static_cast<s32>(value) < static_cast<s32>(last_value)) : (value < last_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::LessEqualLast:
|
|
|
|
{
|
|
|
|
return is_signed ? (static_cast<s32>(value) <= static_cast<s32>(last_value)) : (value <= last_value);
|
|
|
|
}
|
|
|
|
|
2020-10-21 11:40:17 +00:00
|
|
|
case Operator::Any:
|
|
|
|
return true;
|
|
|
|
|
2020-10-19 15:14:49 +00:00
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryScan::Result::UpdateValue(MemoryAccessSize size, bool is_signed)
|
|
|
|
{
|
|
|
|
const u32 old_value = value;
|
|
|
|
|
|
|
|
switch (size)
|
|
|
|
{
|
|
|
|
case MemoryAccessSize::Byte:
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
u8 bvalue = DoMemoryRead<u8>(address);
|
2020-10-19 15:14:49 +00:00
|
|
|
value = is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::HalfWord:
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
u16 bvalue = DoMemoryRead<u16>(address);
|
2020-10-19 15:14:49 +00:00
|
|
|
value = is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::Word:
|
|
|
|
{
|
|
|
|
CPU::SafeReadMemoryWord(address, &value);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
value_changed = (value != old_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
MemoryWatchList::MemoryWatchList() = default;
|
|
|
|
|
|
|
|
MemoryWatchList::~MemoryWatchList() = default;
|
|
|
|
|
|
|
|
const MemoryWatchList::Entry* MemoryWatchList::GetEntryByAddress(u32 address) const
|
|
|
|
{
|
|
|
|
for (const Entry& entry : m_entries)
|
|
|
|
{
|
|
|
|
if (entry.address == address)
|
|
|
|
return &entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MemoryWatchList::AddEntry(std::string description, u32 address, MemoryAccessSize size, bool is_signed, bool freeze)
|
|
|
|
{
|
|
|
|
if (GetEntryByAddress(address))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
Entry entry;
|
|
|
|
entry.description = std::move(description);
|
|
|
|
entry.address = address;
|
|
|
|
entry.size = size;
|
|
|
|
entry.is_signed = is_signed;
|
|
|
|
entry.freeze = false;
|
|
|
|
|
|
|
|
UpdateEntryValue(&entry);
|
|
|
|
|
|
|
|
entry.changed = false;
|
|
|
|
entry.freeze = freeze;
|
|
|
|
|
|
|
|
m_entries.push_back(std::move(entry));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryWatchList::RemoveEntry(u32 index)
|
|
|
|
{
|
|
|
|
if (index >= m_entries.size())
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_entries.erase(m_entries.begin() + index);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MemoryWatchList::RemoveEntryByAddress(u32 address)
|
|
|
|
{
|
|
|
|
for (auto it = m_entries.begin(); it != m_entries.end(); ++it)
|
|
|
|
{
|
|
|
|
if (it->address == address)
|
|
|
|
{
|
|
|
|
m_entries.erase(it);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryWatchList::SetEntryDescription(u32 index, std::string description)
|
|
|
|
{
|
|
|
|
if (index >= m_entries.size())
|
|
|
|
return;
|
|
|
|
|
|
|
|
Entry& entry = m_entries[index];
|
|
|
|
entry.description = std::move(description);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryWatchList::SetEntryFreeze(u32 index, bool freeze)
|
|
|
|
{
|
|
|
|
if (index >= m_entries.size())
|
|
|
|
return;
|
|
|
|
|
|
|
|
Entry& entry = m_entries[index];
|
|
|
|
entry.freeze = freeze;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryWatchList::SetEntryValue(u32 index, u32 value)
|
|
|
|
{
|
|
|
|
if (index >= m_entries.size())
|
|
|
|
return;
|
|
|
|
|
|
|
|
Entry& entry = m_entries[index];
|
|
|
|
if (entry.value == value)
|
|
|
|
return;
|
|
|
|
|
|
|
|
SetEntryValue(&entry, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MemoryWatchList::RemoveEntryByDescription(const char* description)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
for (auto it = m_entries.begin(); it != m_entries.end();)
|
|
|
|
{
|
|
|
|
if (it->description == description)
|
|
|
|
{
|
|
|
|
it = m_entries.erase(it);
|
|
|
|
result = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryWatchList::UpdateValues()
|
|
|
|
{
|
|
|
|
for (Entry& entry : m_entries)
|
|
|
|
UpdateEntryValue(&entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryWatchList::SetEntryValue(Entry* entry, u32 value)
|
|
|
|
{
|
|
|
|
switch (entry->size)
|
|
|
|
{
|
|
|
|
case MemoryAccessSize::Byte:
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u8>(entry->address, Truncate8(value));
|
2020-10-19 15:14:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::HalfWord:
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u16>(entry->address, Truncate16(value));
|
2020-10-19 15:14:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::Word:
|
2020-10-21 12:01:50 +00:00
|
|
|
DoMemoryWrite<u32>(entry->address, value);
|
2020-10-19 15:14:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry->changed = (entry->value != value);
|
|
|
|
entry->value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryWatchList::UpdateEntryValue(Entry* entry)
|
|
|
|
{
|
|
|
|
const u32 old_value = entry->value;
|
|
|
|
|
|
|
|
switch (entry->size)
|
|
|
|
{
|
|
|
|
case MemoryAccessSize::Byte:
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
u8 bvalue = DoMemoryRead<u8>(entry->address);
|
2020-10-19 15:14:49 +00:00
|
|
|
entry->value = entry->is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::HalfWord:
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
u16 bvalue = DoMemoryRead<u16>(entry->address);
|
2020-10-19 15:14:49 +00:00
|
|
|
entry->value = entry->is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MemoryAccessSize::Word:
|
|
|
|
{
|
2020-10-21 12:01:50 +00:00
|
|
|
entry->value = DoMemoryRead<u32>(entry->address);
|
2020-10-19 15:14:49 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry->changed = (old_value != entry->value);
|
|
|
|
|
|
|
|
if (entry->freeze && entry->changed)
|
|
|
|
SetEntryValue(entry, old_value);
|
|
|
|
}
|