summaryrefslogtreecommitdiff
path: root/src/core/memory
diff options
context:
space:
mode:
authorGravatar David2019-09-22 16:24:42 +1000
committerGravatar GitHub2019-09-22 16:24:42 +1000
commit9187350b322c3140c2bec3938b91289ab55e9aae (patch)
treec2f9d045cee2bb87b1182ae93d5072b416b1b87c /src/core/memory
parentMerge pull request #2709 from DarkLordZach/oss-ext-fonts-1 (diff)
parentdmnt_cheat_vm: Default initialize structure values (diff)
downloadyuzu-9187350b322c3140c2bec3938b91289ab55e9aae.tar.gz
yuzu-9187350b322c3140c2bec3938b91289ab55e9aae.tar.xz
yuzu-9187350b322c3140c2bec3938b91289ab55e9aae.zip
Merge pull request #2535 from DarkLordZach/cheat-v2
cheat_engine: Use Atmosphere's Cheat VM and fix cheat crash
Diffstat (limited to 'src/core/memory')
-rw-r--r--src/core/memory/cheat_engine.cpp234
-rw-r--r--src/core/memory/cheat_engine.h86
-rw-r--r--src/core/memory/dmnt_cheat_types.h58
-rw-r--r--src/core/memory/dmnt_cheat_vm.cpp1212
-rw-r--r--src/core/memory/dmnt_cheat_vm.h321
5 files changed, 1911 insertions, 0 deletions
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
new file mode 100644
index 000000000..b56cb0627
--- /dev/null
+++ b/src/core/memory/cheat_engine.cpp
@@ -0,0 +1,234 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <locale>
6#include "common/hex_util.h"
7#include "common/microprofile.h"
8#include "common/swap.h"
9#include "core/core.h"
10#include "core/core_timing.h"
11#include "core/core_timing_util.h"
12#include "core/hle/kernel/process.h"
13#include "core/hle/service/hid/controllers/npad.h"
14#include "core/hle/service/hid/hid.h"
15#include "core/hle/service/sm/sm.h"
16#include "core/memory/cheat_engine.h"
17
18namespace Memory {
19
20constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 12);
21constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
22
23StandardVmCallbacks::StandardVmCallbacks(const Core::System& system,
24 const CheatProcessMetadata& metadata)
25 : system(system), metadata(metadata) {}
26
27StandardVmCallbacks::~StandardVmCallbacks() = default;
28
29void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) {
30 ReadBlock(SanitizeAddress(address), data, size);
31}
32
33void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) {
34 WriteBlock(SanitizeAddress(address), data, size);
35}
36
37u64 StandardVmCallbacks::HidKeysDown() {
38 const auto applet_resource =
39 system.ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
40 if (applet_resource == nullptr) {
41 LOG_WARNING(CheatEngine,
42 "Attempted to read input state, but applet resource is not initialized!");
43 return false;
44 }
45
46 const auto press_state =
47 applet_resource
48 ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
49 .GetAndResetPressState();
50 return press_state & KEYPAD_BITMASK;
51}
52
53void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
54 LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
55}
56
57void StandardVmCallbacks::CommandLog(std::string_view data) {
58 LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
59 data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
60}
61
62VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const {
63 if ((in < metadata.main_nso_extents.base ||
64 in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
65 (in < metadata.heap_extents.base ||
66 in >= metadata.heap_extents.base + metadata.heap_extents.size)) {
67 LOG_ERROR(CheatEngine,
68 "Cheat attempting to access memory at invalid address={:016X}, if this "
69 "persists, "
70 "the cheat may be incorrect. However, this may be normal early in execution if "
71 "the game has not properly set up yet.",
72 in);
73 return 0; ///< Invalid addresses will hard crash
74 }
75
76 return in;
77}
78
79CheatParser::~CheatParser() = default;
80
81TextCheatParser::~TextCheatParser() = default;
82
83namespace {
84template <char match>
85std::string_view ExtractName(std::string_view data, std::size_t start_index) {
86 auto end_index = start_index;
87 while (data[end_index] != match) {
88 ++end_index;
89 if (end_index > data.size() ||
90 (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
91 return {};
92 }
93 }
94
95 return data.substr(start_index, end_index - start_index);
96}
97} // Anonymous namespace
98
99std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
100 std::string_view data) const {
101 std::vector<CheatEntry> out(1);
102 std::optional<u64> current_entry = std::nullopt;
103
104 for (std::size_t i = 0; i < data.size(); ++i) {
105 if (::isspace(data[i])) {
106 continue;
107 }
108
109 if (data[i] == '{') {
110 current_entry = 0;
111
112 if (out[*current_entry].definition.num_opcodes > 0) {
113 return {};
114 }
115
116 const auto name = ExtractName<'}'>(data, i + 1);
117 if (name.empty()) {
118 return {};
119 }
120
121 std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
122 std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
123 name.size()));
124 out[*current_entry]
125 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
126 '\0';
127
128 i += name.length() + 1;
129 } else if (data[i] == '[') {
130 current_entry = out.size();
131 out.emplace_back();
132
133 const auto name = ExtractName<']'>(data, i + 1);
134 if (name.empty()) {
135 return {};
136 }
137
138 std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
139 std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
140 name.size()));
141 out[*current_entry]
142 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
143 '\0';
144
145 i += name.length() + 1;
146 } else if (::isxdigit(data[i])) {
147 if (!current_entry || out[*current_entry].definition.num_opcodes >=
148 out[*current_entry].definition.opcodes.size()) {
149 return {};
150 }
151
152 const auto hex = std::string(data.substr(i, 8));
153 if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
154 return {};
155 }
156
157 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
158 std::stoul(hex, nullptr, 0x10);
159
160 i += 8;
161 } else {
162 return {};
163 }
164 }
165
166 out[0].enabled = out[0].definition.num_opcodes > 0;
167 out[0].cheat_id = 0;
168
169 for (u32 i = 1; i < out.size(); ++i) {
170 out[i].enabled = out[i].definition.num_opcodes > 0;
171 out[i].cheat_id = i;
172 }
173
174 return out;
175}
176
177CheatEngine::CheatEngine(Core::System& system, std::vector<CheatEntry> cheats,
178 const std::array<u8, 0x20>& build_id)
179 : system{system}, core_timing{system.CoreTiming()}, vm{std::make_unique<StandardVmCallbacks>(
180 system, metadata)},
181 cheats(std::move(cheats)) {
182 metadata.main_nso_build_id = build_id;
183}
184
185CheatEngine::~CheatEngine() {
186 core_timing.UnscheduleEvent(event, 0);
187}
188
189void CheatEngine::Initialize() {
190 event = core_timing.RegisterEvent(
191 "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
192 [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
193 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
194
195 metadata.process_id = system.CurrentProcess()->GetProcessID();
196 metadata.title_id = system.CurrentProcess()->GetTitleID();
197
198 const auto& vm_manager = system.CurrentProcess()->VMManager();
199 metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()};
200 metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(),
201 vm_manager.GetAddressSpaceSize()};
202 metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()};
203
204 is_pending_reload.exchange(true);
205}
206
207void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
208 metadata.main_nso_extents = {main_region_begin, main_region_size};
209}
210
211void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
212 this->cheats = std::move(cheats);
213 is_pending_reload.exchange(true);
214}
215
216MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
217
218void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
219 if (is_pending_reload.exchange(false)) {
220 vm.LoadProgram(cheats);
221 }
222
223 if (vm.GetProgramSize() == 0) {
224 return;
225 }
226
227 MICROPROFILE_SCOPE(Cheat_Engine);
228
229 vm.Execute(metadata);
230
231 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
232}
233
234} // namespace Memory
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h
new file mode 100644
index 000000000..0f012e9b5
--- /dev/null
+++ b/src/core/memory/cheat_engine.h
@@ -0,0 +1,86 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <vector>
9#include "common/common_types.h"
10#include "core/memory/dmnt_cheat_types.h"
11#include "core/memory/dmnt_cheat_vm.h"
12
13namespace Core {
14class System;
15}
16
17namespace Core::Timing {
18class CoreTiming;
19struct EventType;
20} // namespace Core::Timing
21
22namespace Memory {
23
24class StandardVmCallbacks : public DmntCheatVm::Callbacks {
25public:
26 StandardVmCallbacks(const Core::System& system, const CheatProcessMetadata& metadata);
27 ~StandardVmCallbacks() override;
28
29 void MemoryRead(VAddr address, void* data, u64 size) override;
30 void MemoryWrite(VAddr address, const void* data, u64 size) override;
31 u64 HidKeysDown() override;
32 void DebugLog(u8 id, u64 value) override;
33 void CommandLog(std::string_view data) override;
34
35private:
36 VAddr SanitizeAddress(VAddr address) const;
37
38 const CheatProcessMetadata& metadata;
39 const Core::System& system;
40};
41
42// Intermediary class that parses a text file or other disk format for storing cheats into a
43// CheatList object, that can be used for execution.
44class CheatParser {
45public:
46 virtual ~CheatParser();
47
48 virtual std::vector<CheatEntry> Parse(const Core::System& system,
49 std::string_view data) const = 0;
50};
51
52// CheatParser implementation that parses text files
53class TextCheatParser final : public CheatParser {
54public:
55 ~TextCheatParser() override;
56
57 std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override;
58};
59
60// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
61class CheatEngine final {
62public:
63 CheatEngine(Core::System& system_, std::vector<CheatEntry> cheats_,
64 const std::array<u8, 0x20>& build_id);
65 ~CheatEngine();
66
67 void Initialize();
68 void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
69
70 void Reload(std::vector<CheatEntry> cheats);
71
72private:
73 void FrameCallback(u64 userdata, s64 cycles_late);
74
75 DmntCheatVm vm;
76 CheatProcessMetadata metadata;
77
78 std::vector<CheatEntry> cheats;
79 std::atomic_bool is_pending_reload{false};
80
81 Core::Timing::EventType* event{};
82 Core::Timing::CoreTiming& core_timing;
83 Core::System& system;
84};
85
86} // namespace Memory
diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h
new file mode 100644
index 000000000..bf68fa0fe
--- /dev/null
+++ b/src/core/memory/dmnt_cheat_types.h
@@ -0,0 +1,58 @@
1/*
2 * Copyright (c) 2018-2019 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2019 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#pragma once
26
27#include "common/common_types.h"
28
29namespace Memory {
30
31struct MemoryRegionExtents {
32 u64 base{};
33 u64 size{};
34};
35
36struct CheatProcessMetadata {
37 u64 process_id{};
38 u64 title_id{};
39 MemoryRegionExtents main_nso_extents{};
40 MemoryRegionExtents heap_extents{};
41 MemoryRegionExtents alias_extents{};
42 MemoryRegionExtents address_space_extents{};
43 std::array<u8, 0x20> main_nso_build_id{};
44};
45
46struct CheatDefinition {
47 std::array<char, 0x40> readable_name{};
48 u32 num_opcodes{};
49 std::array<u32, 0x100> opcodes{};
50};
51
52struct CheatEntry {
53 bool enabled{};
54 u32 cheat_id{};
55 CheatDefinition definition{};
56};
57
58} // namespace Memory
diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp
new file mode 100644
index 000000000..cc16d15a4
--- /dev/null
+++ b/src/core/memory/dmnt_cheat_vm.cpp
@@ -0,0 +1,1212 @@
1/*
2 * Copyright (c) 2018-2019 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2019 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#include "common/assert.h"
26#include "common/scope_exit.h"
27#include "core/memory/dmnt_cheat_types.h"
28#include "core/memory/dmnt_cheat_vm.h"
29
30namespace Memory {
31
32DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks) : callbacks(std::move(callbacks)) {}
33
34DmntCheatVm::~DmntCheatVm() = default;
35
36void DmntCheatVm::DebugLog(u32 log_id, u64 value) {
37 callbacks->DebugLog(static_cast<u8>(log_id), value);
38}
39
40void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) {
41 if (auto store_static = std::get_if<StoreStaticOpcode>(&opcode.opcode)) {
42 callbacks->CommandLog("Opcode: Store Static");
43 callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width));
44 callbacks->CommandLog(
45 fmt::format("Mem Type: {:X}", static_cast<u32>(store_static->mem_type)));
46 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register));
47 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address));
48 callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64));
49 } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&opcode.opcode)) {
50 callbacks->CommandLog("Opcode: Begin Conditional");
51 callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width));
52 callbacks->CommandLog(
53 fmt::format("Mem Type: {:X}", static_cast<u32>(begin_cond->mem_type)));
54 callbacks->CommandLog(
55 fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type)));
56 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address));
57 callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64));
58 } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&opcode.opcode)) {
59 callbacks->CommandLog("Opcode: End Conditional");
60 } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&opcode.opcode)) {
61 if (ctrl_loop->start_loop) {
62 callbacks->CommandLog("Opcode: Start Loop");
63 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
64 callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters));
65 } else {
66 callbacks->CommandLog("Opcode: End Loop");
67 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
68 }
69 } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&opcode.opcode)) {
70 callbacks->CommandLog("Opcode: Load Register Static");
71 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index));
72 callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value));
73 } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&opcode.opcode)) {
74 callbacks->CommandLog("Opcode: Load Register Memory");
75 callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width));
76 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index));
77 callbacks->CommandLog(
78 fmt::format("Mem Type: {:X}", static_cast<u32>(ldr_memory->mem_type)));
79 callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg));
80 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address));
81 } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&opcode.opcode)) {
82 callbacks->CommandLog("Opcode: Store Static to Address");
83 callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width));
84 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index));
85 if (str_static->add_offset_reg) {
86 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index));
87 }
88 callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg));
89 callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value));
90 } else if (auto perform_math_static =
91 std::get_if<PerformArithmeticStaticOpcode>(&opcode.opcode)) {
92 callbacks->CommandLog("Opcode: Perform Static Arithmetic");
93 callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width));
94 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index));
95 callbacks->CommandLog(
96 fmt::format("Math Type: {:X}", static_cast<u32>(perform_math_static->math_type)));
97 callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value));
98 } else if (auto begin_keypress_cond =
99 std::get_if<BeginKeypressConditionalOpcode>(&opcode.opcode)) {
100 callbacks->CommandLog("Opcode: Begin Keypress Conditional");
101 callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask));
102 } else if (auto perform_math_reg =
103 std::get_if<PerformArithmeticRegisterOpcode>(&opcode.opcode)) {
104 callbacks->CommandLog("Opcode: Perform Register Arithmetic");
105 callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width));
106 callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index));
107 callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index));
108 if (perform_math_reg->has_immediate) {
109 callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64));
110 } else {
111 callbacks->CommandLog(
112 fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index));
113 }
114 } else if (auto str_register = std::get_if<StoreRegisterToAddressOpcode>(&opcode.opcode)) {
115 callbacks->CommandLog("Opcode: Store Register to Address");
116 callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width));
117 callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index));
118 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index));
119 callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg));
120 switch (str_register->ofs_type) {
121 case StoreRegisterOffsetType::None:
122 break;
123 case StoreRegisterOffsetType::Reg:
124 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index));
125 break;
126 case StoreRegisterOffsetType::Imm:
127 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
128 break;
129 case StoreRegisterOffsetType::MemReg:
130 callbacks->CommandLog(
131 fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
132 break;
133 case StoreRegisterOffsetType::MemImm:
134 case StoreRegisterOffsetType::MemImmReg:
135 callbacks->CommandLog(
136 fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
137 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
138 break;
139 }
140 } else if (auto begin_reg_cond = std::get_if<BeginRegisterConditionalOpcode>(&opcode.opcode)) {
141 callbacks->CommandLog("Opcode: Begin Register Conditional");
142 callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width));
143 callbacks->CommandLog(
144 fmt::format("Cond Type: {:X}", static_cast<u32>(begin_reg_cond->cond_type)));
145 callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index));
146 switch (begin_reg_cond->comp_type) {
147 case CompareRegisterValueType::StaticValue:
148 callbacks->CommandLog("Comp Type: Static Value");
149 callbacks->CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64));
150 break;
151 case CompareRegisterValueType::OtherRegister:
152 callbacks->CommandLog("Comp Type: Other Register");
153 callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index));
154 break;
155 case CompareRegisterValueType::MemoryRelAddr:
156 callbacks->CommandLog("Comp Type: Memory Relative Address");
157 callbacks->CommandLog(
158 fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
159 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
160 break;
161 case CompareRegisterValueType::MemoryOfsReg:
162 callbacks->CommandLog("Comp Type: Memory Offset Register");
163 callbacks->CommandLog(
164 fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
165 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
166 break;
167 case CompareRegisterValueType::RegisterRelAddr:
168 callbacks->CommandLog("Comp Type: Register Relative Address");
169 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
170 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
171 break;
172 case CompareRegisterValueType::RegisterOfsReg:
173 callbacks->CommandLog("Comp Type: Register Offset Register");
174 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
175 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
176 break;
177 }
178 } else if (auto save_restore_reg = std::get_if<SaveRestoreRegisterOpcode>(&opcode.opcode)) {
179 callbacks->CommandLog("Opcode: Save or Restore Register");
180 callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index));
181 callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index));
182 callbacks->CommandLog(
183 fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_reg->op_type)));
184 } else if (auto save_restore_regmask =
185 std::get_if<SaveRestoreRegisterMaskOpcode>(&opcode.opcode)) {
186 callbacks->CommandLog("Opcode: Save or Restore Register Mask");
187 callbacks->CommandLog(
188 fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_regmask->op_type)));
189 for (std::size_t i = 0; i < NumRegisters; i++) {
190 callbacks->CommandLog(
191 fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i]));
192 }
193 } else if (auto debug_log = std::get_if<DebugLogOpcode>(&opcode.opcode)) {
194 callbacks->CommandLog("Opcode: Debug Log");
195 callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width));
196 callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id));
197 callbacks->CommandLog(
198 fmt::format("Val Type: {:X}", static_cast<u32>(debug_log->val_type)));
199 switch (debug_log->val_type) {
200 case DebugLogValueType::RegisterValue:
201 callbacks->CommandLog("Val Type: Register Value");
202 callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index));
203 break;
204 case DebugLogValueType::MemoryRelAddr:
205 callbacks->CommandLog("Val Type: Memory Relative Address");
206 callbacks->CommandLog(
207 fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
208 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
209 break;
210 case DebugLogValueType::MemoryOfsReg:
211 callbacks->CommandLog("Val Type: Memory Offset Register");
212 callbacks->CommandLog(
213 fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
214 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
215 break;
216 case DebugLogValueType::RegisterRelAddr:
217 callbacks->CommandLog("Val Type: Register Relative Address");
218 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
219 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
220 break;
221 case DebugLogValueType::RegisterOfsReg:
222 callbacks->CommandLog("Val Type: Register Offset Register");
223 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
224 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
225 break;
226 }
227 } else if (auto instr = std::get_if<UnrecognizedInstruction>(&opcode.opcode)) {
228 callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode)));
229 }
230}
231
232DmntCheatVm::Callbacks::~Callbacks() = default;
233
234bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
235 // If we've ever seen a decode failure, return false.
236 bool valid = decode_success;
237 CheatVmOpcode opcode = {};
238 SCOPE_EXIT({
239 decode_success &= valid;
240 if (valid) {
241 out = opcode;
242 }
243 });
244
245 // Helper function for getting instruction dwords.
246 const auto GetNextDword = [&] {
247 if (instruction_ptr >= num_opcodes) {
248 valid = false;
249 return static_cast<u32>(0);
250 }
251 return program[instruction_ptr++];
252 };
253
254 // Helper function for parsing a VmInt.
255 const auto GetNextVmInt = [&](const u32 bit_width) {
256 VmInt val{};
257
258 const u32 first_dword = GetNextDword();
259 switch (bit_width) {
260 case 1:
261 val.bit8 = static_cast<u8>(first_dword);
262 break;
263 case 2:
264 val.bit16 = static_cast<u16>(first_dword);
265 break;
266 case 4:
267 val.bit32 = first_dword;
268 break;
269 case 8:
270 val.bit64 = (static_cast<u64>(first_dword) << 32ul) | static_cast<u64>(GetNextDword());
271 break;
272 }
273
274 return val;
275 };
276
277 // Read opcode.
278 const u32 first_dword = GetNextDword();
279 if (!valid) {
280 return valid;
281 }
282
283 auto opcode_type = static_cast<CheatVmOpcodeType>(((first_dword >> 28) & 0xF));
284 if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) {
285 opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) |
286 ((first_dword >> 24) & 0xF));
287 }
288 if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) {
289 opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) |
290 ((first_dword >> 20) & 0xF));
291 }
292
293 // detect condition start.
294 switch (opcode_type) {
295 case CheatVmOpcodeType::BeginConditionalBlock:
296 case CheatVmOpcodeType::BeginKeypressConditionalBlock:
297 case CheatVmOpcodeType::BeginRegisterConditionalBlock:
298 opcode.begin_conditional_block = true;
299 break;
300 default:
301 opcode.begin_conditional_block = false;
302 break;
303 }
304
305 switch (opcode_type) {
306 case CheatVmOpcodeType::StoreStatic: {
307 StoreStaticOpcode store_static{};
308 // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
309 // Read additional words.
310 const u32 second_dword = GetNextDword();
311 store_static.bit_width = (first_dword >> 24) & 0xF;
312 store_static.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
313 store_static.offset_register = ((first_dword >> 16) & 0xF);
314 store_static.rel_address =
315 (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
316 store_static.value = GetNextVmInt(store_static.bit_width);
317 opcode.opcode = store_static;
318 } break;
319 case CheatVmOpcodeType::BeginConditionalBlock: {
320 BeginConditionalOpcode begin_cond{};
321 // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
322 // Read additional words.
323 const u32 second_dword = GetNextDword();
324 begin_cond.bit_width = (first_dword >> 24) & 0xF;
325 begin_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
326 begin_cond.cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
327 begin_cond.rel_address =
328 (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
329 begin_cond.value = GetNextVmInt(begin_cond.bit_width);
330 opcode.opcode = begin_cond;
331 } break;
332 case CheatVmOpcodeType::EndConditionalBlock: {
333 // 20000000
334 // There's actually nothing left to process here!
335 opcode.opcode = EndConditionalOpcode{};
336 } break;
337 case CheatVmOpcodeType::ControlLoop: {
338 ControlLoopOpcode ctrl_loop{};
339 // 300R0000 VVVVVVVV
340 // 310R0000
341 // Parse register, whether loop start or loop end.
342 ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0;
343 ctrl_loop.reg_index = ((first_dword >> 20) & 0xF);
344
345 // Read number of iters if loop start.
346 if (ctrl_loop.start_loop) {
347 ctrl_loop.num_iters = GetNextDword();
348 }
349 opcode.opcode = ctrl_loop;
350 } break;
351 case CheatVmOpcodeType::LoadRegisterStatic: {
352 LoadRegisterStaticOpcode ldr_static{};
353 // 400R0000 VVVVVVVV VVVVVVVV
354 // Read additional words.
355 ldr_static.reg_index = ((first_dword >> 16) & 0xF);
356 ldr_static.value =
357 (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
358 opcode.opcode = ldr_static;
359 } break;
360 case CheatVmOpcodeType::LoadRegisterMemory: {
361 LoadRegisterMemoryOpcode ldr_memory{};
362 // 5TMRI0AA AAAAAAAA
363 // Read additional words.
364 const u32 second_dword = GetNextDword();
365 ldr_memory.bit_width = (first_dword >> 24) & 0xF;
366 ldr_memory.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
367 ldr_memory.reg_index = ((first_dword >> 16) & 0xF);
368 ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0;
369 ldr_memory.rel_address =
370 (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
371 opcode.opcode = ldr_memory;
372 } break;
373 case CheatVmOpcodeType::StoreStaticToAddress: {
374 StoreStaticToAddressOpcode str_static{};
375 // 6T0RIor0 VVVVVVVV VVVVVVVV
376 // Read additional words.
377 str_static.bit_width = (first_dword >> 24) & 0xF;
378 str_static.reg_index = ((first_dword >> 16) & 0xF);
379 str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0;
380 str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0;
381 str_static.offset_reg_index = ((first_dword >> 4) & 0xF);
382 str_static.value =
383 (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
384 opcode.opcode = str_static;
385 } break;
386 case CheatVmOpcodeType::PerformArithmeticStatic: {
387 PerformArithmeticStaticOpcode perform_math_static{};
388 // 7T0RC000 VVVVVVVV
389 // Read additional words.
390 perform_math_static.bit_width = (first_dword >> 24) & 0xF;
391 perform_math_static.reg_index = ((first_dword >> 16) & 0xF);
392 perform_math_static.math_type =
393 static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF);
394 perform_math_static.value = GetNextDword();
395 opcode.opcode = perform_math_static;
396 } break;
397 case CheatVmOpcodeType::BeginKeypressConditionalBlock: {
398 BeginKeypressConditionalOpcode begin_keypress_cond{};
399 // 8kkkkkkk
400 // Just parse the mask.
401 begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
402 } break;
403 case CheatVmOpcodeType::PerformArithmeticRegister: {
404 PerformArithmeticRegisterOpcode perform_math_reg{};
405 // 9TCRSIs0 (VVVVVVVV (VVVVVVVV))
406 perform_math_reg.bit_width = (first_dword >> 24) & 0xF;
407 perform_math_reg.math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF);
408 perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF);
409 perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF);
410 perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0;
411 if (perform_math_reg.has_immediate) {
412 perform_math_reg.src_reg_2_index = 0;
413 perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width);
414 } else {
415 perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF);
416 }
417 opcode.opcode = perform_math_reg;
418 } break;
419 case CheatVmOpcodeType::StoreRegisterToAddress: {
420 StoreRegisterToAddressOpcode str_register{};
421 // ATSRIOxa (aaaaaaaa)
422 // A = opcode 10
423 // T = bit width
424 // S = src register index
425 // R = address register index
426 // I = 1 if increment address register, 0 if not increment address register
427 // O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region,
428 // 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region +
429 // Relative Address
430 // x = offset register (for offset type 1), memory type (for offset type 3)
431 // a = relative address (for offset type 2+3)
432 str_register.bit_width = (first_dword >> 24) & 0xF;
433 str_register.str_reg_index = ((first_dword >> 20) & 0xF);
434 str_register.addr_reg_index = ((first_dword >> 16) & 0xF);
435 str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0;
436 str_register.ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF));
437 str_register.ofs_reg_index = ((first_dword >> 4) & 0xF);
438 switch (str_register.ofs_type) {
439 case StoreRegisterOffsetType::None:
440 case StoreRegisterOffsetType::Reg:
441 // Nothing more to do
442 break;
443 case StoreRegisterOffsetType::Imm:
444 str_register.rel_address =
445 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
446 break;
447 case StoreRegisterOffsetType::MemReg:
448 str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
449 break;
450 case StoreRegisterOffsetType::MemImm:
451 case StoreRegisterOffsetType::MemImmReg:
452 str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
453 str_register.rel_address =
454 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
455 break;
456 default:
457 str_register.ofs_type = StoreRegisterOffsetType::None;
458 break;
459 }
460 opcode.opcode = str_register;
461 } break;
462 case CheatVmOpcodeType::BeginRegisterConditionalBlock: {
463 BeginRegisterConditionalOpcode begin_reg_cond{};
464 // C0TcSX##
465 // C0TcS0Ma aaaaaaaa
466 // C0TcS1Mr
467 // C0TcS2Ra aaaaaaaa
468 // C0TcS3Rr
469 // C0TcS400 VVVVVVVV (VVVVVVVV)
470 // C0TcS5X0
471 // C0 = opcode 0xC0
472 // T = bit width
473 // c = condition type.
474 // S = source register.
475 // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset
476 // register,
477 // 2 = register with relative offset, 3 = register with offset register, 4 = static
478 // value, 5 = other register.
479 // M = memory type.
480 // R = address register.
481 // a = relative address.
482 // r = offset register.
483 // X = other register.
484 // V = value.
485 begin_reg_cond.bit_width = (first_dword >> 20) & 0xF;
486 begin_reg_cond.cond_type =
487 static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
488 begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF);
489 begin_reg_cond.comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF);
490
491 switch (begin_reg_cond.comp_type) {
492 case CompareRegisterValueType::StaticValue:
493 begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width);
494 break;
495 case CompareRegisterValueType::OtherRegister:
496 begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF);
497 break;
498 case CompareRegisterValueType::MemoryRelAddr:
499 begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
500 begin_reg_cond.rel_address =
501 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
502 break;
503 case CompareRegisterValueType::MemoryOfsReg:
504 begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
505 begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
506 break;
507 case CompareRegisterValueType::RegisterRelAddr:
508 begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
509 begin_reg_cond.rel_address =
510 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
511 break;
512 case CompareRegisterValueType::RegisterOfsReg:
513 begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
514 begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
515 break;
516 }
517 opcode.opcode = begin_reg_cond;
518 } break;
519 case CheatVmOpcodeType::SaveRestoreRegister: {
520 SaveRestoreRegisterOpcode save_restore_reg{};
521 // C10D0Sx0
522 // C1 = opcode 0xC1
523 // D = destination index.
524 // S = source index.
525 // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring
526 // a register.
527 // NOTE: If we add more save slots later, current encoding is backwards compatible.
528 save_restore_reg.dst_index = (first_dword >> 16) & 0xF;
529 save_restore_reg.src_index = (first_dword >> 8) & 0xF;
530 save_restore_reg.op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF);
531 opcode.opcode = save_restore_reg;
532 } break;
533 case CheatVmOpcodeType::SaveRestoreRegisterMask: {
534 SaveRestoreRegisterMaskOpcode save_restore_regmask{};
535 // C2x0XXXX
536 // C2 = opcode 0xC2
537 // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring.
538 // X = 16-bit bitmask, bit i --> save or restore register i.
539 save_restore_regmask.op_type =
540 static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF);
541 for (std::size_t i = 0; i < NumRegisters; i++) {
542 save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0;
543 }
544 opcode.opcode = save_restore_regmask;
545 } break;
546 case CheatVmOpcodeType::DebugLog: {
547 DebugLogOpcode debug_log{};
548 // FFFTIX##
549 // FFFTI0Ma aaaaaaaa
550 // FFFTI1Mr
551 // FFFTI2Ra aaaaaaaa
552 // FFFTI3Rr
553 // FFFTI4X0
554 // FFF = opcode 0xFFF
555 // T = bit width.
556 // I = log id.
557 // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset
558 // register,
559 // 2 = register with relative offset, 3 = register with offset register, 4 = register
560 // value.
561 // M = memory type.
562 // R = address register.
563 // a = relative address.
564 // r = offset register.
565 // X = value register.
566 debug_log.bit_width = (first_dword >> 16) & 0xF;
567 debug_log.log_id = ((first_dword >> 12) & 0xF);
568 debug_log.val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF);
569
570 switch (debug_log.val_type) {
571 case DebugLogValueType::RegisterValue:
572 debug_log.val_reg_index = ((first_dword >> 4) & 0xF);
573 break;
574 case DebugLogValueType::MemoryRelAddr:
575 debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
576 debug_log.rel_address =
577 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
578 break;
579 case DebugLogValueType::MemoryOfsReg:
580 debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
581 debug_log.ofs_reg_index = (first_dword & 0xF);
582 break;
583 case DebugLogValueType::RegisterRelAddr:
584 debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
585 debug_log.rel_address =
586 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
587 break;
588 case DebugLogValueType::RegisterOfsReg:
589 debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
590 debug_log.ofs_reg_index = (first_dword & 0xF);
591 break;
592 }
593 opcode.opcode = debug_log;
594 } break;
595 case CheatVmOpcodeType::ExtendedWidth:
596 case CheatVmOpcodeType::DoubleExtendedWidth:
597 default:
598 // Unrecognized instruction cannot be decoded.
599 valid = false;
600 opcode.opcode = UnrecognizedInstruction{opcode_type};
601 break;
602 }
603
604 // End decoding.
605 return valid;
606}
607
608void DmntCheatVm::SkipConditionalBlock() {
609 if (condition_depth > 0) {
610 // We want to continue until we're out of the current block.
611 const std::size_t desired_depth = condition_depth - 1;
612
613 CheatVmOpcode skip_opcode{};
614 while (condition_depth > desired_depth && DecodeNextOpcode(skip_opcode)) {
615 // Decode instructions until we see end of the current conditional block.
616 // NOTE: This is broken in gateway's implementation.
617 // Gateway currently checks for "0x2" instead of "0x20000000"
618 // In addition, they do a linear scan instead of correctly decoding opcodes.
619 // This causes issues if "0x2" appears as an immediate in the conditional block...
620
621 // We also support nesting of conditional blocks, and Gateway does not.
622 if (skip_opcode.begin_conditional_block) {
623 condition_depth++;
624 } else if (std::holds_alternative<EndConditionalOpcode>(skip_opcode.opcode)) {
625 condition_depth--;
626 }
627 }
628 } else {
629 // Skipping, but condition_depth = 0.
630 // This is an error condition.
631 // However, I don't actually believe it is possible for this to happen.
632 // I guess we'll throw a fatal error here, so as to encourage me to fix the VM
633 // in the event that someone triggers it? I don't know how you'd do that.
634 UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM");
635 }
636}
637
638u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) {
639 switch (bit_width) {
640 case 1:
641 return value.bit8;
642 case 2:
643 return value.bit16;
644 case 4:
645 return value.bit32;
646 case 8:
647 return value.bit64;
648 default:
649 // Invalid bit width -> return 0.
650 return 0;
651 }
652}
653
654u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
655 MemoryAccessType mem_type, u64 rel_address) {
656 switch (mem_type) {
657 case MemoryAccessType::MainNso:
658 default:
659 return metadata.main_nso_extents.base + rel_address;
660 case MemoryAccessType::Heap:
661 return metadata.heap_extents.base + rel_address;
662 }
663}
664
665void DmntCheatVm::ResetState() {
666 registers.fill(0);
667 saved_values.fill(0);
668 loop_tops.fill(0);
669 instruction_ptr = 0;
670 condition_depth = 0;
671 decode_success = true;
672}
673
674bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
675 // Reset opcode count.
676 num_opcodes = 0;
677
678 for (std::size_t i = 0; i < entries.size(); i++) {
679 if (entries[i].enabled) {
680 // Bounds check.
681 if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) {
682 num_opcodes = 0;
683 return false;
684 }
685
686 for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) {
687 program[num_opcodes++] = entries[i].definition.opcodes[n];
688 }
689 }
690 }
691
692 return true;
693}
694
695void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
696 CheatVmOpcode cur_opcode{};
697
698 // Get Keys down.
699 u64 kDown = callbacks->HidKeysDown();
700
701 callbacks->CommandLog("Started VM execution.");
702 callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
703 callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
704 callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
705
706 // Clear VM state.
707 ResetState();
708
709 // Loop until program finishes.
710 while (DecodeNextOpcode(cur_opcode)) {
711 callbacks->CommandLog(
712 fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
713
714 for (std::size_t i = 0; i < NumRegisters; i++) {
715 callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
716 }
717
718 for (std::size_t i = 0; i < NumRegisters; i++) {
719 callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
720 }
721 LogOpcode(cur_opcode);
722
723 // Increment conditional depth, if relevant.
724 if (cur_opcode.begin_conditional_block) {
725 condition_depth++;
726 }
727
728 if (auto store_static = std::get_if<StoreStaticOpcode>(&cur_opcode.opcode)) {
729 // Calculate address, write value to memory.
730 u64 dst_address = GetCheatProcessAddress(metadata, store_static->mem_type,
731 store_static->rel_address +
732 registers[store_static->offset_register]);
733 u64 dst_value = GetVmInt(store_static->value, store_static->bit_width);
734 switch (store_static->bit_width) {
735 case 1:
736 case 2:
737 case 4:
738 case 8:
739 callbacks->MemoryWrite(dst_address, &dst_value, store_static->bit_width);
740 break;
741 }
742 } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&cur_opcode.opcode)) {
743 // Read value from memory.
744 u64 src_address =
745 GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address);
746 u64 src_value = 0;
747 switch (store_static->bit_width) {
748 case 1:
749 case 2:
750 case 4:
751 case 8:
752 callbacks->MemoryRead(src_address, &src_value, begin_cond->bit_width);
753 break;
754 }
755 // Check against condition.
756 u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width);
757 bool cond_met = false;
758 switch (begin_cond->cond_type) {
759 case ConditionalComparisonType::GT:
760 cond_met = src_value > cond_value;
761 break;
762 case ConditionalComparisonType::GE:
763 cond_met = src_value >= cond_value;
764 break;
765 case ConditionalComparisonType::LT:
766 cond_met = src_value < cond_value;
767 break;
768 case ConditionalComparisonType::LE:
769 cond_met = src_value <= cond_value;
770 break;
771 case ConditionalComparisonType::EQ:
772 cond_met = src_value == cond_value;
773 break;
774 case ConditionalComparisonType::NE:
775 cond_met = src_value != cond_value;
776 break;
777 }
778 // Skip conditional block if condition not met.
779 if (!cond_met) {
780 SkipConditionalBlock();
781 }
782 } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&cur_opcode.opcode)) {
783 // Decrement the condition depth.
784 // We will assume, graciously, that mismatched conditional block ends are a nop.
785 if (condition_depth > 0) {
786 condition_depth--;
787 }
788 } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&cur_opcode.opcode)) {
789 if (ctrl_loop->start_loop) {
790 // Start a loop.
791 registers[ctrl_loop->reg_index] = ctrl_loop->num_iters;
792 loop_tops[ctrl_loop->reg_index] = instruction_ptr;
793 } else {
794 // End a loop.
795 registers[ctrl_loop->reg_index]--;
796 if (registers[ctrl_loop->reg_index] != 0) {
797 instruction_ptr = loop_tops[ctrl_loop->reg_index];
798 }
799 }
800 } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&cur_opcode.opcode)) {
801 // Set a register to a static value.
802 registers[ldr_static->reg_index] = ldr_static->value;
803 } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&cur_opcode.opcode)) {
804 // Choose source address.
805 u64 src_address;
806 if (ldr_memory->load_from_reg) {
807 src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address;
808 } else {
809 src_address =
810 GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address);
811 }
812 // Read into register. Gateway only reads on valid bitwidth.
813 switch (ldr_memory->bit_width) {
814 case 1:
815 case 2:
816 case 4:
817 case 8:
818 callbacks->MemoryRead(src_address, &registers[ldr_memory->reg_index],
819 ldr_memory->bit_width);
820 break;
821 }
822 } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&cur_opcode.opcode)) {
823 // Calculate address.
824 u64 dst_address = registers[str_static->reg_index];
825 u64 dst_value = str_static->value;
826 if (str_static->add_offset_reg) {
827 dst_address += registers[str_static->offset_reg_index];
828 }
829 // Write value to memory. Gateway only writes on valid bitwidth.
830 switch (str_static->bit_width) {
831 case 1:
832 case 2:
833 case 4:
834 case 8:
835 callbacks->MemoryWrite(dst_address, &dst_value, str_static->bit_width);
836 break;
837 }
838 // Increment register if relevant.
839 if (str_static->increment_reg) {
840 registers[str_static->reg_index] += str_static->bit_width;
841 }
842 } else if (auto perform_math_static =
843 std::get_if<PerformArithmeticStaticOpcode>(&cur_opcode.opcode)) {
844 // Do requested math.
845 switch (perform_math_static->math_type) {
846 case RegisterArithmeticType::Addition:
847 registers[perform_math_static->reg_index] +=
848 static_cast<u64>(perform_math_static->value);
849 break;
850 case RegisterArithmeticType::Subtraction:
851 registers[perform_math_static->reg_index] -=
852 static_cast<u64>(perform_math_static->value);
853 break;
854 case RegisterArithmeticType::Multiplication:
855 registers[perform_math_static->reg_index] *=
856 static_cast<u64>(perform_math_static->value);
857 break;
858 case RegisterArithmeticType::LeftShift:
859 registers[perform_math_static->reg_index] <<=
860 static_cast<u64>(perform_math_static->value);
861 break;
862 case RegisterArithmeticType::RightShift:
863 registers[perform_math_static->reg_index] >>=
864 static_cast<u64>(perform_math_static->value);
865 break;
866 default:
867 // Do not handle extensions here.
868 break;
869 }
870 // Apply bit width.
871 switch (perform_math_static->bit_width) {
872 case 1:
873 registers[perform_math_static->reg_index] =
874 static_cast<u8>(registers[perform_math_static->reg_index]);
875 break;
876 case 2:
877 registers[perform_math_static->reg_index] =
878 static_cast<u16>(registers[perform_math_static->reg_index]);
879 break;
880 case 4:
881 registers[perform_math_static->reg_index] =
882 static_cast<u32>(registers[perform_math_static->reg_index]);
883 break;
884 case 8:
885 registers[perform_math_static->reg_index] =
886 static_cast<u64>(registers[perform_math_static->reg_index]);
887 break;
888 }
889 } else if (auto begin_keypress_cond =
890 std::get_if<BeginKeypressConditionalOpcode>(&cur_opcode.opcode)) {
891 // Check for keypress.
892 if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) {
893 // Keys not pressed. Skip conditional block.
894 SkipConditionalBlock();
895 }
896 } else if (auto perform_math_reg =
897 std::get_if<PerformArithmeticRegisterOpcode>(&cur_opcode.opcode)) {
898 const u64 operand_1_value = registers[perform_math_reg->src_reg_1_index];
899 const u64 operand_2_value =
900 perform_math_reg->has_immediate
901 ? GetVmInt(perform_math_reg->value, perform_math_reg->bit_width)
902 : registers[perform_math_reg->src_reg_2_index];
903
904 u64 res_val = 0;
905 // Do requested math.
906 switch (perform_math_reg->math_type) {
907 case RegisterArithmeticType::Addition:
908 res_val = operand_1_value + operand_2_value;
909 break;
910 case RegisterArithmeticType::Subtraction:
911 res_val = operand_1_value - operand_2_value;
912 break;
913 case RegisterArithmeticType::Multiplication:
914 res_val = operand_1_value * operand_2_value;
915 break;
916 case RegisterArithmeticType::LeftShift:
917 res_val = operand_1_value << operand_2_value;
918 break;
919 case RegisterArithmeticType::RightShift:
920 res_val = operand_1_value >> operand_2_value;
921 break;
922 case RegisterArithmeticType::LogicalAnd:
923 res_val = operand_1_value & operand_2_value;
924 break;
925 case RegisterArithmeticType::LogicalOr:
926 res_val = operand_1_value | operand_2_value;
927 break;
928 case RegisterArithmeticType::LogicalNot:
929 res_val = ~operand_1_value;
930 break;
931 case RegisterArithmeticType::LogicalXor:
932 res_val = operand_1_value ^ operand_2_value;
933 break;
934 case RegisterArithmeticType::None:
935 res_val = operand_1_value;
936 break;
937 }
938
939 // Apply bit width.
940 switch (perform_math_reg->bit_width) {
941 case 1:
942 res_val = static_cast<u8>(res_val);
943 break;
944 case 2:
945 res_val = static_cast<u16>(res_val);
946 break;
947 case 4:
948 res_val = static_cast<u32>(res_val);
949 break;
950 case 8:
951 res_val = static_cast<u64>(res_val);
952 break;
953 }
954
955 // Save to register.
956 registers[perform_math_reg->dst_reg_index] = res_val;
957 } else if (auto str_register =
958 std::get_if<StoreRegisterToAddressOpcode>(&cur_opcode.opcode)) {
959 // Calculate address.
960 u64 dst_value = registers[str_register->str_reg_index];
961 u64 dst_address = registers[str_register->addr_reg_index];
962 switch (str_register->ofs_type) {
963 case StoreRegisterOffsetType::None:
964 // Nothing more to do
965 break;
966 case StoreRegisterOffsetType::Reg:
967 dst_address += registers[str_register->ofs_reg_index];
968 break;
969 case StoreRegisterOffsetType::Imm:
970 dst_address += str_register->rel_address;
971 break;
972 case StoreRegisterOffsetType::MemReg:
973 dst_address = GetCheatProcessAddress(metadata, str_register->mem_type,
974 registers[str_register->addr_reg_index]);
975 break;
976 case StoreRegisterOffsetType::MemImm:
977 dst_address = GetCheatProcessAddress(metadata, str_register->mem_type,
978 str_register->rel_address);
979 break;
980 case StoreRegisterOffsetType::MemImmReg:
981 dst_address = GetCheatProcessAddress(metadata, str_register->mem_type,
982 registers[str_register->addr_reg_index] +
983 str_register->rel_address);
984 break;
985 }
986
987 // Write value to memory. Write only on valid bitwidth.
988 switch (str_register->bit_width) {
989 case 1:
990 case 2:
991 case 4:
992 case 8:
993 callbacks->MemoryWrite(dst_address, &dst_value, str_register->bit_width);
994 break;
995 }
996
997 // Increment register if relevant.
998 if (str_register->increment_reg) {
999 registers[str_register->addr_reg_index] += str_register->bit_width;
1000 }
1001 } else if (auto begin_reg_cond =
1002 std::get_if<BeginRegisterConditionalOpcode>(&cur_opcode.opcode)) {
1003 // Get value from register.
1004 u64 src_value = 0;
1005 switch (begin_reg_cond->bit_width) {
1006 case 1:
1007 src_value = static_cast<u8>(registers[begin_reg_cond->val_reg_index] & 0xFFul);
1008 break;
1009 case 2:
1010 src_value = static_cast<u16>(registers[begin_reg_cond->val_reg_index] & 0xFFFFul);
1011 break;
1012 case 4:
1013 src_value =
1014 static_cast<u32>(registers[begin_reg_cond->val_reg_index] & 0xFFFFFFFFul);
1015 break;
1016 case 8:
1017 src_value = static_cast<u64>(registers[begin_reg_cond->val_reg_index] &
1018 0xFFFFFFFFFFFFFFFFul);
1019 break;
1020 }
1021
1022 // Read value from memory.
1023 u64 cond_value = 0;
1024 if (begin_reg_cond->comp_type == CompareRegisterValueType::StaticValue) {
1025 cond_value = GetVmInt(begin_reg_cond->value, begin_reg_cond->bit_width);
1026 } else if (begin_reg_cond->comp_type == CompareRegisterValueType::OtherRegister) {
1027 switch (begin_reg_cond->bit_width) {
1028 case 1:
1029 cond_value =
1030 static_cast<u8>(registers[begin_reg_cond->other_reg_index] & 0xFFul);
1031 break;
1032 case 2:
1033 cond_value =
1034 static_cast<u16>(registers[begin_reg_cond->other_reg_index] & 0xFFFFul);
1035 break;
1036 case 4:
1037 cond_value =
1038 static_cast<u32>(registers[begin_reg_cond->other_reg_index] & 0xFFFFFFFFul);
1039 break;
1040 case 8:
1041 cond_value = static_cast<u64>(registers[begin_reg_cond->other_reg_index] &
1042 0xFFFFFFFFFFFFFFFFul);
1043 break;
1044 }
1045 } else {
1046 u64 cond_address = 0;
1047 switch (begin_reg_cond->comp_type) {
1048 case CompareRegisterValueType::MemoryRelAddr:
1049 cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type,
1050 begin_reg_cond->rel_address);
1051 break;
1052 case CompareRegisterValueType::MemoryOfsReg:
1053 cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type,
1054 registers[begin_reg_cond->ofs_reg_index]);
1055 break;
1056 case CompareRegisterValueType::RegisterRelAddr:
1057 cond_address =
1058 registers[begin_reg_cond->addr_reg_index] + begin_reg_cond->rel_address;
1059 break;
1060 case CompareRegisterValueType::RegisterOfsReg:
1061 cond_address = registers[begin_reg_cond->addr_reg_index] +
1062 registers[begin_reg_cond->ofs_reg_index];
1063 break;
1064 default:
1065 break;
1066 }
1067 switch (begin_reg_cond->bit_width) {
1068 case 1:
1069 case 2:
1070 case 4:
1071 case 8:
1072 callbacks->MemoryRead(cond_address, &cond_value, begin_reg_cond->bit_width);
1073 break;
1074 }
1075 }
1076
1077 // Check against condition.
1078 bool cond_met = false;
1079 switch (begin_reg_cond->cond_type) {
1080 case ConditionalComparisonType::GT:
1081 cond_met = src_value > cond_value;
1082 break;
1083 case ConditionalComparisonType::GE:
1084 cond_met = src_value >= cond_value;
1085 break;
1086 case ConditionalComparisonType::LT:
1087 cond_met = src_value < cond_value;
1088 break;
1089 case ConditionalComparisonType::LE:
1090 cond_met = src_value <= cond_value;
1091 break;
1092 case ConditionalComparisonType::EQ:
1093 cond_met = src_value == cond_value;
1094 break;
1095 case ConditionalComparisonType::NE:
1096 cond_met = src_value != cond_value;
1097 break;
1098 }
1099
1100 // Skip conditional block if condition not met.
1101 if (!cond_met) {
1102 SkipConditionalBlock();
1103 }
1104 } else if (auto save_restore_reg =
1105 std::get_if<SaveRestoreRegisterOpcode>(&cur_opcode.opcode)) {
1106 // Save or restore a register.
1107 switch (save_restore_reg->op_type) {
1108 case SaveRestoreRegisterOpType::ClearRegs:
1109 registers[save_restore_reg->dst_index] = 0ul;
1110 break;
1111 case SaveRestoreRegisterOpType::ClearSaved:
1112 saved_values[save_restore_reg->dst_index] = 0ul;
1113 break;
1114 case SaveRestoreRegisterOpType::Save:
1115 saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index];
1116 break;
1117 case SaveRestoreRegisterOpType::Restore:
1118 default:
1119 registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index];
1120 break;
1121 }
1122 } else if (auto save_restore_regmask =
1123 std::get_if<SaveRestoreRegisterMaskOpcode>(&cur_opcode.opcode)) {
1124 // Save or restore register mask.
1125 u64* src;
1126 u64* dst;
1127 switch (save_restore_regmask->op_type) {
1128 case SaveRestoreRegisterOpType::ClearSaved:
1129 case SaveRestoreRegisterOpType::Save:
1130 src = registers.data();
1131 dst = saved_values.data();
1132 break;
1133 case SaveRestoreRegisterOpType::ClearRegs:
1134 case SaveRestoreRegisterOpType::Restore:
1135 default:
1136 src = registers.data();
1137 dst = saved_values.data();
1138 break;
1139 }
1140 for (std::size_t i = 0; i < NumRegisters; i++) {
1141 if (save_restore_regmask->should_operate[i]) {
1142 switch (save_restore_regmask->op_type) {
1143 case SaveRestoreRegisterOpType::ClearSaved:
1144 case SaveRestoreRegisterOpType::ClearRegs:
1145 dst[i] = 0ul;
1146 break;
1147 case SaveRestoreRegisterOpType::Save:
1148 case SaveRestoreRegisterOpType::Restore:
1149 default:
1150 dst[i] = src[i];
1151 break;
1152 }
1153 }
1154 }
1155 } else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) {
1156 // Read value from memory.
1157 u64 log_value = 0;
1158 if (debug_log->val_type == DebugLogValueType::RegisterValue) {
1159 switch (debug_log->bit_width) {
1160 case 1:
1161 log_value = static_cast<u8>(registers[debug_log->val_reg_index] & 0xFFul);
1162 break;
1163 case 2:
1164 log_value = static_cast<u16>(registers[debug_log->val_reg_index] & 0xFFFFul);
1165 break;
1166 case 4:
1167 log_value =
1168 static_cast<u32>(registers[debug_log->val_reg_index] & 0xFFFFFFFFul);
1169 break;
1170 case 8:
1171 log_value = static_cast<u64>(registers[debug_log->val_reg_index] &
1172 0xFFFFFFFFFFFFFFFFul);
1173 break;
1174 }
1175 } else {
1176 u64 val_address = 0;
1177 switch (debug_log->val_type) {
1178 case DebugLogValueType::MemoryRelAddr:
1179 val_address = GetCheatProcessAddress(metadata, debug_log->mem_type,
1180 debug_log->rel_address);
1181 break;
1182 case DebugLogValueType::MemoryOfsReg:
1183 val_address = GetCheatProcessAddress(metadata, debug_log->mem_type,
1184 registers[debug_log->ofs_reg_index]);
1185 break;
1186 case DebugLogValueType::RegisterRelAddr:
1187 val_address = registers[debug_log->addr_reg_index] + debug_log->rel_address;
1188 break;
1189 case DebugLogValueType::RegisterOfsReg:
1190 val_address =
1191 registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index];
1192 break;
1193 default:
1194 break;
1195 }
1196 switch (debug_log->bit_width) {
1197 case 1:
1198 case 2:
1199 case 4:
1200 case 8:
1201 callbacks->MemoryRead(val_address, &log_value, debug_log->bit_width);
1202 break;
1203 }
1204 }
1205
1206 // Log value.
1207 DebugLog(debug_log->log_id, log_value);
1208 }
1209 }
1210}
1211
1212} // namespace Memory
diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h
new file mode 100644
index 000000000..c36212cf1
--- /dev/null
+++ b/src/core/memory/dmnt_cheat_vm.h
@@ -0,0 +1,321 @@
1/*
2 * Copyright (c) 2018-2019 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2019 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#pragma once
26
27#include <variant>
28#include <vector>
29#include <fmt/printf.h>
30#include "common/common_types.h"
31#include "core/memory/dmnt_cheat_types.h"
32
33namespace Memory {
34
35enum class CheatVmOpcodeType : u32 {
36 StoreStatic = 0,
37 BeginConditionalBlock = 1,
38 EndConditionalBlock = 2,
39 ControlLoop = 3,
40 LoadRegisterStatic = 4,
41 LoadRegisterMemory = 5,
42 StoreStaticToAddress = 6,
43 PerformArithmeticStatic = 7,
44 BeginKeypressConditionalBlock = 8,
45
46 // These are not implemented by Gateway's VM.
47 PerformArithmeticRegister = 9,
48 StoreRegisterToAddress = 10,
49 Reserved11 = 11,
50
51 // This is a meta entry, and not a real opcode.
52 // This is to facilitate multi-nybble instruction decoding.
53 ExtendedWidth = 12,
54
55 // Extended width opcodes.
56 BeginRegisterConditionalBlock = 0xC0,
57 SaveRestoreRegister = 0xC1,
58 SaveRestoreRegisterMask = 0xC2,
59
60 // This is a meta entry, and not a real opcode.
61 // This is to facilitate multi-nybble instruction decoding.
62 DoubleExtendedWidth = 0xF0,
63
64 // Double-extended width opcodes.
65 DebugLog = 0xFFF,
66};
67
68enum class MemoryAccessType : u32 {
69 MainNso = 0,
70 Heap = 1,
71};
72
73enum class ConditionalComparisonType : u32 {
74 GT = 1,
75 GE = 2,
76 LT = 3,
77 LE = 4,
78 EQ = 5,
79 NE = 6,
80};
81
82enum class RegisterArithmeticType : u32 {
83 Addition = 0,
84 Subtraction = 1,
85 Multiplication = 2,
86 LeftShift = 3,
87 RightShift = 4,
88
89 // These are not supported by Gateway's VM.
90 LogicalAnd = 5,
91 LogicalOr = 6,
92 LogicalNot = 7,
93 LogicalXor = 8,
94
95 None = 9,
96};
97
98enum class StoreRegisterOffsetType : u32 {
99 None = 0,
100 Reg = 1,
101 Imm = 2,
102 MemReg = 3,
103 MemImm = 4,
104 MemImmReg = 5,
105};
106
107enum class CompareRegisterValueType : u32 {
108 MemoryRelAddr = 0,
109 MemoryOfsReg = 1,
110 RegisterRelAddr = 2,
111 RegisterOfsReg = 3,
112 StaticValue = 4,
113 OtherRegister = 5,
114};
115
116enum class SaveRestoreRegisterOpType : u32 {
117 Restore = 0,
118 Save = 1,
119 ClearSaved = 2,
120 ClearRegs = 3,
121};
122
123enum class DebugLogValueType : u32 {
124 MemoryRelAddr = 0,
125 MemoryOfsReg = 1,
126 RegisterRelAddr = 2,
127 RegisterOfsReg = 3,
128 RegisterValue = 4,
129};
130
131union VmInt {
132 u8 bit8;
133 u16 bit16;
134 u32 bit32;
135 u64 bit64;
136};
137
138struct StoreStaticOpcode {
139 u32 bit_width{};
140 MemoryAccessType mem_type{};
141 u32 offset_register{};
142 u64 rel_address{};
143 VmInt value{};
144};
145
146struct BeginConditionalOpcode {
147 u32 bit_width{};
148 MemoryAccessType mem_type{};
149 ConditionalComparisonType cond_type{};
150 u64 rel_address{};
151 VmInt value{};
152};
153
154struct EndConditionalOpcode {};
155
156struct ControlLoopOpcode {
157 bool start_loop{};
158 u32 reg_index{};
159 u32 num_iters{};
160};
161
162struct LoadRegisterStaticOpcode {
163 u32 reg_index{};
164 u64 value{};
165};
166
167struct LoadRegisterMemoryOpcode {
168 u32 bit_width{};
169 MemoryAccessType mem_type{};
170 u32 reg_index{};
171 bool load_from_reg{};
172 u64 rel_address{};
173};
174
175struct StoreStaticToAddressOpcode {
176 u32 bit_width{};
177 u32 reg_index{};
178 bool increment_reg{};
179 bool add_offset_reg{};
180 u32 offset_reg_index{};
181 u64 value{};
182};
183
184struct PerformArithmeticStaticOpcode {
185 u32 bit_width{};
186 u32 reg_index{};
187 RegisterArithmeticType math_type{};
188 u32 value{};
189};
190
191struct BeginKeypressConditionalOpcode {
192 u32 key_mask{};
193};
194
195struct PerformArithmeticRegisterOpcode {
196 u32 bit_width{};
197 RegisterArithmeticType math_type{};
198 u32 dst_reg_index{};
199 u32 src_reg_1_index{};
200 u32 src_reg_2_index{};
201 bool has_immediate{};
202 VmInt value{};
203};
204
205struct StoreRegisterToAddressOpcode {
206 u32 bit_width{};
207 u32 str_reg_index{};
208 u32 addr_reg_index{};
209 bool increment_reg{};
210 StoreRegisterOffsetType ofs_type{};
211 MemoryAccessType mem_type{};
212 u32 ofs_reg_index{};
213 u64 rel_address{};
214};
215
216struct BeginRegisterConditionalOpcode {
217 u32 bit_width{};
218 ConditionalComparisonType cond_type{};
219 u32 val_reg_index{};
220 CompareRegisterValueType comp_type{};
221 MemoryAccessType mem_type{};
222 u32 addr_reg_index{};
223 u32 other_reg_index{};
224 u32 ofs_reg_index{};
225 u64 rel_address{};
226 VmInt value{};
227};
228
229struct SaveRestoreRegisterOpcode {
230 u32 dst_index{};
231 u32 src_index{};
232 SaveRestoreRegisterOpType op_type{};
233};
234
235struct SaveRestoreRegisterMaskOpcode {
236 SaveRestoreRegisterOpType op_type{};
237 std::array<bool, 0x10> should_operate{};
238};
239
240struct DebugLogOpcode {
241 u32 bit_width{};
242 u32 log_id{};
243 DebugLogValueType val_type{};
244 MemoryAccessType mem_type{};
245 u32 addr_reg_index{};
246 u32 val_reg_index{};
247 u32 ofs_reg_index{};
248 u64 rel_address{};
249};
250
251struct UnrecognizedInstruction {
252 CheatVmOpcodeType opcode{};
253};
254
255struct CheatVmOpcode {
256 bool begin_conditional_block{};
257 std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
258 LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
259 PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
260 PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
261 BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
262 SaveRestoreRegisterMaskOpcode, DebugLogOpcode, UnrecognizedInstruction>
263 opcode{};
264};
265
266class DmntCheatVm {
267public:
268 /// Helper Type for DmntCheatVm <=> yuzu Interface
269 class Callbacks {
270 public:
271 virtual ~Callbacks();
272
273 virtual void MemoryRead(VAddr address, void* data, u64 size) = 0;
274 virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0;
275
276 virtual u64 HidKeysDown() = 0;
277
278 virtual void DebugLog(u8 id, u64 value) = 0;
279 virtual void CommandLog(std::string_view data) = 0;
280 };
281
282 static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
283 static constexpr std::size_t NumRegisters = 0x10;
284
285 explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks);
286 ~DmntCheatVm();
287
288 std::size_t GetProgramSize() const {
289 return this->num_opcodes;
290 }
291
292 bool LoadProgram(const std::vector<CheatEntry>& cheats);
293 void Execute(const CheatProcessMetadata& metadata);
294
295private:
296 std::unique_ptr<Callbacks> callbacks;
297
298 std::size_t num_opcodes = 0;
299 std::size_t instruction_ptr = 0;
300 std::size_t condition_depth = 0;
301 bool decode_success = false;
302 std::array<u32, MaximumProgramOpcodeCount> program{};
303 std::array<u64, NumRegisters> registers{};
304 std::array<u64, NumRegisters> saved_values{};
305 std::array<std::size_t, NumRegisters> loop_tops{};
306
307 bool DecodeNextOpcode(CheatVmOpcode& out);
308 void SkipConditionalBlock();
309 void ResetState();
310
311 // For implementing the DebugLog opcode.
312 void DebugLog(u32 log_id, u64 value);
313
314 void LogOpcode(const CheatVmOpcode& opcode);
315
316 static u64 GetVmInt(VmInt value, u32 bit_width);
317 static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
318 MemoryAccessType mem_type, u64 rel_address);
319};
320
321}; // namespace Memory