mirror of
https://github.com/stenzek/duckstation.git
synced 2026-07-08 17:46:19 +00: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
|
||||
|
||||
add_executable(core-tests
|
||||
../core/cheats.cpp
|
||||
../core/cpu_disasm.cpp
|
||||
../core/cpu_types.cpp
|
||||
../util/zip_helpers.cpp
|
||||
cheats_tests.cpp
|
||||
cpu_disasm_tests.cpp
|
||||
stub_cpu.cpp
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
292
src/core-tests/cheats_tests.cpp
Normal file
292
src/core-tests/cheats_tests.cpp
Normal file
@@ -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" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
|
||||
<ClCompile Include="..\core\cheats.cpp" />
|
||||
<ClCompile Include="..\core\cpu_disasm.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="stub_cpu.cpp" />
|
||||
</ItemGroup>
|
||||
@@ -12,6 +15,9 @@
|
||||
<ProjectReference Include="..\..\dep\googletest\googletest.vcxproj">
|
||||
<Project>{49953e1b-2ef7-46a4-b88b-1bf9e099093b}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
||||
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\common\common.vcxproj">
|
||||
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
|
||||
</ProjectReference>
|
||||
@@ -20,13 +26,15 @@
|
||||
<ProjectGuid>{93D9300C-B64A-4B21-A4B5-30173DE88747}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\dep\vsprops\ConsoleApplication.props" />
|
||||
<Import Project="..\common\common.props" />
|
||||
<Import Project="..\util\util.props" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\googletest\include</AdditionalIncludeDirectories>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="..\..\dep\vsprops\Targets.props" />
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
|
||||
<ClCompile Include="..\core\cheats.cpp" />
|
||||
<ClCompile Include="..\core\cpu_disasm.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="stub_cpu.cpp" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "core/cpu_core.h"
|
||||
#include "core/system.h"
|
||||
|
||||
CPU::State CPU::g_state;
|
||||
|
||||
@@ -25,3 +26,32 @@ bool CPU::SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value)
|
||||
*value = 0;
|
||||
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
|
||||
cheats.cpp
|
||||
cheats.h
|
||||
cheats_private.h
|
||||
controller.cpp
|
||||
controller.h
|
||||
controller_helpers.h
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "cheats.h"
|
||||
#include "achievements.h"
|
||||
#include "bus.h"
|
||||
#include "cheats_private.h"
|
||||
#include "controller.h"
|
||||
#include "core.h"
|
||||
#include "cpu_core.h"
|
||||
@@ -162,50 +163,6 @@ private:
|
||||
|
||||
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 ActiveCodeList = std::vector<const CheatCode*>;
|
||||
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 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 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 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
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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))
|
||||
{
|
||||
}
|
||||
@@ -4565,42 +4405,6 @@ bool Cheats::GamesharkCheatCode::HasRestorableOnDisableEffects() const
|
||||
// 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))
|
||||
{
|
||||
}
|
||||
@@ -4795,6 +4599,11 @@ std::unique_ptr<Cheats::AssemblyCheatCode> Cheats::AssemblyCheatCode::Parse(Meta
|
||||
return code;
|
||||
}
|
||||
|
||||
std::span<const Cheats::AssemblyCheatCode::Instruction> Cheats::AssemblyCheatCode::GetInstructions() const
|
||||
{
|
||||
return instructions;
|
||||
}
|
||||
|
||||
void Cheats::AssemblyCheatCode::SetOptionValue(u32 value)
|
||||
{
|
||||
for (const auto& [index, bit_position, bit_count] : option_instruction_values)
|
||||
|
||||
203
src/core/cheats_private.h
Normal file
203
src/core/cheats_private.h
Normal file
@@ -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_subq_replacement.h" />
|
||||
<ClInclude Include="cheats.h" />
|
||||
<ClInclude Include="cheats_private.h" />
|
||||
<ClInclude Include="achievements.h" />
|
||||
<ClInclude Include="controller_helpers.h" />
|
||||
<ClInclude Include="core.h" />
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
<ClInclude Include="cpu_pgxp.h" />
|
||||
<ClInclude Include="cpu_core_private.h" />
|
||||
<ClInclude Include="cheats.h" />
|
||||
<ClInclude Include="cheats_private.h" />
|
||||
<ClInclude Include="memory_card_image.h" />
|
||||
<ClInclude Include="analog_joystick.h" />
|
||||
<ClInclude Include="gpu_types.h" />
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "util/image.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
Reference in New Issue
Block a user