Add CheatEngine and support for Gateway cheats (#4406)
* Add CheatEngine; Add support for Gateway cheats; Add Cheat UI * fix a potential crash on some systems * fix substr with negative length * Add Joker to the NonOp comp handling * Fixup JokerOp * minor fixup in patchop; add todo for nested loops * Add comment for PadState member variable in HID * fix: stol to stoul in parsing cheat file * fix misplaced parsing of values; fix patchop code * add missing break * Make read_func and write_func a template parameter
This commit is contained in:
parent
560df843b1
commit
b90ff739a0
23 changed files with 1052 additions and 1 deletions
|
@ -26,6 +26,12 @@ add_library(core STATIC
|
|||
arm/skyeye_common/vfp/vfpdouble.cpp
|
||||
arm/skyeye_common/vfp/vfpinstr.cpp
|
||||
arm/skyeye_common/vfp/vfpsingle.cpp
|
||||
cheats/cheat_base.cpp
|
||||
cheats/cheat_base.h
|
||||
cheats/cheats.cpp
|
||||
cheats/cheats.h
|
||||
cheats/gateway_cheat.cpp
|
||||
cheats/gateway_cheat.h
|
||||
core.cpp
|
||||
core.h
|
||||
core_timing.cpp
|
||||
|
|
9
src/core/cheats/cheat_base.cpp
Normal file
9
src/core/cheats/cheat_base.cpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/cheats/cheat_base.h"
|
||||
|
||||
namespace Cheats {
|
||||
CheatBase::~CheatBase() = default;
|
||||
} // namespace Cheats
|
28
src/core/cheats/cheat_base.h
Normal file
28
src/core/cheats/cheat_base.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Cheats {
|
||||
class CheatBase {
|
||||
public:
|
||||
virtual ~CheatBase();
|
||||
virtual void Execute(Core::System& system) = 0;
|
||||
|
||||
virtual bool IsEnabled() const = 0;
|
||||
virtual void SetEnabled(bool enabled) = 0;
|
||||
|
||||
virtual std::string GetComments() const = 0;
|
||||
virtual std::string GetName() const = 0;
|
||||
virtual std::string GetType() const = 0;
|
||||
|
||||
virtual std::string ToString() const = 0;
|
||||
};
|
||||
} // namespace Cheats
|
58
src/core/cheats/cheats.cpp
Normal file
58
src/core/cheats/cheats.cpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <functional>
|
||||
#include <fmt/format.h>
|
||||
#include "core/cheats/cheats.h"
|
||||
#include "core/cheats/gateway_cheat.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
||||
namespace Cheats {
|
||||
|
||||
constexpr u64 run_interval_ticks = BASE_CLOCK_RATE_ARM11 / 60;
|
||||
|
||||
CheatEngine::CheatEngine(Core::System& system_) : system(system_) {
|
||||
LoadCheatFile();
|
||||
event = system.CoreTiming().RegisterEvent(
|
||||
"CheatCore::run_event",
|
||||
[this](u64 thread_id, s64 cycle_late) { RunCallback(thread_id, cycle_late); });
|
||||
system.CoreTiming().ScheduleEvent(run_interval_ticks, event);
|
||||
}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
system.CoreTiming().UnscheduleEvent(event, 0);
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<CheatBase>>& CheatEngine::GetCheats() const {
|
||||
return cheats_list;
|
||||
}
|
||||
|
||||
void CheatEngine::LoadCheatFile() {
|
||||
const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir);
|
||||
const std::string filepath = fmt::format(
|
||||
"{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id);
|
||||
|
||||
if (!FileUtil::IsDirectory(cheat_dir)) {
|
||||
FileUtil::CreateDir(cheat_dir);
|
||||
}
|
||||
|
||||
if (!FileUtil::Exists(filepath))
|
||||
return;
|
||||
|
||||
auto gateway_cheats = GatewayCheat::LoadFile(filepath);
|
||||
std::move(gateway_cheats.begin(), gateway_cheats.end(), std::back_inserter(cheats_list));
|
||||
}
|
||||
|
||||
void CheatEngine::RunCallback([[maybe_unused]] u64 userdata, int cycles_late) {
|
||||
for (auto& cheat : cheats_list) {
|
||||
if (cheat->IsEnabled()) {
|
||||
cheat->Execute(system);
|
||||
}
|
||||
}
|
||||
system.CoreTiming().ScheduleEvent(run_interval_ticks - cycles_late, event);
|
||||
}
|
||||
|
||||
} // namespace Cheats
|
37
src/core/cheats/cheats.h
Normal file
37
src/core/cheats/cheats.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
struct TimingEventType;
|
||||
} // namespace Core
|
||||
|
||||
namespace CoreTiming {
|
||||
struct EventType;
|
||||
}
|
||||
|
||||
namespace Cheats {
|
||||
|
||||
class CheatBase;
|
||||
|
||||
class CheatEngine {
|
||||
public:
|
||||
explicit CheatEngine(Core::System& system);
|
||||
~CheatEngine();
|
||||
const std::vector<std::unique_ptr<CheatBase>>& GetCheats() const;
|
||||
|
||||
private:
|
||||
void LoadCheatFile();
|
||||
void RunCallback(u64 userdata, int cycles_late);
|
||||
std::vector<std::unique_ptr<CheatBase>> cheats_list;
|
||||
Core::TimingEventType* event;
|
||||
Core::System& system;
|
||||
};
|
||||
} // namespace Cheats
|
463
src/core/cheats/gateway_cheat.cpp
Normal file
463
src/core/cheats/gateway_cheat.cpp
Normal file
|
@ -0,0 +1,463 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/cheats/gateway_cheat.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Cheats {
|
||||
|
||||
struct State {
|
||||
u32 reg = 0;
|
||||
u32 offset = 0;
|
||||
u32 if_flag = 0;
|
||||
u32 loop_count = 0;
|
||||
std::size_t loop_back_line = 0;
|
||||
std::size_t current_line_nr = 0;
|
||||
bool loop_flag = false;
|
||||
};
|
||||
|
||||
template <typename T, typename WriteFunction>
|
||||
static inline std::enable_if_t<std::is_integral_v<T>> WriteOp(const GatewayCheat::CheatLine& line,
|
||||
const State& state,
|
||||
WriteFunction write_func,
|
||||
Core::System& system) {
|
||||
u32 addr = line.address + state.offset;
|
||||
write_func(addr, static_cast<T>(line.value));
|
||||
system.CPU().InvalidateCacheRange(addr, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T, typename ReadFunction, typename CompareFunc>
|
||||
static inline std::enable_if_t<std::is_integral_v<T>> CompOp(const GatewayCheat::CheatLine& line,
|
||||
State& state, ReadFunction read_func,
|
||||
CompareFunc comp) {
|
||||
u32 addr = line.address + state.offset;
|
||||
T val = read_func(addr);
|
||||
if (!comp(val)) {
|
||||
state.if_flag++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void LoadOffsetOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
u32 addr = line.address + state.offset;
|
||||
state.offset = Memory::Read32(addr);
|
||||
}
|
||||
|
||||
static inline void LoopOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.loop_flag = state.loop_count < line.value;
|
||||
state.loop_count++;
|
||||
state.loop_back_line = state.current_line_nr;
|
||||
}
|
||||
|
||||
static inline void TerminateOp(State& state) {
|
||||
if (state.if_flag > 0) {
|
||||
state.if_flag--;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void LoopExecuteVariantOp(State& state) {
|
||||
if (state.loop_flag) {
|
||||
state.current_line_nr = state.loop_back_line - 1;
|
||||
} else {
|
||||
state.loop_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void FullTerminateOp(State& state) {
|
||||
if (state.loop_flag) {
|
||||
state.current_line_nr = state.loop_back_line - 1;
|
||||
} else {
|
||||
state.offset = 0;
|
||||
state.reg = 0;
|
||||
state.loop_count = 0;
|
||||
state.if_flag = 0;
|
||||
state.loop_flag = false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void SetOffsetOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.offset = line.value;
|
||||
}
|
||||
|
||||
static inline void AddValueOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.reg += line.value;
|
||||
}
|
||||
|
||||
static inline void SetValueOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.reg = line.value;
|
||||
}
|
||||
|
||||
template <typename T, typename WriteFunction>
|
||||
static inline std::enable_if_t<std::is_integral_v<T>> IncrementiveWriteOp(
|
||||
const GatewayCheat::CheatLine& line, State& state, WriteFunction write_func,
|
||||
Core::System& system) {
|
||||
u32 addr = line.value + state.offset;
|
||||
write_func(addr, static_cast<T>(state.reg));
|
||||
system.CPU().InvalidateCacheRange(addr, sizeof(T));
|
||||
state.offset += sizeof(T);
|
||||
}
|
||||
|
||||
template <typename T, typename ReadFunction>
|
||||
static inline std::enable_if_t<std::is_integral_v<T>> LoadOp(const GatewayCheat::CheatLine& line,
|
||||
State& state, ReadFunction read_func) {
|
||||
|
||||
u32 addr = line.value + state.offset;
|
||||
state.reg = read_func(addr);
|
||||
}
|
||||
|
||||
static inline void AddOffsetOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.offset += line.value;
|
||||
}
|
||||
|
||||
static inline void JokerOp(const GatewayCheat::CheatLine& line, State& state,
|
||||
const Core::System& system) {
|
||||
u32 pad_state = system.ServiceManager()
|
||||
.GetService<Service::HID::Module::Interface>("hid:USER")
|
||||
->GetModule()
|
||||
->GetState()
|
||||
.hex;
|
||||
bool pressed = (pad_state & line.value) == line.value;
|
||||
if (!pressed) {
|
||||
state.if_flag++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Core::System& system,
|
||||
const std::vector<GatewayCheat::CheatLine>& cheat_lines) {
|
||||
if (state.if_flag > 0) {
|
||||
// Skip over the additional patch lines
|
||||
state.current_line_nr += static_cast<int>(std::ceil(line.value / 8.0));
|
||||
return;
|
||||
}
|
||||
u32 num_bytes = line.value;
|
||||
u32 addr = line.address + state.offset;
|
||||
system.CPU().InvalidateCacheRange(addr, num_bytes);
|
||||
bool first = true;
|
||||
u32 bit_offset = 0;
|
||||
if (num_bytes > 0)
|
||||
state.current_line_nr++; // skip over the current code
|
||||
while (num_bytes >= 4) {
|
||||
u32 tmp = first ? cheat_lines[state.current_line_nr].first
|
||||
: cheat_lines[state.current_line_nr].value;
|
||||
if (!first && num_bytes > 4) {
|
||||
state.current_line_nr++;
|
||||
}
|
||||
first = !first;
|
||||
Memory::Write32(addr, tmp);
|
||||
addr += 4;
|
||||
num_bytes -= 4;
|
||||
}
|
||||
while (num_bytes > 0) {
|
||||
u32 tmp = (first ? cheat_lines[state.current_line_nr].first
|
||||
: cheat_lines[state.current_line_nr].value) >>
|
||||
bit_offset;
|
||||
Memory::Write8(addr, tmp);
|
||||
addr += 1;
|
||||
num_bytes -= 1;
|
||||
bit_offset += 8;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayCheat::CheatLine::CheatLine(const std::string& line) {
|
||||
constexpr std::size_t cheat_length = 17;
|
||||
if (line.length() != cheat_length) {
|
||||
type = CheatType::Null;
|
||||
cheat_line = line;
|
||||
LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
std::string type_temp = line.substr(0, 1);
|
||||
// 0xD types have extra subtype value, i.e. 0xDA
|
||||
std::string sub_type_temp;
|
||||
if (type_temp == "D" || type_temp == "d")
|
||||
sub_type_temp = line.substr(1, 1);
|
||||
type = static_cast<CheatType>(std::stoi(type_temp + sub_type_temp, 0, 16));
|
||||
first = std::stoul(line.substr(0, 8), 0, 16);
|
||||
address = first & 0x0FFFFFFF;
|
||||
value = std::stoul(line.substr(9, 8), 0, 16);
|
||||
cheat_line = line;
|
||||
} catch (const std::logic_error& e) {
|
||||
type = CheatType::Null;
|
||||
cheat_line = line;
|
||||
LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
GatewayCheat::GatewayCheat(std::string name_, std::vector<CheatLine> cheat_lines_,
|
||||
std::string comments_)
|
||||
: name(std::move(name_)), cheat_lines(std::move(cheat_lines_)), comments(std::move(comments_)) {
|
||||
}
|
||||
|
||||
GatewayCheat::~GatewayCheat() = default;
|
||||
|
||||
void GatewayCheat::Execute(Core::System& system) {
|
||||
State state;
|
||||
|
||||
for (state.current_line_nr = 0; state.current_line_nr < cheat_lines.size();
|
||||
state.current_line_nr++) {
|
||||
auto line = cheat_lines[state.current_line_nr];
|
||||
if (state.if_flag > 0) {
|
||||
switch (line.type) {
|
||||
case CheatType::GreaterThan32:
|
||||
case CheatType::LessThan32:
|
||||
case CheatType::EqualTo32:
|
||||
case CheatType::NotEqualTo32:
|
||||
case CheatType::GreaterThan16WithMask:
|
||||
case CheatType::LessThan16WithMask:
|
||||
case CheatType::EqualTo16WithMask:
|
||||
case CheatType::NotEqualTo16WithMask:
|
||||
case CheatType::Joker:
|
||||
// Increment the if_flag to handle the end if correctly
|
||||
state.if_flag++;
|
||||
break;
|
||||
case CheatType::Patch:
|
||||
// EXXXXXXX YYYYYYYY
|
||||
// Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset].
|
||||
// We need to call this here to skip the additional patch lines
|
||||
PatchOp(line, state, system, cheat_lines);
|
||||
break;
|
||||
case CheatType::Terminator:
|
||||
// D0000000 00000000 - ENDIF
|
||||
TerminateOp(state);
|
||||
break;
|
||||
case CheatType::FullTerminator:
|
||||
// D2000000 00000000 - END; offset = 0; reg = 0;
|
||||
FullTerminateOp(state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Do not execute any other op code
|
||||
continue;
|
||||
}
|
||||
switch (line.type) {
|
||||
case CheatType::Null:
|
||||
break;
|
||||
case CheatType::Write32:
|
||||
// 0XXXXXXX YYYYYYYY - word[XXXXXXX+offset] = YYYYYYYY
|
||||
WriteOp<u32>(line, state, &Memory::Write32, system);
|
||||
break;
|
||||
case CheatType::Write16:
|
||||
// 1XXXXXXX 0000YYYY - half[XXXXXXX+offset] = YYYY
|
||||
WriteOp<u16>(line, state, &Memory::Write16, system);
|
||||
break;
|
||||
case CheatType::Write8:
|
||||
// 2XXXXXXX 000000YY - byte[XXXXXXX+offset] = YY
|
||||
WriteOp<u8>(line, state, &Memory::Write8, system);
|
||||
break;
|
||||
case CheatType::GreaterThan32:
|
||||
// 3XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY > word[XXXXXXX] ;unsigned
|
||||
CompOp<u32>(line, state, &Memory::Read32,
|
||||
[&line](u32 val) -> bool { return line.value > val; });
|
||||
break;
|
||||
case CheatType::LessThan32:
|
||||
// 4XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY < word[XXXXXXX] ;unsigned
|
||||
CompOp<u32>(line, state, &Memory::Read32,
|
||||
[&line](u32 val) -> bool { return line.value < val; });
|
||||
break;
|
||||
case CheatType::EqualTo32:
|
||||
// 5XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY == word[XXXXXXX] ;unsigned
|
||||
CompOp<u32>(line, state, &Memory::Read32,
|
||||
[&line](u32 val) -> bool { return line.value == val; });
|
||||
break;
|
||||
case CheatType::NotEqualTo32:
|
||||
// 6XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY != word[XXXXXXX] ;unsigned
|
||||
CompOp<u32>(line, state, &Memory::Read32,
|
||||
[&line](u32 val) -> bool { return line.value != val; });
|
||||
break;
|
||||
case CheatType::GreaterThan16WithMask:
|
||||
// 7XXXXXXX ZZZZYYYY - Execute next block IF YYYY > ((not ZZZZ) AND half[XXXXXXX])
|
||||
CompOp<u16>(line, state, &Memory::Read16, [&line](u16 val) -> bool {
|
||||
return static_cast<u16>(line.value) > ~(static_cast<u16>(~line.value >> 16) & val);
|
||||
});
|
||||
break;
|
||||
case CheatType::LessThan16WithMask:
|
||||
// 8XXXXXXX ZZZZYYYY - Execute next block IF YYYY < ((not ZZZZ) AND half[XXXXXXX])
|
||||
CompOp<u16>(line, state, &Memory::Read16, [&line](u16 val) -> bool {
|
||||
return static_cast<u16>(line.value) < ~(static_cast<u16>(~line.value >> 16) & val);
|
||||
});
|
||||
break;
|
||||
case CheatType::EqualTo16WithMask:
|
||||
// 9XXXXXXX ZZZZYYYY - Execute next block IF YYYY = ((not ZZZZ) AND half[XXXXXXX])
|
||||
CompOp<u16>(line, state, &Memory::Read16, [&line](u16 val) -> bool {
|
||||
return static_cast<u16>(line.value) == ~(static_cast<u16>(~line.value >> 16) & val);
|
||||
});
|
||||
break;
|
||||
case CheatType::NotEqualTo16WithMask:
|
||||
// AXXXXXXX ZZZZYYYY - Execute next block IF YYYY <> ((not ZZZZ) AND half[XXXXXXX])
|
||||
CompOp<u16>(line, state, &Memory::Read16, [&line](u16 val) -> bool {
|
||||
return static_cast<u16>(line.value) != ~(static_cast<u16>(~line.value >> 16) & val);
|
||||
});
|
||||
break;
|
||||
case CheatType::LoadOffset:
|
||||
// BXXXXXXX 00000000 - offset = word[XXXXXXX+offset]
|
||||
LoadOffsetOp(line, state);
|
||||
break;
|
||||
case CheatType::Loop: {
|
||||
// C0000000 YYYYYYYY - LOOP next block YYYYYYYY times
|
||||
// TODO(B3N30): Support nested loops if necessary
|
||||
LoopOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::Terminator: {
|
||||
// D0000000 00000000 - END IF
|
||||
TerminateOp(state);
|
||||
break;
|
||||
}
|
||||
case CheatType::LoopExecuteVariant: {
|
||||
// D1000000 00000000 - END LOOP
|
||||
LoopExecuteVariantOp(state);
|
||||
break;
|
||||
}
|
||||
case CheatType::FullTerminator: {
|
||||
// D2000000 00000000 - NEXT & Flush
|
||||
FullTerminateOp(state);
|
||||
break;
|
||||
}
|
||||
case CheatType::SetOffset: {
|
||||
// D3000000 XXXXXXXX – Sets the offset to XXXXXXXX
|
||||
SetOffsetOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::AddValue: {
|
||||
// D4000000 XXXXXXXX – reg += XXXXXXXX
|
||||
AddValueOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::SetValue: {
|
||||
// D5000000 XXXXXXXX – reg = XXXXXXXX
|
||||
SetValueOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::IncrementiveWrite32: {
|
||||
// D6000000 XXXXXXXX – (32bit) [XXXXXXXX+offset] = reg ; offset += 4
|
||||
IncrementiveWriteOp<u32>(line, state, &Memory::Write32, system);
|
||||
break;
|
||||
}
|
||||
case CheatType::IncrementiveWrite16: {
|
||||
// D7000000 XXXXXXXX – (16bit) [XXXXXXXX+offset] = reg & 0xffff ; offset += 2
|
||||
IncrementiveWriteOp<u16>(line, state, &Memory::Write16, system);
|
||||
break;
|
||||
}
|
||||
case CheatType::IncrementiveWrite8: {
|
||||
// D8000000 XXXXXXXX – (16bit) [XXXXXXXX+offset] = reg & 0xff ; offset++
|
||||
IncrementiveWriteOp<u8>(line, state, &Memory::Write8, system);
|
||||
break;
|
||||
}
|
||||
case CheatType::Load32: {
|
||||
// D9000000 XXXXXXXX – reg = [XXXXXXXX+offset]
|
||||
LoadOp<u32>(line, state, &Memory::Read32);
|
||||
break;
|
||||
}
|
||||
case CheatType::Load16: {
|
||||
// DA000000 XXXXXXXX – reg = [XXXXXXXX+offset] & 0xFFFF
|
||||
LoadOp<u16>(line, state, &Memory::Read16);
|
||||
break;
|
||||
}
|
||||
case CheatType::Load8: {
|
||||
// DB000000 XXXXXXXX – reg = [XXXXXXXX+offset] & 0xFF
|
||||
LoadOp<u8>(line, state, &Memory::Read8);
|
||||
break;
|
||||
}
|
||||
case CheatType::AddOffset: {
|
||||
// DC000000 XXXXXXXX – offset + XXXXXXXX
|
||||
AddOffsetOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::Joker: {
|
||||
// DD000000 XXXXXXXX – if KEYPAD has value XXXXXXXX execute next block
|
||||
JokerOp(line, state, system);
|
||||
break;
|
||||
}
|
||||
case CheatType::Patch: {
|
||||
// EXXXXXXX YYYYYYYY
|
||||
// Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset].
|
||||
PatchOp(line, state, system, cheat_lines);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GatewayCheat::IsEnabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void GatewayCheat::SetEnabled(bool enabled_) {
|
||||
enabled = enabled_;
|
||||
if (enabled) {
|
||||
LOG_WARNING(Core_Cheats, "Cheats enabled. This might lead to weird behaviour or crashes");
|
||||
}
|
||||
}
|
||||
|
||||
std::string GatewayCheat::GetComments() const {
|
||||
return comments;
|
||||
}
|
||||
|
||||
std::string GatewayCheat::GetName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string GatewayCheat::GetType() const {
|
||||
return "Gateway";
|
||||
}
|
||||
|
||||
std::string GatewayCheat::ToString() const {
|
||||
std::string result;
|
||||
result += '[' + name + "]\n";
|
||||
result += comments + '\n';
|
||||
for (const auto& line : cheat_lines)
|
||||
result += line.cheat_line + '\n';
|
||||
result += '\n';
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string& filepath) {
|
||||
std::vector<std::unique_ptr<CheatBase>> cheats;
|
||||
|
||||
std::ifstream file;
|
||||
OpenFStream(file, filepath, std::ios_base::in);
|
||||
if (!file) {
|
||||
return cheats;
|
||||
}
|
||||
|
||||
std::string comments;
|
||||
std::vector<CheatLine> cheat_lines;
|
||||
std::string name;
|
||||
|
||||
while (!file.eof()) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
|
||||
line = Common::StripSpaces(line); // remove spaces at front and end
|
||||
if (line.length() >= 2 && line.front() == '[') {
|
||||
if (!cheat_lines.empty()) {
|
||||
cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments));
|
||||
}
|
||||
name = line.substr(1, line.length() - 2);
|
||||
cheat_lines.clear();
|
||||
comments.erase();
|
||||
} else if (!line.empty() && line.front() == '*') {
|
||||
comments += line.substr(1, line.length() - 1) + '\n';
|
||||
} else if (!line.empty()) {
|
||||
cheat_lines.emplace_back(std::move(line));
|
||||
}
|
||||
}
|
||||
if (!cheat_lines.empty()) {
|
||||
cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments));
|
||||
}
|
||||
return cheats;
|
||||
}
|
||||
} // namespace Cheats
|
83
src/core/cheats/gateway_cheat.h
Normal file
83
src/core/cheats/gateway_cheat.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include "core/cheats/cheat_base.h"
|
||||
|
||||
namespace Cheats {
|
||||
class GatewayCheat final : public CheatBase {
|
||||
public:
|
||||
enum class CheatType {
|
||||
Null = -0x1,
|
||||
Write32 = 0x00,
|
||||
Write16 = 0x01,
|
||||
Write8 = 0x02,
|
||||
GreaterThan32 = 0x03,
|
||||
LessThan32 = 0x04,
|
||||
EqualTo32 = 0x05,
|
||||
NotEqualTo32 = 0x06,
|
||||
GreaterThan16WithMask = 0x07,
|
||||
LessThan16WithMask = 0x08,
|
||||
EqualTo16WithMask = 0x09,
|
||||
NotEqualTo16WithMask = 0x0A,
|
||||
LoadOffset = 0x0B,
|
||||
Loop = 0x0C,
|
||||
Terminator = 0xD0,
|
||||
LoopExecuteVariant = 0xD1,
|
||||
FullTerminator = 0xD2,
|
||||
SetOffset = 0xD3,
|
||||
AddValue = 0xD4,
|
||||
SetValue = 0xD5,
|
||||
IncrementiveWrite32 = 0xD6,
|
||||
IncrementiveWrite16 = 0xD7,
|
||||
IncrementiveWrite8 = 0xD8,
|
||||
Load32 = 0xD9,
|
||||
Load16 = 0xDA,
|
||||
Load8 = 0xDB,
|
||||
AddOffset = 0xDC,
|
||||
Joker = 0xDD,
|
||||
Patch = 0xE,
|
||||
};
|
||||
|
||||
struct CheatLine {
|
||||
explicit CheatLine(const std::string& line);
|
||||
CheatType type;
|
||||
u32 address;
|
||||
u32 value;
|
||||
u32 first;
|
||||
std::string cheat_line;
|
||||
};
|
||||
|
||||
GatewayCheat(std::string name, std::vector<CheatLine> cheat_lines, std::string comments);
|
||||
~GatewayCheat();
|
||||
|
||||
void Execute(Core::System& system) override;
|
||||
|
||||
bool IsEnabled() const override;
|
||||
void SetEnabled(bool enabled) override;
|
||||
|
||||
std::string GetComments() const override;
|
||||
std::string GetName() const override;
|
||||
std::string GetType() const override;
|
||||
std::string ToString() const override;
|
||||
|
||||
/// Gateway cheats look like:
|
||||
/// [Name]
|
||||
/// 12345678 90ABCDEF
|
||||
/// 12345678 90ABCDEF
|
||||
/// (there might be multiple lines of those hex numbers)
|
||||
/// Comment lines start with a '*'
|
||||
/// This function will pares the file for such structures
|
||||
static std::vector<std::unique_ptr<CheatBase>> LoadFile(const std::string& filepath);
|
||||
|
||||
private:
|
||||
std::atomic<bool> enabled = false;
|
||||
const std::string name;
|
||||
const std::vector<CheatLine> cheat_lines;
|
||||
const std::string comments;
|
||||
};
|
||||
} // namespace Cheats
|
|
@ -12,6 +12,7 @@
|
|||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#endif
|
||||
#include "core/arm/dyncom/arm_dyncom.h"
|
||||
#include "core/cheats/cheats.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
|
@ -143,6 +144,7 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file
|
|||
}
|
||||
}
|
||||
Memory::SetCurrentPageTable(&kernel->GetCurrentProcess()->vm_manager.page_table);
|
||||
cheat_engine = std::make_unique<Cheats::CheatEngine>(*this);
|
||||
status = ResultStatus::Success;
|
||||
m_emu_window = &emu_window;
|
||||
m_filepath = filepath;
|
||||
|
@ -248,6 +250,14 @@ const Timing& System::CoreTiming() const {
|
|||
return *timing;
|
||||
}
|
||||
|
||||
Cheats::CheatEngine& System::CheatEngine() {
|
||||
return *cheat_engine;
|
||||
}
|
||||
|
||||
const Cheats::CheatEngine& System::CheatEngine() const {
|
||||
return *cheat_engine;
|
||||
}
|
||||
|
||||
void System::RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard> swkbd) {
|
||||
registered_swkbd = std::move(swkbd);
|
||||
}
|
||||
|
@ -271,6 +281,7 @@ void System::Shutdown() {
|
|||
#ifdef ENABLE_SCRIPTING
|
||||
rpc_server.reset();
|
||||
#endif
|
||||
cheat_engine.reset();
|
||||
service_manager.reset();
|
||||
dsp_core.reset();
|
||||
cpu_core.reset();
|
||||
|
|
|
@ -39,6 +39,10 @@ namespace Kernel {
|
|||
class KernelSystem;
|
||||
}
|
||||
|
||||
namespace Cheats {
|
||||
class CheatEngine;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Timing;
|
||||
|
@ -184,6 +188,12 @@ public:
|
|||
/// Gets a const reference to the timing system
|
||||
const Timing& CoreTiming() const;
|
||||
|
||||
/// Gets a reference to the cheat engine
|
||||
Cheats::CheatEngine& CheatEngine();
|
||||
|
||||
/// Gets a const reference to the cheat engine
|
||||
const Cheats::CheatEngine& CheatEngine() const;
|
||||
|
||||
PerfStats perf_stats;
|
||||
FrameLimiter frame_limiter;
|
||||
|
||||
|
@ -244,6 +254,9 @@ private:
|
|||
/// Frontend applets
|
||||
std::shared_ptr<Frontend::SoftwareKeyboard> registered_swkbd;
|
||||
|
||||
/// Cheats manager
|
||||
std::unique_ptr<Cheats::CheatEngine> cheat_engine;
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
/// RPC Server for scripting support
|
||||
std::unique_ptr<RPC::RPCServer> rpc_server;
|
||||
|
|
|
@ -74,7 +74,6 @@ void Module::UpdatePadCallback(u64 userdata, s64 cycles_late) {
|
|||
if (is_device_reload_pending.exchange(false))
|
||||
LoadInputDevices();
|
||||
|
||||
PadState state;
|
||||
using namespace Settings::NativeButton;
|
||||
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
@ -394,6 +393,10 @@ void Module::ReloadInputDevices() {
|
|||
is_device_reload_pending.store(true);
|
||||
}
|
||||
|
||||
const PadState& Module::GetState() const {
|
||||
return state;
|
||||
}
|
||||
|
||||
std::shared_ptr<Module> GetModule(Core::System& system) {
|
||||
auto hid = system.ServiceManager().GetService<Service::HID::Module::Interface>("hid:USER");
|
||||
if (!hid)
|
||||
|
|
|
@ -299,6 +299,8 @@ public:
|
|||
|
||||
void ReloadInputDevices();
|
||||
|
||||
const PadState& GetState() const;
|
||||
|
||||
private:
|
||||
void LoadInputDevices();
|
||||
void UpdatePadCallback(u64 userdata, s64 cycles_late);
|
||||
|
@ -317,6 +319,10 @@ private:
|
|||
Kernel::SharedPtr<Kernel::Event> event_gyroscope;
|
||||
Kernel::SharedPtr<Kernel::Event> event_debug_pad;
|
||||
|
||||
// The HID module of a 3DS does not store the PadState.
|
||||
// Storing this here was necessary for emulation specific tasks like cheats or scripting.
|
||||
PadState state;
|
||||
|
||||
u32 next_pad_index = 0;
|
||||
u32 next_touch_index = 0;
|
||||
u32 next_accelerometer_index = 0;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue