mirror of
https://github.com/stenzek/duckstation.git
synced 2026-07-11 01:24:11 +02:00
Cheats: Expand test coverage
This commit is contained in:
@@ -2,11 +2,22 @@
|
|||||||
# SPDX-License-Identifier: CC-BY-NC-ND-4.0 + Packaging Restriction
|
# SPDX-License-Identifier: CC-BY-NC-ND-4.0 + Packaging Restriction
|
||||||
|
|
||||||
add_executable(core-tests
|
add_executable(core-tests
|
||||||
|
../core/cheats.cpp
|
||||||
../core/cpu_disasm.cpp
|
../core/cpu_disasm.cpp
|
||||||
../core/cpu_types.cpp
|
../core/cpu_types.cpp
|
||||||
|
../util/zip_helpers.cpp
|
||||||
|
cheats_tests.cpp
|
||||||
cpu_disasm_tests.cpp
|
cpu_disasm_tests.cpp
|
||||||
stub_cpu.cpp
|
stub_cpu.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(core-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
target_include_directories(core-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||||
target_link_libraries(core-tests PRIVATE common gtest gtest_main)
|
target_link_libraries(core-tests PRIVATE common imgui libzip::zip gtest gtest_main)
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
target_compile_options(core-tests PRIVATE -ffunction-sections -fdata-sections)
|
||||||
|
target_link_options(core-tests PRIVATE -Wl,-dead_strip)
|
||||||
|
elseif(NOT MSVC)
|
||||||
|
target_compile_options(core-tests PRIVATE -ffunction-sections -fdata-sections)
|
||||||
|
target_link_options(core-tests PRIVATE -Wl,--gc-sections)
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
|
#include "core/cheats_private.h"
|
||||||
|
|
||||||
|
#include "common/error.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static Cheats::CheatCode::Metadata MakeMetadata(Cheats::CodeType type,
|
||||||
|
Cheats::CodeActivation activation = Cheats::CodeActivation::EndFrame)
|
||||||
|
{
|
||||||
|
Cheats::CheatCode::Metadata metadata = {};
|
||||||
|
metadata.name = "Test Code";
|
||||||
|
metadata.type = type;
|
||||||
|
metadata.activation = activation;
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ExpectAssemblyInstructions(const Cheats::AssemblyCheatCode& code,
|
||||||
|
std::span<const std::pair<u32, u32>> expected)
|
||||||
|
{
|
||||||
|
ASSERT_EQ(code.GetInstructions().size(), expected.size());
|
||||||
|
for (size_t i = 0; i < expected.size(); i++)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(code.GetInstructions()[i].pc, expected[i].first) << i;
|
||||||
|
EXPECT_EQ(code.GetInstructions()[i].new_value, expected[i].second) << i;
|
||||||
|
EXPECT_EQ(code.GetInstructions()[i].old_value, Cheats::AssemblyCheatCode::UNINITIALIZED_OLD_VALUE) << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(Cheats, ParseCodeInfo)
|
||||||
|
{
|
||||||
|
static constexpr std::string_view input = "# ignored comment\n"
|
||||||
|
"[Group\\Code]\n"
|
||||||
|
"Author = Test Author\n"
|
||||||
|
"Description = Test Description\n"
|
||||||
|
"Type = Assembly\n"
|
||||||
|
"Activation = EndFrame\n"
|
||||||
|
"Option = Easy:0x12\n"
|
||||||
|
"Option = Hard:52\n"
|
||||||
|
"OptionRange = 1:255\n"
|
||||||
|
"DisallowForAchievements = true\n"
|
||||||
|
".org 0x80010000\n"
|
||||||
|
"addiu t0, zero, 1\n";
|
||||||
|
|
||||||
|
Cheats::CodeInfoList codes;
|
||||||
|
Error error;
|
||||||
|
ASSERT_TRUE(Cheats::ImportCodesFromString(&codes, input, Cheats::FileFormat::DuckStation, true, &error))
|
||||||
|
<< error.GetDescription();
|
||||||
|
ASSERT_EQ(codes.size(), 1u);
|
||||||
|
|
||||||
|
const Cheats::CodeInfo& code = codes.front();
|
||||||
|
EXPECT_EQ(code.name, "Group\\Code");
|
||||||
|
EXPECT_EQ(code.GetNameParentPart(), "Group");
|
||||||
|
EXPECT_EQ(code.GetNamePart(), "Code");
|
||||||
|
EXPECT_EQ(code.author, "Test Author");
|
||||||
|
EXPECT_EQ(code.description, "Test Description");
|
||||||
|
EXPECT_EQ(code.type, Cheats::CodeType::Assembly);
|
||||||
|
EXPECT_EQ(code.activation, Cheats::CodeActivation::EndFrame);
|
||||||
|
EXPECT_FALSE(code.from_database);
|
||||||
|
EXPECT_TRUE(code.disallow_for_achievements);
|
||||||
|
ASSERT_EQ(code.options.size(), 2u);
|
||||||
|
EXPECT_EQ(code.options[0], Cheats::CodeOption("Easy", 0x12));
|
||||||
|
EXPECT_EQ(code.options[1], Cheats::CodeOption("Hard", 52));
|
||||||
|
EXPECT_EQ(code.option_range_start, 1);
|
||||||
|
EXPECT_EQ(code.option_range_end, 255);
|
||||||
|
EXPECT_EQ(code.body, ".org 0x80010000\naddiu t0, zero, 1");
|
||||||
|
EXPECT_EQ(input.substr(code.file_offset_start, code.file_offset_body_start - code.file_offset_start),
|
||||||
|
"[Group\\Code]\nAuthor = Test Author\nDescription = Test Description\nType = Assembly\n"
|
||||||
|
"Activation = EndFrame\nOption = Easy:0x12\nOption = Hard:52\nOptionRange = 1:255\n"
|
||||||
|
"DisallowForAchievements = true\n");
|
||||||
|
EXPECT_EQ(input.substr(code.file_offset_body_start, code.file_offset_end - code.file_offset_body_start),
|
||||||
|
".org 0x80010000\naddiu t0, zero, 1\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Cheats, ParseCodeInfoLegacyIgnoreAndReplacement)
|
||||||
|
{
|
||||||
|
static constexpr std::string_view input = "#group=Legacy\n"
|
||||||
|
"#type=Gameshark\n"
|
||||||
|
"#activation=Manual\n"
|
||||||
|
"[Ignored]\n"
|
||||||
|
"Ignore = true\n"
|
||||||
|
"80010000 0001\n"
|
||||||
|
"[Duplicate]\n"
|
||||||
|
"80010000 0002\n"
|
||||||
|
"[Duplicate]\n"
|
||||||
|
"80010000 0003\n";
|
||||||
|
|
||||||
|
Cheats::CodeInfoList codes;
|
||||||
|
Error error;
|
||||||
|
ASSERT_TRUE(Cheats::ImportCodesFromString(&codes, input, Cheats::FileFormat::DuckStation, true, &error))
|
||||||
|
<< error.GetDescription();
|
||||||
|
ASSERT_EQ(codes.size(), 1u);
|
||||||
|
EXPECT_EQ(codes[0].name, "Legacy\\Duplicate");
|
||||||
|
EXPECT_EQ(codes[0].type, Cheats::CodeType::Gameshark);
|
||||||
|
EXPECT_EQ(codes[0].activation, Cheats::CodeActivation::Manual);
|
||||||
|
EXPECT_EQ(codes[0].body, "80010000 0003");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Cheats, ParseCodeInfoErrors)
|
||||||
|
{
|
||||||
|
Cheats::CodeInfoList codes;
|
||||||
|
Error error;
|
||||||
|
EXPECT_FALSE(Cheats::ImportCodesFromString(&codes, "[Code]\nOptionRange = 10:1\n80010000 0001\n",
|
||||||
|
Cheats::FileFormat::DuckStation, true, &error));
|
||||||
|
EXPECT_TRUE(error.IsValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Cheats, ParseGamesharkCode)
|
||||||
|
{
|
||||||
|
static constexpr std::string_view input = "30000010 00000012\n" // ConstantWrite8
|
||||||
|
"80000020 00003456\n" // ConstantWrite16
|
||||||
|
"90000030 89ABCDEF\n" // ExtConstantWrite32
|
||||||
|
"10000040 00000001\n" // Increment16
|
||||||
|
"11000050 00000002\n" // Decrement16
|
||||||
|
"20000060 00000003\n" // Increment8
|
||||||
|
"21000070 00000004\n" // Decrement8
|
||||||
|
"60000080 00000005\n" // ExtIncrement32
|
||||||
|
"61000090 00000006\n" // ExtDecrement32
|
||||||
|
"D00000A0 00001111\n" // CompareEqual16
|
||||||
|
"D10000B0 00002222\n" // CompareNotEqual16
|
||||||
|
"D20000C0 00003333\n" // CompareLess16
|
||||||
|
"D30000D0 00004444\n" // CompareGreater16
|
||||||
|
"E00000E0 00000055\n" // CompareEqual8
|
||||||
|
"E10000F0 00000066\n" // CompareNotEqual8
|
||||||
|
"A0000100 12345678\n" // ExtCompareEqual32
|
||||||
|
"A1010110 87654321\n" // ExtCompareNotEqual32
|
||||||
|
"50000203 00040005\n" // Slide
|
||||||
|
"C2000120 00000130\n" // MemoryCopy
|
||||||
|
"53000140 01020304\n" // ExtImprovedSlide
|
||||||
|
"31000150 00000080\n" // ExtConstantBitSet8
|
||||||
|
"82000160 00004000\n" // ExtConstantBitClear16
|
||||||
|
"91000170 DEADBEEF\n" // ExtConstantBitSet32
|
||||||
|
"F4000180 00000190\n" // ExtFindAndReplace
|
||||||
|
"51000001 00000002\n" // ExtCheatRegisters
|
||||||
|
"52000003 00000004\n"; // ExtCheatRegistersCompare
|
||||||
|
static constexpr std::array expected = {
|
||||||
|
std::pair{0x30000010u, 0x00000012u}, std::pair{0x80000020u, 0x00003456u}, std::pair{0x90000030u, 0x89ABCDEFu},
|
||||||
|
std::pair{0x10000040u, 0x00000001u}, std::pair{0x11000050u, 0x00000002u}, std::pair{0x20000060u, 0x00000003u},
|
||||||
|
std::pair{0x21000070u, 0x00000004u}, std::pair{0x60000080u, 0x00000005u}, std::pair{0x61000090u, 0x00000006u},
|
||||||
|
std::pair{0xD00000A0u, 0x00001111u}, std::pair{0xD10000B0u, 0x00002222u}, std::pair{0xD20000C0u, 0x00003333u},
|
||||||
|
std::pair{0xD30000D0u, 0x00004444u}, std::pair{0xE00000E0u, 0x00000055u}, std::pair{0xE10000F0u, 0x00000066u},
|
||||||
|
std::pair{0xA0000100u, 0x12345678u}, std::pair{0xA1010110u, 0x87654321u}, std::pair{0x50000203u, 0x00040005u},
|
||||||
|
std::pair{0xC2000120u, 0x00000130u}, std::pair{0x53000140u, 0x01020304u}, std::pair{0x31000150u, 0x00000080u},
|
||||||
|
std::pair{0x82000160u, 0x00004000u}, std::pair{0x91000170u, 0xDEADBEEFu}, std::pair{0xF4000180u, 0x00000190u},
|
||||||
|
std::pair{0x51000001u, 0x00000002u}, std::pair{0x52000003u, 0x00000004u},
|
||||||
|
};
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
std::unique_ptr<Cheats::CheatCode> base = Cheats::ParseCode(MakeMetadata(Cheats::CodeType::Gameshark), input, &error);
|
||||||
|
ASSERT_NE(base, nullptr) << error.GetDescription();
|
||||||
|
Cheats::GamesharkCheatCode* code = static_cast<Cheats::GamesharkCheatCode*>(base.get());
|
||||||
|
ASSERT_EQ(code->GetInstructions().size(), expected.size());
|
||||||
|
for (size_t i = 0; i < expected.size(); i++)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(code->GetInstructions()[i].first, expected[i].first) << i;
|
||||||
|
EXPECT_EQ(code->GetInstructions()[i].second, expected[i].second) << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Cheats, ParseGamesharkOptions)
|
||||||
|
{
|
||||||
|
Cheats::CheatCode::Metadata metadata = MakeMetadata(Cheats::CodeType::Gameshark);
|
||||||
|
metadata.has_options = true;
|
||||||
|
Error error;
|
||||||
|
std::unique_ptr<Cheats::CheatCode> base =
|
||||||
|
Cheats::ParseCode(std::move(metadata), "30000010 000000??\n80000020 00??0000\n90000030 ????????\n", &error);
|
||||||
|
ASSERT_NE(base, nullptr) << error.GetDescription();
|
||||||
|
Cheats::GamesharkCheatCode* code = static_cast<Cheats::GamesharkCheatCode*>(base.get());
|
||||||
|
|
||||||
|
code->SetOptionValue(0x89ABCDEF);
|
||||||
|
ASSERT_EQ(code->GetInstructions().size(), 3u);
|
||||||
|
EXPECT_EQ(code->GetInstructions()[0].second, 0x000000EFu);
|
||||||
|
EXPECT_EQ(code->GetInstructions()[1].second, 0x00EF0000u);
|
||||||
|
EXPECT_EQ(code->GetInstructions()[2].second, 0x89ABCDEFu);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Cheats, ParseGamesharkCodeErrors)
|
||||||
|
{
|
||||||
|
static constexpr std::array<std::string_view, 4> cases = {
|
||||||
|
"",
|
||||||
|
"80010000",
|
||||||
|
"80010000 zzzzzzzz",
|
||||||
|
"80010000 00??00??",
|
||||||
|
};
|
||||||
|
for (const std::string_view input : cases)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
EXPECT_EQ(Cheats::ParseCode(MakeMetadata(Cheats::CodeType::Gameshark), input, &error), nullptr) << input;
|
||||||
|
EXPECT_TRUE(error.IsValid()) << input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Cheats, ParseAssemblyCode)
|
||||||
|
{
|
||||||
|
Cheats::CheatCode::Metadata metadata = MakeMetadata(Cheats::CodeType::Assembly);
|
||||||
|
metadata.has_options = true;
|
||||||
|
static constexpr std::string_view input = ".org 0x80010000\n"
|
||||||
|
"start: addiu t0, zero, 1\n"
|
||||||
|
"beq t0, zero, target\n"
|
||||||
|
"j start\n"
|
||||||
|
"nop\n"
|
||||||
|
"target: ori t1, zero, 0x????\n";
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
std::unique_ptr<Cheats::CheatCode> base = Cheats::ParseCode(std::move(metadata), input, &error);
|
||||||
|
ASSERT_NE(base, nullptr) << error.GetDescription();
|
||||||
|
Cheats::AssemblyCheatCode* code = static_cast<Cheats::AssemblyCheatCode*>(base.get());
|
||||||
|
static constexpr std::array expected = {
|
||||||
|
std::pair{0x80010000u, 0x24080001u}, std::pair{0x80010004u, 0x11000002u}, std::pair{0x80010008u, 0x08004000u},
|
||||||
|
std::pair{0x8001000Cu, 0x00000000u}, std::pair{0x80010010u, 0x34090000u},
|
||||||
|
};
|
||||||
|
ExpectAssemblyInstructions(*code, expected);
|
||||||
|
|
||||||
|
code->SetOptionValue(0x1234);
|
||||||
|
EXPECT_EQ(code->GetInstructions().back().new_value, 0x34091234u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Cheats, ParseAssemblyInstructionFormatsAndOrigins)
|
||||||
|
{
|
||||||
|
static constexpr std::string_view input = "; first section\n"
|
||||||
|
".org 0x80020000\n"
|
||||||
|
"lui t0, 0x1234\n"
|
||||||
|
"ori t0, t0, 0x5678 # construct constant\n"
|
||||||
|
"sw t0, 16(sp)\n"
|
||||||
|
"lw v0, -4(sp)\n"
|
||||||
|
"addu v1, v0, t0\n"
|
||||||
|
".org 0x80030000\n"
|
||||||
|
"jal 0x80031000\n"
|
||||||
|
"jr ra\n";
|
||||||
|
static constexpr std::array expected = {
|
||||||
|
std::pair{0x80020000u, 0x3C081234u}, std::pair{0x80020004u, 0x35085678u}, std::pair{0x80020008u, 0xAFA80010u},
|
||||||
|
std::pair{0x8002000Cu, 0x8FA2FFFCu}, std::pair{0x80020010u, 0x00481821u}, std::pair{0x80030000u, 0x0C00C400u},
|
||||||
|
std::pair{0x80030004u, 0x03E00008u},
|
||||||
|
};
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
std::unique_ptr<Cheats::CheatCode> base = Cheats::ParseCode(MakeMetadata(Cheats::CodeType::Assembly), input, &error);
|
||||||
|
ASSERT_NE(base, nullptr) << error.GetDescription();
|
||||||
|
ExpectAssemblyInstructions(*static_cast<Cheats::AssemblyCheatCode*>(base.get()), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Cheats, ParseAssemblyBranchTypes)
|
||||||
|
{
|
||||||
|
static constexpr std::string_view input = ".org 0x80040000\n"
|
||||||
|
"target: nop\n"
|
||||||
|
"beq t0, t1, target\n"
|
||||||
|
"bne t0, t1, target\n"
|
||||||
|
"blez t0, target\n"
|
||||||
|
"bgtz t0, target\n"
|
||||||
|
"bltz t0, target\n"
|
||||||
|
"bgez t0, target\n"
|
||||||
|
"bltzal t0, target\n"
|
||||||
|
"bgezal t0, target\n";
|
||||||
|
static constexpr std::array expected = {
|
||||||
|
std::pair{0x80040000u, 0x00000000u}, std::pair{0x80040004u, 0x1109FFFEu}, std::pair{0x80040008u, 0x1509FFFDu},
|
||||||
|
std::pair{0x8004000Cu, 0x1900FFFCu}, std::pair{0x80040010u, 0x1D00FFFBu}, std::pair{0x80040014u, 0x0500FFFAu},
|
||||||
|
std::pair{0x80040018u, 0x0501FFF9u}, std::pair{0x8004001Cu, 0x0510FFF8u}, std::pair{0x80040020u, 0x0511FFF7u},
|
||||||
|
};
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
std::unique_ptr<Cheats::CheatCode> base = Cheats::ParseCode(MakeMetadata(Cheats::CodeType::Assembly), input, &error);
|
||||||
|
ASSERT_NE(base, nullptr) << error.GetDescription();
|
||||||
|
ExpectAssemblyInstructions(*static_cast<Cheats::AssemblyCheatCode*>(base.get()), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Cheats, ParseAssemblyCodeErrors)
|
||||||
|
{
|
||||||
|
static constexpr std::array<std::string_view, 8> cases = {
|
||||||
|
"nop\n",
|
||||||
|
".org 0x80010002\nnop\n",
|
||||||
|
".org nope\nnop\n",
|
||||||
|
".org 0x80010000\nbeq zero, zero, missing\n",
|
||||||
|
".org 0x80010000\nlabel: nop\nlabel: nop\n",
|
||||||
|
".org 0x80010000\nnop\n.org 0x80010000\nnop\n",
|
||||||
|
".org 0x80010000\nori t0, zero, 0x??0?\n",
|
||||||
|
".org 0x80010000\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const std::string_view input : cases)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
EXPECT_EQ(Cheats::ParseCode(MakeMetadata(Cheats::CodeType::Assembly), input, &error), nullptr) << input;
|
||||||
|
EXPECT_TRUE(error.IsValid()) << input;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,11 @@
|
|||||||
<Import Project="..\..\dep\vsprops\Configurations.props" />
|
<Import Project="..\..\dep\vsprops\Configurations.props" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
|
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
|
||||||
|
<ClCompile Include="..\core\cheats.cpp" />
|
||||||
<ClCompile Include="..\core\cpu_disasm.cpp" />
|
<ClCompile Include="..\core\cpu_disasm.cpp" />
|
||||||
<ClCompile Include="..\core\cpu_types.cpp" />
|
<ClCompile Include="..\core\cpu_types.cpp" />
|
||||||
|
<ClCompile Include="..\util\zip_helpers.cpp" />
|
||||||
|
<ClCompile Include="cheats_tests.cpp" />
|
||||||
<ClCompile Include="cpu_disasm_tests.cpp" />
|
<ClCompile Include="cpu_disasm_tests.cpp" />
|
||||||
<ClCompile Include="stub_cpu.cpp" />
|
<ClCompile Include="stub_cpu.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -12,6 +15,9 @@
|
|||||||
<ProjectReference Include="..\..\dep\googletest\googletest.vcxproj">
|
<ProjectReference Include="..\..\dep\googletest\googletest.vcxproj">
|
||||||
<Project>{49953e1b-2ef7-46a4-b88b-1bf9e099093b}</Project>
|
<Project>{49953e1b-2ef7-46a4-b88b-1bf9e099093b}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
||||||
|
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\common\common.vcxproj">
|
<ProjectReference Include="..\common\common.vcxproj">
|
||||||
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
|
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
@@ -20,13 +26,15 @@
|
|||||||
<ProjectGuid>{93D9300C-B64A-4B21-A4B5-30173DE88747}</ProjectGuid>
|
<ProjectGuid>{93D9300C-B64A-4B21-A4B5-30173DE88747}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="..\..\dep\vsprops\ConsoleApplication.props" />
|
<Import Project="..\..\dep\vsprops\ConsoleApplication.props" />
|
||||||
<Import Project="..\common\common.props" />
|
<Import Project="..\util\util.props" />
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\googletest\include</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\googletest\include</AdditionalIncludeDirectories>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Console</SubSystem>
|
<SubSystem>Console</SubSystem>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<Import Project="..\..\dep\vsprops\Targets.props" />
|
<Import Project="..\..\dep\vsprops\Targets.props" />
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
|
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
|
||||||
|
<ClCompile Include="..\core\cheats.cpp" />
|
||||||
<ClCompile Include="..\core\cpu_disasm.cpp" />
|
<ClCompile Include="..\core\cpu_disasm.cpp" />
|
||||||
<ClCompile Include="..\core\cpu_types.cpp" />
|
<ClCompile Include="..\core\cpu_types.cpp" />
|
||||||
|
<ClCompile Include="..\util\zip_helpers.cpp" />
|
||||||
|
<ClCompile Include="cheats_tests.cpp" />
|
||||||
<ClCompile Include="cpu_disasm_tests.cpp" />
|
<ClCompile Include="cpu_disasm_tests.cpp" />
|
||||||
<ClCompile Include="stub_cpu.cpp" />
|
<ClCompile Include="stub_cpu.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
#include "core/cpu_core.h"
|
#include "core/cpu_core.h"
|
||||||
|
#include "core/system.h"
|
||||||
|
|
||||||
CPU::State CPU::g_state;
|
CPU::State CPU::g_state;
|
||||||
|
|
||||||
@@ -25,3 +26,32 @@ bool CPU::SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value)
|
|||||||
*value = 0;
|
*value = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::InvalidateICacheAt(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 System::GetFrameNumber()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller* System::GetController(u32 slot)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ add_library(core
|
|||||||
cdrom_subq_replacement.h
|
cdrom_subq_replacement.h
|
||||||
cheats.cpp
|
cheats.cpp
|
||||||
cheats.h
|
cheats.h
|
||||||
|
cheats_private.h
|
||||||
controller.cpp
|
controller.cpp
|
||||||
controller.h
|
controller.h
|
||||||
controller_helpers.h
|
controller_helpers.h
|
||||||
|
|||||||
+6
-197
@@ -4,6 +4,7 @@
|
|||||||
#include "cheats.h"
|
#include "cheats.h"
|
||||||
#include "achievements.h"
|
#include "achievements.h"
|
||||||
#include "bus.h"
|
#include "bus.h"
|
||||||
|
#include "cheats_private.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "cpu_core.h"
|
#include "cpu_core.h"
|
||||||
@@ -162,50 +163,6 @@ private:
|
|||||||
|
|
||||||
namespace Cheats {
|
namespace Cheats {
|
||||||
|
|
||||||
namespace {
|
|
||||||
/// Represents a cheat code, after being parsed.
|
|
||||||
class CheatCode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/// Additional metadata to a cheat code, present for all types.
|
|
||||||
struct Metadata
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
CodeType type = CodeType::Gameshark;
|
|
||||||
CodeActivation activation = CodeActivation::EndFrame;
|
|
||||||
std::optional<u32> override_cpu_overclock;
|
|
||||||
std::optional<DisplayAspectRatio> override_aspect_ratio;
|
|
||||||
bool has_options : 1;
|
|
||||||
bool disable_widescreen_rendering : 1;
|
|
||||||
bool enable_8mb_ram : 1;
|
|
||||||
bool disallow_for_achievements : 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit CheatCode(Metadata metadata);
|
|
||||||
virtual ~CheatCode();
|
|
||||||
|
|
||||||
ALWAYS_INLINE const Metadata& GetMetadata() const { return m_metadata; }
|
|
||||||
ALWAYS_INLINE const std::string& GetName() const { return m_metadata.name; }
|
|
||||||
ALWAYS_INLINE CodeActivation GetActivation() const { return m_metadata.activation; }
|
|
||||||
ALWAYS_INLINE bool IsManuallyActivated() const { return (m_metadata.activation == CodeActivation::Manual); }
|
|
||||||
ALWAYS_INLINE bool HasOptions() const { return m_metadata.has_options; }
|
|
||||||
|
|
||||||
bool HasAnySettingOverrides() const;
|
|
||||||
void ApplySettingOverrides();
|
|
||||||
|
|
||||||
virtual void SetOptionValue(u32 value) = 0;
|
|
||||||
|
|
||||||
virtual void Apply() const = 0;
|
|
||||||
virtual void ApplyOnDisable(RollbackLog* rollback_list) const = 0;
|
|
||||||
|
|
||||||
virtual bool HasRestorableOnDisableEffects() const = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Metadata m_metadata;
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
using CheatCodeList = std::vector<std::unique_ptr<CheatCode>>;
|
using CheatCodeList = std::vector<std::unique_ptr<CheatCode>>;
|
||||||
using ActiveCodeList = std::vector<const CheatCode*>;
|
using ActiveCodeList = std::vector<const CheatCode*>;
|
||||||
using EnableCodeList = std::vector<std::string>;
|
using EnableCodeList = std::vector<std::string>;
|
||||||
@@ -238,7 +195,6 @@ static void EnumerateChtFiles(const std::string_view serial, std::optional<GameH
|
|||||||
static std::optional<CodeOption> ParseOption(const std::string_view value);
|
static std::optional<CodeOption> ParseOption(const std::string_view value);
|
||||||
static bool ParseOptionRange(const std::string_view value, u16* out_range_start, u16* out_range_end);
|
static bool ParseOptionRange(const std::string_view value, u16* out_range_start, u16* out_range_end);
|
||||||
static void ParseFile(CheatCodeList* dst_list, const std::string_view file_contents);
|
static void ParseFile(CheatCodeList* dst_list, const std::string_view file_contents);
|
||||||
static std::unique_ptr<CheatCode> ParseCode(CheatCode::Metadata metadata, const std::string_view data, Error* error);
|
|
||||||
|
|
||||||
static Cheats::FileFormat DetectFileFormat(const std::string_view file_contents);
|
static Cheats::FileFormat DetectFileFormat(const std::string_view file_contents);
|
||||||
static bool ImportPCSXFile(CodeInfoList* dst, const std::string_view file_contents, bool stop_on_error, Error* error);
|
static bool ImportPCSXFile(CodeInfoList* dst, const std::string_view file_contents, bool stop_on_error, Error* error);
|
||||||
@@ -2174,122 +2130,6 @@ bool Cheats::ImportOldChtFile(const std::string_view serial)
|
|||||||
// Gameshark codes
|
// Gameshark codes
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
namespace Cheats {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class GamesharkCheatCode final : public CheatCode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit GamesharkCheatCode(Metadata metadata);
|
|
||||||
~GamesharkCheatCode() override;
|
|
||||||
|
|
||||||
static std::unique_ptr<GamesharkCheatCode> Parse(Metadata metadata, const std::string_view data, Error* error);
|
|
||||||
|
|
||||||
void SetOptionValue(u32 value) override;
|
|
||||||
|
|
||||||
void Apply() const override;
|
|
||||||
void ApplyOnDisable(RollbackLog* rollback_list) const override;
|
|
||||||
|
|
||||||
bool HasRestorableOnDisableEffects() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum class InstructionCode : u8
|
|
||||||
{
|
|
||||||
Nop = 0x00,
|
|
||||||
ConstantWrite8 = 0x30,
|
|
||||||
ConstantWrite16 = 0x80,
|
|
||||||
ScratchpadWrite16 = 0x1F,
|
|
||||||
Increment16 = 0x10,
|
|
||||||
Decrement16 = 0x11,
|
|
||||||
Increment8 = 0x20,
|
|
||||||
Decrement8 = 0x21,
|
|
||||||
DelayActivation = 0xC1,
|
|
||||||
SkipIfNotEqual16 = 0xC0,
|
|
||||||
SkipIfButtonsNotEqual = 0xD5,
|
|
||||||
SkipIfButtonsEqual = 0xD6,
|
|
||||||
CompareButtons = 0xD4,
|
|
||||||
CompareEqual16 = 0xD0,
|
|
||||||
CompareNotEqual16 = 0xD1,
|
|
||||||
CompareLess16 = 0xD2,
|
|
||||||
CompareGreater16 = 0xD3,
|
|
||||||
CompareEqual8 = 0xE0,
|
|
||||||
CompareNotEqual8 = 0xE1,
|
|
||||||
CompareLess8 = 0xE2,
|
|
||||||
CompareGreater8 = 0xE3,
|
|
||||||
Slide = 0x50,
|
|
||||||
MemoryCopy = 0xC2,
|
|
||||||
ExtImprovedSlide = 0x53,
|
|
||||||
|
|
||||||
// Extension opcodes, not present on original GameShark.
|
|
||||||
ExtConstantWrite32 = 0x90,
|
|
||||||
ExtScratchpadWrite32 = 0xA5,
|
|
||||||
ExtCompareEqual32 = 0xA0,
|
|
||||||
ExtCompareNotEqual32 = 0xA1,
|
|
||||||
ExtCompareLess32 = 0xA2,
|
|
||||||
ExtCompareGreater32 = 0xA3,
|
|
||||||
ExtSkipIfNotEqual32 = 0xA4,
|
|
||||||
ExtIncrement32 = 0x60,
|
|
||||||
ExtDecrement32 = 0x61,
|
|
||||||
ExtConstantWriteIfMatch16 = 0xA6,
|
|
||||||
ExtConstantWriteIfMatchWithRestore16 = 0xA7,
|
|
||||||
ExtConstantWriteIfMatchWithRestore8 = 0xA8,
|
|
||||||
ExtConstantForceRange8 = 0xF0,
|
|
||||||
ExtConstantForceRangeLimits16 = 0xF1,
|
|
||||||
ExtConstantForceRangeRollRound16 = 0xF2,
|
|
||||||
ExtConstantForceRange16 = 0xF3,
|
|
||||||
ExtFindAndReplace = 0xF4,
|
|
||||||
ExtConstantSwap16 = 0xF5,
|
|
||||||
|
|
||||||
ExtConstantBitSet8 = 0x31,
|
|
||||||
ExtConstantBitClear8 = 0x32,
|
|
||||||
ExtConstantBitSet16 = 0x81,
|
|
||||||
ExtConstantBitClear16 = 0x82,
|
|
||||||
ExtConstantBitSet32 = 0x91,
|
|
||||||
ExtConstantBitClear32 = 0x92,
|
|
||||||
|
|
||||||
ExtBitCompareButtons = 0xD7,
|
|
||||||
ExtSkipIfNotLess8 = 0xC3,
|
|
||||||
ExtSkipIfNotGreater8 = 0xC4,
|
|
||||||
ExtSkipIfNotLess16 = 0xC5,
|
|
||||||
ExtSkipIfNotGreater16 = 0xC6,
|
|
||||||
ExtMultiConditionals = 0xF6,
|
|
||||||
|
|
||||||
ExtCheatRegisters = 0x51,
|
|
||||||
ExtCheatRegistersCompare = 0x52,
|
|
||||||
|
|
||||||
ExtCompareBitsSet8 = 0xE4, // Only used inside ExtMultiConditionals
|
|
||||||
ExtCompareBitsClear8 = 0xE5, // Only used inside ExtMultiConditionals
|
|
||||||
};
|
|
||||||
|
|
||||||
union Instruction
|
|
||||||
{
|
|
||||||
u64 bits;
|
|
||||||
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
u32 second;
|
|
||||||
u32 first;
|
|
||||||
};
|
|
||||||
|
|
||||||
BitField<u64, InstructionCode, 32 + 24, 8> code;
|
|
||||||
BitField<u64, u32, 32, 24> address;
|
|
||||||
BitField<u64, u32, 0, 32> value32;
|
|
||||||
BitField<u64, u16, 0, 16> value16;
|
|
||||||
BitField<u64, u8, 0, 8> value8;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Instruction> instructions;
|
|
||||||
std::vector<std::tuple<u32, u8, u8>> option_instruction_values;
|
|
||||||
|
|
||||||
u32 GetNextNonConditionalInstruction(u32 index) const;
|
|
||||||
|
|
||||||
static bool IsConditionalInstruction(InstructionCode code);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
} // namespace Cheats
|
|
||||||
|
|
||||||
Cheats::GamesharkCheatCode::GamesharkCheatCode(Metadata metadata) : CheatCode(std::move(metadata))
|
Cheats::GamesharkCheatCode::GamesharkCheatCode(Metadata metadata) : CheatCode(std::move(metadata))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -4565,42 +4405,6 @@ bool Cheats::GamesharkCheatCode::HasRestorableOnDisableEffects() const
|
|||||||
// Assembly codes
|
// Assembly codes
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
namespace Cheats {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class AssemblyCheatCode final : public CheatCode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit AssemblyCheatCode(Metadata metadata);
|
|
||||||
~AssemblyCheatCode() override;
|
|
||||||
|
|
||||||
static std::unique_ptr<AssemblyCheatCode> Parse(Metadata metadata, std::string_view data, Error* error);
|
|
||||||
|
|
||||||
void SetOptionValue(u32 value) override;
|
|
||||||
|
|
||||||
void Apply() const override;
|
|
||||||
void ApplyOnDisable(RollbackLog* rollback_list) const override;
|
|
||||||
|
|
||||||
bool HasRestorableOnDisableEffects() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr u64 UNINITIALIZED_OLD_VALUE = UINT64_C(0xFFFFFFFFFFFFFFFF);
|
|
||||||
|
|
||||||
struct Instruction
|
|
||||||
{
|
|
||||||
u32 pc;
|
|
||||||
u32 new_value;
|
|
||||||
mutable u64 old_value; // higher order bits are set so fresh codes apply
|
|
||||||
};
|
|
||||||
static_assert(std::is_trivially_copyable_v<Instruction>);
|
|
||||||
|
|
||||||
std::vector<Instruction> instructions;
|
|
||||||
std::vector<std::tuple<u32, u8, u8>> option_instruction_values;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
} // namespace Cheats
|
|
||||||
|
|
||||||
Cheats::AssemblyCheatCode::AssemblyCheatCode(Metadata metadata) : CheatCode(std::move(metadata))
|
Cheats::AssemblyCheatCode::AssemblyCheatCode(Metadata metadata) : CheatCode(std::move(metadata))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -4795,6 +4599,11 @@ std::unique_ptr<Cheats::AssemblyCheatCode> Cheats::AssemblyCheatCode::Parse(Meta
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::span<const Cheats::AssemblyCheatCode::Instruction> Cheats::AssemblyCheatCode::GetInstructions() const
|
||||||
|
{
|
||||||
|
return instructions;
|
||||||
|
}
|
||||||
|
|
||||||
void Cheats::AssemblyCheatCode::SetOptionValue(u32 value)
|
void Cheats::AssemblyCheatCode::SetOptionValue(u32 value)
|
||||||
{
|
{
|
||||||
for (const auto& [index, bit_position, bit_count] : option_instruction_values)
|
for (const auto& [index, bit_position, bit_count] : option_instruction_values)
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
|
||||||
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cheats.h"
|
||||||
|
|
||||||
|
#include "common/bitfield.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace Cheats {
|
||||||
|
|
||||||
|
/// Represents a cheat code, after being parsed.
|
||||||
|
class CheatCode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Additional metadata to a cheat code, present for all types.
|
||||||
|
struct Metadata
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
CodeType type = CodeType::Gameshark;
|
||||||
|
CodeActivation activation = CodeActivation::EndFrame;
|
||||||
|
std::optional<u32> override_cpu_overclock;
|
||||||
|
std::optional<DisplayAspectRatio> override_aspect_ratio;
|
||||||
|
bool has_options : 1;
|
||||||
|
bool disable_widescreen_rendering : 1;
|
||||||
|
bool enable_8mb_ram : 1;
|
||||||
|
bool disallow_for_achievements : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CheatCode(Metadata metadata);
|
||||||
|
virtual ~CheatCode();
|
||||||
|
|
||||||
|
ALWAYS_INLINE const Metadata& GetMetadata() const { return m_metadata; }
|
||||||
|
ALWAYS_INLINE const std::string& GetName() const { return m_metadata.name; }
|
||||||
|
ALWAYS_INLINE CodeActivation GetActivation() const { return m_metadata.activation; }
|
||||||
|
ALWAYS_INLINE bool IsManuallyActivated() const { return (m_metadata.activation == CodeActivation::Manual); }
|
||||||
|
ALWAYS_INLINE bool HasOptions() const { return m_metadata.has_options; }
|
||||||
|
|
||||||
|
bool HasAnySettingOverrides() const;
|
||||||
|
void ApplySettingOverrides();
|
||||||
|
|
||||||
|
virtual void SetOptionValue(u32 value) = 0;
|
||||||
|
|
||||||
|
virtual void Apply() const = 0;
|
||||||
|
virtual void ApplyOnDisable(RollbackLog* rollback_list) const = 0;
|
||||||
|
|
||||||
|
virtual bool HasRestorableOnDisableEffects() const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Metadata m_metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GamesharkCheatCode final : public CheatCode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class InstructionCode : u8
|
||||||
|
{
|
||||||
|
Nop = 0x00,
|
||||||
|
ConstantWrite8 = 0x30,
|
||||||
|
ConstantWrite16 = 0x80,
|
||||||
|
ScratchpadWrite16 = 0x1F,
|
||||||
|
Increment16 = 0x10,
|
||||||
|
Decrement16 = 0x11,
|
||||||
|
Increment8 = 0x20,
|
||||||
|
Decrement8 = 0x21,
|
||||||
|
DelayActivation = 0xC1,
|
||||||
|
SkipIfNotEqual16 = 0xC0,
|
||||||
|
SkipIfButtonsNotEqual = 0xD5,
|
||||||
|
SkipIfButtonsEqual = 0xD6,
|
||||||
|
CompareButtons = 0xD4,
|
||||||
|
CompareEqual16 = 0xD0,
|
||||||
|
CompareNotEqual16 = 0xD1,
|
||||||
|
CompareLess16 = 0xD2,
|
||||||
|
CompareGreater16 = 0xD3,
|
||||||
|
CompareEqual8 = 0xE0,
|
||||||
|
CompareNotEqual8 = 0xE1,
|
||||||
|
CompareLess8 = 0xE2,
|
||||||
|
CompareGreater8 = 0xE3,
|
||||||
|
Slide = 0x50,
|
||||||
|
MemoryCopy = 0xC2,
|
||||||
|
ExtImprovedSlide = 0x53,
|
||||||
|
|
||||||
|
// Extension opcodes, not present on original GameShark.
|
||||||
|
ExtConstantWrite32 = 0x90,
|
||||||
|
ExtScratchpadWrite32 = 0xA5,
|
||||||
|
ExtCompareEqual32 = 0xA0,
|
||||||
|
ExtCompareNotEqual32 = 0xA1,
|
||||||
|
ExtCompareLess32 = 0xA2,
|
||||||
|
ExtCompareGreater32 = 0xA3,
|
||||||
|
ExtSkipIfNotEqual32 = 0xA4,
|
||||||
|
ExtIncrement32 = 0x60,
|
||||||
|
ExtDecrement32 = 0x61,
|
||||||
|
ExtConstantWriteIfMatch16 = 0xA6,
|
||||||
|
ExtConstantWriteIfMatchWithRestore16 = 0xA7,
|
||||||
|
ExtConstantWriteIfMatchWithRestore8 = 0xA8,
|
||||||
|
ExtConstantForceRange8 = 0xF0,
|
||||||
|
ExtConstantForceRangeLimits16 = 0xF1,
|
||||||
|
ExtConstantForceRangeRollRound16 = 0xF2,
|
||||||
|
ExtConstantForceRange16 = 0xF3,
|
||||||
|
ExtFindAndReplace = 0xF4,
|
||||||
|
ExtConstantSwap16 = 0xF5,
|
||||||
|
|
||||||
|
ExtConstantBitSet8 = 0x31,
|
||||||
|
ExtConstantBitClear8 = 0x32,
|
||||||
|
ExtConstantBitSet16 = 0x81,
|
||||||
|
ExtConstantBitClear16 = 0x82,
|
||||||
|
ExtConstantBitSet32 = 0x91,
|
||||||
|
ExtConstantBitClear32 = 0x92,
|
||||||
|
|
||||||
|
ExtBitCompareButtons = 0xD7,
|
||||||
|
ExtSkipIfNotLess8 = 0xC3,
|
||||||
|
ExtSkipIfNotGreater8 = 0xC4,
|
||||||
|
ExtSkipIfNotLess16 = 0xC5,
|
||||||
|
ExtSkipIfNotGreater16 = 0xC6,
|
||||||
|
ExtMultiConditionals = 0xF6,
|
||||||
|
|
||||||
|
ExtCheatRegisters = 0x51,
|
||||||
|
ExtCheatRegistersCompare = 0x52,
|
||||||
|
|
||||||
|
ExtCompareBitsSet8 = 0xE4, // Only used inside ExtMultiConditionals
|
||||||
|
ExtCompareBitsClear8 = 0xE5, // Only used inside ExtMultiConditionals
|
||||||
|
};
|
||||||
|
|
||||||
|
union Instruction
|
||||||
|
{
|
||||||
|
u64 bits;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u32 second;
|
||||||
|
u32 first;
|
||||||
|
};
|
||||||
|
|
||||||
|
BitField<u64, InstructionCode, 32 + 24, 8> code;
|
||||||
|
BitField<u64, u32, 32, 24> address;
|
||||||
|
BitField<u64, u32, 0, 32> value32;
|
||||||
|
BitField<u64, u16, 0, 16> value16;
|
||||||
|
BitField<u64, u8, 0, 8> value8;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit GamesharkCheatCode(Metadata metadata);
|
||||||
|
~GamesharkCheatCode() override;
|
||||||
|
|
||||||
|
static std::unique_ptr<GamesharkCheatCode> Parse(Metadata metadata, const std::string_view data, Error* error);
|
||||||
|
|
||||||
|
std::span<const Instruction> GetInstructions() const;
|
||||||
|
|
||||||
|
void SetOptionValue(u32 value) override;
|
||||||
|
|
||||||
|
void Apply() const override;
|
||||||
|
void ApplyOnDisable(RollbackLog* rollback_list) const override;
|
||||||
|
|
||||||
|
bool HasRestorableOnDisableEffects() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Instruction> instructions;
|
||||||
|
std::vector<std::tuple<u32, u8, u8>> option_instruction_values;
|
||||||
|
|
||||||
|
u32 GetNextNonConditionalInstruction(u32 index) const;
|
||||||
|
|
||||||
|
static bool IsConditionalInstruction(InstructionCode code);
|
||||||
|
};
|
||||||
|
|
||||||
|
class AssemblyCheatCode final : public CheatCode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr u64 UNINITIALIZED_OLD_VALUE = UINT64_C(0xFFFFFFFFFFFFFFFF);
|
||||||
|
|
||||||
|
struct Instruction
|
||||||
|
{
|
||||||
|
u32 pc;
|
||||||
|
u32 new_value;
|
||||||
|
mutable u64 old_value; // higher order bits are set so fresh codes apply
|
||||||
|
};
|
||||||
|
static_assert(std::is_trivially_copyable_v<Instruction>);
|
||||||
|
|
||||||
|
explicit AssemblyCheatCode(Metadata metadata);
|
||||||
|
~AssemblyCheatCode() override;
|
||||||
|
|
||||||
|
static std::unique_ptr<AssemblyCheatCode> Parse(Metadata metadata, std::string_view data, Error* error);
|
||||||
|
|
||||||
|
std::span<const Instruction> GetInstructions() const;
|
||||||
|
|
||||||
|
void SetOptionValue(u32 value) override;
|
||||||
|
|
||||||
|
void Apply() const override;
|
||||||
|
void ApplyOnDisable(RollbackLog* rollback_list) const override;
|
||||||
|
|
||||||
|
bool HasRestorableOnDisableEffects() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Instruction> instructions;
|
||||||
|
std::vector<std::tuple<u32, u8, u8>> option_instruction_values;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<CheatCode> ParseCode(CheatCode::Metadata metadata, std::string_view data, Error* error);
|
||||||
|
|
||||||
|
} // namespace Cheats
|
||||||
@@ -95,6 +95,7 @@
|
|||||||
<ClInclude Include="cdrom_async_reader.h" />
|
<ClInclude Include="cdrom_async_reader.h" />
|
||||||
<ClInclude Include="cdrom_subq_replacement.h" />
|
<ClInclude Include="cdrom_subq_replacement.h" />
|
||||||
<ClInclude Include="cheats.h" />
|
<ClInclude Include="cheats.h" />
|
||||||
|
<ClInclude Include="cheats_private.h" />
|
||||||
<ClInclude Include="achievements.h" />
|
<ClInclude Include="achievements.h" />
|
||||||
<ClInclude Include="controller_helpers.h" />
|
<ClInclude Include="controller_helpers.h" />
|
||||||
<ClInclude Include="core.h" />
|
<ClInclude Include="core.h" />
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
<ClInclude Include="cpu_pgxp.h" />
|
<ClInclude Include="cpu_pgxp.h" />
|
||||||
<ClInclude Include="cpu_core_private.h" />
|
<ClInclude Include="cpu_core_private.h" />
|
||||||
<ClInclude Include="cheats.h" />
|
<ClInclude Include="cheats.h" />
|
||||||
|
<ClInclude Include="cheats_private.h" />
|
||||||
<ClInclude Include="memory_card_image.h" />
|
<ClInclude Include="memory_card_image.h" />
|
||||||
<ClInclude Include="analog_joystick.h" />
|
<ClInclude Include="analog_joystick.h" />
|
||||||
<ClInclude Include="gpu_types.h" />
|
<ClInclude Include="gpu_types.h" />
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include "util/image.h"
|
#include "util/image.h"
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|||||||
Reference in New Issue
Block a user