Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/core/cheats.cpp
7365 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]> and contributors.
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "cheats.h"
5
#include "achievements.h"
6
#include "bus.h"
7
#include "controller.h"
8
#include "core.h"
9
#include "cpu_core.h"
10
#include "game_database.h"
11
#include "host.h"
12
#include "system.h"
13
14
#include "util/imgui_manager.h"
15
#include "util/translation.h"
16
17
#include "common/assert.h"
18
#include "common/error.h"
19
#include "common/file_system.h"
20
#include "common/log.h"
21
#include "common/path.h"
22
#include "common/settings_interface.h"
23
#include "common/small_string.h"
24
#include "common/string_util.h"
25
#include "common/zip_helpers.h"
26
27
#include "IconsEmoji.h"
28
#include "IconsFontAwesome.h"
29
#include "fmt/format.h"
30
31
LOG_CHANNEL(Cheats);
32
33
namespace {
34
class CheatFileReader
35
{
36
public:
37
explicit CheatFileReader(const std::string_view contents) : m_contents(contents) {}
38
39
ALWAYS_INLINE size_t GetCurrentOffset() const { return m_current_offset; }
40
ALWAYS_INLINE size_t GetCurrentLineOffset() const { return m_current_line_offset; }
41
ALWAYS_INLINE u32 GetCurrentLineNumber() const { return m_current_line_number; }
42
43
bool GetLine(std::string_view* line)
44
{
45
const size_t length = m_contents.length();
46
if (m_current_offset == length)
47
{
48
m_current_line_offset = m_current_offset;
49
return false;
50
}
51
52
size_t end_position = m_current_offset;
53
for (; end_position < length; end_position++)
54
{
55
// ignore carriage returns
56
if (m_contents[end_position] == '\r')
57
continue;
58
59
if (m_contents[end_position] == '\n')
60
break;
61
}
62
63
m_current_line_number++;
64
m_current_line_offset = m_current_offset;
65
*line = m_contents.substr(m_current_offset, end_position - m_current_offset);
66
m_current_offset = std::min(end_position + 1, length);
67
return true;
68
}
69
70
std::optional<std::string_view> GetLine()
71
{
72
std::optional<std::string_view> ret = std::string_view();
73
if (!GetLine(&ret.value()))
74
ret.reset();
75
return ret;
76
}
77
78
template<typename... T>
79
bool LogError(Error* error, bool stop_on_error, fmt::format_string<T...> fmt, T&&... args)
80
{
81
if (!stop_on_error)
82
{
83
Log::WriteFmtArgs(Log::PackCategory(Log::Channel::Cheats, Log::Level::Warning, Log::Color::StrongOrange), fmt,
84
fmt::make_format_args(args...));
85
return true;
86
}
87
88
if (error)
89
error->SetString(fmt::vformat(fmt, fmt::make_format_args(args...)));
90
91
return false;
92
}
93
94
private:
95
const std::string_view m_contents;
96
size_t m_current_offset = 0;
97
size_t m_current_line_offset = 0;
98
u32 m_current_line_number = 0;
99
};
100
101
class CheatArchive
102
{
103
public:
104
~CheatArchive()
105
{
106
// zip has to be destroyed before data
107
m_zip.reset();
108
m_data.deallocate();
109
}
110
111
ALWAYS_INLINE bool IsOpen() const { return static_cast<bool>(m_zip); }
112
113
bool Open(bool cheats)
114
{
115
if (m_zip)
116
return true;
117
118
#ifndef __ANDROID__
119
const char* name = cheats ? "cheats.zip" : "patches.zip";
120
#else
121
const char* name = cheats ? "patchcodes.zip" : "patches.zip";
122
#endif
123
124
Error error;
125
std::optional<DynamicHeapArray<u8>> data = Host::ReadResourceFile(name, false, &error);
126
if (!data.has_value())
127
{
128
ERROR_LOG("Failed to read cheat archive {}: {}", name, error.GetDescription());
129
return false;
130
}
131
132
m_data = std::move(data.value());
133
m_zip = ZipHelpers::OpenManagedZipBuffer(m_data.data(), m_data.size(), 0, false, &error);
134
if (!m_zip) [[unlikely]]
135
{
136
ERROR_LOG("Failed to open cheat archive {}: {}", name, error.GetDescription());
137
return false;
138
}
139
140
return true;
141
}
142
143
std::optional<std::string> ReadFile(const char* name) const
144
{
145
Error error;
146
std::optional<std::string> ret = ZipHelpers::ReadFileInZipToString(m_zip.get(), name, true, &error);
147
if (!ret.has_value())
148
DEV_LOG("Failed to read {} from zip: {}", name, error.GetDescription());
149
return ret;
150
}
151
152
private:
153
// Maybe counter-intuitive, but it ends up faster for reading a single game's cheats if we keep a
154
// copy of the archive in memory, as opposed to reading from disk.
155
DynamicHeapArray<u8> m_data;
156
ZipHelpers::ManagedZipT m_zip;
157
};
158
159
} // namespace
160
161
namespace Cheats {
162
163
namespace {
164
/// Represents a cheat code, after being parsed.
165
class CheatCode
166
{
167
public:
168
/// Additional metadata to a cheat code, present for all types.
169
struct Metadata
170
{
171
std::string name;
172
CodeType type = CodeType::Gameshark;
173
CodeActivation activation = CodeActivation::EndFrame;
174
std::optional<u32> override_cpu_overclock;
175
std::optional<DisplayAspectRatio> override_aspect_ratio;
176
bool has_options : 1;
177
bool disable_widescreen_rendering : 1;
178
bool enable_8mb_ram : 1;
179
bool disallow_for_achievements : 1;
180
};
181
182
public:
183
explicit CheatCode(Metadata metadata);
184
virtual ~CheatCode();
185
186
ALWAYS_INLINE const Metadata& GetMetadata() const { return m_metadata; }
187
ALWAYS_INLINE const std::string& GetName() const { return m_metadata.name; }
188
ALWAYS_INLINE CodeActivation GetActivation() const { return m_metadata.activation; }
189
ALWAYS_INLINE bool IsManuallyActivated() const { return (m_metadata.activation == CodeActivation::Manual); }
190
ALWAYS_INLINE bool HasOptions() const { return m_metadata.has_options; }
191
192
bool HasAnySettingOverrides() const;
193
void ApplySettingOverrides();
194
195
virtual void SetOptionValue(u32 value) = 0;
196
197
virtual void Apply() const = 0;
198
virtual void ApplyOnDisable() const = 0;
199
200
protected:
201
Metadata m_metadata;
202
};
203
} // namespace
204
205
using CheatCodeList = std::vector<std::unique_ptr<CheatCode>>;
206
using ActiveCodeList = std::vector<const CheatCode*>;
207
using EnableCodeList = std::vector<std::string>;
208
209
static std::string GetChtTemplate(const std::string_view serial, std::optional<GameHash> hash, bool add_wildcard);
210
static std::vector<std::string> FindChtFilesOnDisk(const std::string_view serial, std::optional<GameHash> hash,
211
bool cheats);
212
static bool ExtractCodeInfo(CodeInfoList* dst, const std::string_view file_data, bool from_database, bool stop_on_error,
213
Error* error);
214
static void AppendCheatToList(CodeInfoList* dst, CodeInfo code);
215
216
static bool ShouldLoadDatabaseCheats();
217
static std::optional<DisplayAspectRatio> GetWantedAspectRatio();
218
static bool AreAnyPatchesEnabled();
219
static void ReloadEnabledLists();
220
static u32 EnablePatches(const CheatCodeList& patches, const EnableCodeList& enable_list, const char* section,
221
bool hc_mode_active);
222
static bool EnableWidescreenPatch(const CheatCodeList& patches, bool hc_mode_active, const DisplayAspectRatio& ar);
223
static void UpdateActiveCodes(bool reload_enabled_list, bool verbose, bool verbose_if_changed,
224
bool show_disabled_codes);
225
226
template<typename F>
227
static bool SearchCheatArchive(CheatArchive& archive, std::string_view serial, std::optional<GameHash> hash,
228
const F& f);
229
230
template<typename F>
231
static void EnumerateChtFiles(const std::string_view serial, std::optional<GameHash> hash, bool cheats, bool for_ui,
232
bool load_from_disk, bool load_from_database, const F& f);
233
234
static std::optional<CodeOption> ParseOption(const std::string_view value);
235
static bool ParseOptionRange(const std::string_view value, u16* out_range_start, u16* out_range_end);
236
static void ParseFile(CheatCodeList* dst_list, const std::string_view file_contents);
237
static std::unique_ptr<CheatCode> ParseCode(CheatCode::Metadata metadata, const std::string_view data, Error* error);
238
239
static Cheats::FileFormat DetectFileFormat(const std::string_view file_contents);
240
static bool ImportPCSXFile(CodeInfoList* dst, const std::string_view file_contents, bool stop_on_error, Error* error);
241
static bool ImportLibretroFile(CodeInfoList* dst, const std::string_view file_contents, bool stop_on_error,
242
Error* error);
243
static bool ImportEPSXeFile(CodeInfoList* dst, const std::string_view file_contents, bool stop_on_error, Error* error);
244
static bool ImportOldChtFile(const std::string_view serial);
245
246
const char* PATCHES_CONFIG_SECTION = "Patches";
247
const char* CHEATS_CONFIG_SECTION = "Cheats";
248
const char* PATCH_ENABLE_CONFIG_KEY = "Enable";
249
250
namespace {
251
struct Locals
252
{
253
CheatCodeList patch_codes;
254
CheatCodeList cheat_codes;
255
EnableCodeList enabled_cheats;
256
EnableCodeList enabled_patches;
257
258
ActiveCodeList frame_end_codes;
259
260
u32 active_patch_count = 0;
261
u32 active_cheat_count = 0;
262
bool patches_enabled = false;
263
bool cheats_enabled = false;
264
bool has_widescreen_patch = false;
265
bool database_cheat_codes_enabled = false;
266
};
267
268
struct ArchiveLocals
269
{
270
std::mutex zip_mutex;
271
CheatArchive patches_zip;
272
CheatArchive cheats_zip;
273
};
274
} // namespace
275
276
ALIGN_TO_CACHE_LINE static Locals s_locals;
277
ALIGN_TO_CACHE_LINE static ArchiveLocals s_archive_locals;
278
279
} // namespace Cheats
280
281
Cheats::CheatCode::CheatCode(Metadata metadata) : m_metadata(std::move(metadata))
282
{
283
}
284
285
Cheats::CheatCode::~CheatCode() = default;
286
287
bool Cheats::CheatCode::HasAnySettingOverrides() const
288
{
289
return (m_metadata.disable_widescreen_rendering || m_metadata.enable_8mb_ram ||
290
m_metadata.override_aspect_ratio.has_value() || m_metadata.override_cpu_overclock.has_value());
291
}
292
293
void Cheats::CheatCode::ApplySettingOverrides()
294
{
295
if (m_metadata.disable_widescreen_rendering && g_settings.gpu_widescreen_hack)
296
{
297
DEV_LOG("Disabling widescreen rendering from {} patch.", GetName());
298
g_settings.gpu_widescreen_hack = false;
299
}
300
if (m_metadata.enable_8mb_ram && !g_settings.cpu_enable_8mb_ram)
301
{
302
DEV_LOG("Enabling 8MB ram from {} patch.", GetName());
303
g_settings.cpu_enable_8mb_ram = true;
304
}
305
if (m_metadata.override_aspect_ratio.has_value() && g_settings.display_aspect_ratio == DisplayAspectRatio::Auto())
306
{
307
DEV_LOG("Setting aspect ratio to {} from {} patch.",
308
Settings::GetDisplayAspectRatioName(m_metadata.override_aspect_ratio.value()), GetName());
309
g_settings.display_aspect_ratio = m_metadata.override_aspect_ratio.value();
310
}
311
if (m_metadata.override_cpu_overclock.has_value() && !g_settings.cpu_overclock_active)
312
{
313
DEV_LOG("Setting CPU overclock to {} from {} patch.", m_metadata.override_cpu_overclock.value(), GetName());
314
g_settings.SetCPUOverclockPercent(m_metadata.override_cpu_overclock.value());
315
g_settings.cpu_overclock_enable = true;
316
g_settings.UpdateOverclockActive();
317
}
318
}
319
320
static std::array<const char*, 1> s_cheat_code_type_names = {{"Gameshark"}};
321
static std::array<const char*, 1> s_cheat_code_type_display_names{{TRANSLATE_NOOP("Cheats", "Gameshark")}};
322
323
const char* Cheats::GetTypeName(CodeType type)
324
{
325
return s_cheat_code_type_names[static_cast<u32>(type)];
326
}
327
328
const char* Cheats::GetTypeDisplayName(CodeType type)
329
{
330
return TRANSLATE("Cheats", s_cheat_code_type_display_names[static_cast<u32>(type)]);
331
}
332
333
std::optional<Cheats::CodeType> Cheats::ParseTypeName(const std::string_view str)
334
{
335
for (size_t i = 0; i < s_cheat_code_type_names.size(); i++)
336
{
337
if (str == s_cheat_code_type_names[i])
338
return static_cast<CodeType>(i);
339
}
340
341
return std::nullopt;
342
}
343
344
static std::array<const char*, 2> s_cheat_code_activation_names = {{"Manual", "EndFrame"}};
345
static std::array<const char*, 2> s_cheat_code_activation_display_names{
346
{TRANSLATE_NOOP("Cheats", "Manual"), TRANSLATE_NOOP("Cheats", "Automatic (Frame End)")}};
347
348
const char* Cheats::GetActivationName(CodeActivation activation)
349
{
350
return s_cheat_code_activation_names[static_cast<u32>(activation)];
351
}
352
353
const char* Cheats::GetActivationDisplayName(CodeActivation activation)
354
{
355
return TRANSLATE("Cheats", s_cheat_code_activation_display_names[static_cast<u32>(activation)]);
356
}
357
358
std::optional<Cheats::CodeActivation> Cheats::ParseActivationName(const std::string_view str)
359
{
360
for (u32 i = 0; i < static_cast<u32>(s_cheat_code_activation_names.size()); i++)
361
{
362
if (str == s_cheat_code_activation_names[i])
363
return static_cast<CodeActivation>(i);
364
}
365
366
return std::nullopt;
367
}
368
369
std::string Cheats::GetChtTemplate(const std::string_view serial, std::optional<GameHash> hash, bool add_wildcard)
370
{
371
if (!hash.has_value())
372
return fmt::format("{}{}.cht", serial, add_wildcard ? "*" : "");
373
else
374
return fmt::format("{}_{:016X}{}.cht", serial, hash.value(), add_wildcard ? "*" : "");
375
}
376
377
std::vector<std::string> Cheats::FindChtFilesOnDisk(const std::string_view serial, std::optional<GameHash> hash,
378
bool cheats)
379
{
380
std::vector<std::string> ret;
381
FileSystem::FindResultsArray files;
382
FileSystem::FindFiles(cheats ? EmuFolders::Cheats.c_str() : EmuFolders::Patches.c_str(),
383
GetChtTemplate(serial, std::nullopt, true).c_str(),
384
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, &files);
385
ret.reserve(files.size());
386
387
for (FILESYSTEM_FIND_DATA& fd : files)
388
{
389
// Skip mismatched hashes.
390
if (hash.has_value())
391
{
392
if (const std::string_view filename = Path::GetFileTitle(fd.FileName); filename.length() >= serial.length() + 17)
393
{
394
const std::string_view filename_hash = filename.substr(serial.length() + 1, 16);
395
const std::optional filename_parsed_hash = StringUtil::FromChars<GameHash>(filename_hash, 16);
396
if (filename_parsed_hash.has_value() && filename_parsed_hash.value() != hash.value())
397
continue;
398
}
399
}
400
ret.push_back(std::move(fd.FileName));
401
}
402
403
return ret;
404
}
405
406
template<typename F>
407
bool Cheats::SearchCheatArchive(CheatArchive& archive, std::string_view serial, std::optional<GameHash> hash,
408
const F& f)
409
{
410
// Prefer filename with hash.
411
std::string zip_filename = GetChtTemplate(serial, hash, false);
412
std::optional<std::string> data = archive.ReadFile(zip_filename.c_str());
413
if (!data.has_value() && hash.has_value())
414
{
415
// Try without the hash.
416
zip_filename = GetChtTemplate(serial, std::nullopt, false);
417
data = archive.ReadFile(zip_filename.c_str());
418
}
419
if (data.has_value())
420
{
421
f(zip_filename, std::move(data.value()), true);
422
return true;
423
}
424
425
return false;
426
}
427
428
template<typename F>
429
void Cheats::EnumerateChtFiles(const std::string_view serial, std::optional<GameHash> hash, bool cheats, bool for_ui,
430
bool load_from_files, bool load_from_database, const F& f)
431
{
432
// Prefer files on disk over the zip, so we have to load the zip first.
433
if (load_from_database)
434
{
435
const std::unique_lock lock(s_archive_locals.zip_mutex);
436
CheatArchive& archive = cheats ? s_archive_locals.cheats_zip : s_archive_locals.patches_zip;
437
if (!archive.IsOpen())
438
archive.Open(cheats);
439
440
if (archive.IsOpen())
441
{
442
if (!SearchCheatArchive(archive, serial, hash, f))
443
{
444
// Is this game part of a disc set? Try codes for the other discs.
445
const GameDatabase::Entry* gentry = GameDatabase::GetEntryForSerial(serial);
446
if (gentry && gentry->disc_set)
447
{
448
for (const std::string_view& set_serial : gentry->disc_set->serials)
449
{
450
if (set_serial == serial)
451
continue;
452
else if (SearchCheatArchive(archive, set_serial, std::nullopt, f))
453
break;
454
}
455
}
456
}
457
}
458
}
459
460
if (load_from_files)
461
{
462
std::vector<std::string> disk_patch_files;
463
if (for_ui || !Achievements::IsHardcoreModeActive())
464
{
465
disk_patch_files = FindChtFilesOnDisk(serial, hash, cheats);
466
if (cheats && disk_patch_files.empty())
467
{
468
// Check if there's an old-format titled file.
469
if (ImportOldChtFile(serial))
470
disk_patch_files = FindChtFilesOnDisk(serial, hash, cheats);
471
}
472
473
if (disk_patch_files.empty())
474
{
475
// Is this game part of a disc set? Try codes for the other discs.
476
const GameDatabase::Entry* gentry = GameDatabase::GetEntryForSerial(serial);
477
if (gentry && gentry->disc_set)
478
{
479
for (const std::string_view& set_serial : gentry->disc_set->serials)
480
{
481
if (set_serial == serial)
482
continue;
483
484
disk_patch_files = FindChtFilesOnDisk(set_serial, std::nullopt, cheats);
485
if (!disk_patch_files.empty())
486
break;
487
}
488
}
489
}
490
}
491
492
Error error;
493
if (!disk_patch_files.empty())
494
{
495
for (const std::string& file : disk_patch_files)
496
{
497
const std::optional<std::string> contents = FileSystem::ReadFileToString(file.c_str(), &error);
498
if (contents.has_value())
499
f(file, std::move(contents.value()), false);
500
else
501
WARNING_LOG("Failed to read cht file '{}': {}", Path::GetFileName(file), error.GetDescription());
502
}
503
}
504
}
505
}
506
507
std::string_view Cheats::CodeInfo::GetNamePart() const
508
{
509
const std::string::size_type pos = name.rfind('\\');
510
std::string_view ret = name;
511
if (pos != std::string::npos)
512
ret = ret.substr(pos + 1);
513
return ret;
514
}
515
516
std::string_view Cheats::CodeInfo::GetNameParentPart() const
517
{
518
const std::string::size_type pos = name.rfind('\\');
519
std::string_view ret;
520
if (pos != std::string::npos)
521
ret = std::string_view(name).substr(0, pos);
522
return ret;
523
}
524
525
std::string_view Cheats::CodeInfo::MapOptionValueToName(u32 value) const
526
{
527
std::string_view ret;
528
if (!options.empty())
529
ret = options.front().first;
530
531
for (const Cheats::CodeOption& opt : options)
532
{
533
if (opt.second == value)
534
{
535
ret = opt.first;
536
break;
537
}
538
}
539
540
return ret;
541
}
542
543
std::string_view Cheats::CodeInfo::MapOptionValueToName(const std::string_view value) const
544
{
545
const std::optional<u32> value_uint = StringUtil::FromChars<u32>(value);
546
return MapOptionValueToName(value_uint.value_or(options.empty() ? 0 : options.front().second));
547
}
548
549
u32 Cheats::CodeInfo::MapOptionNameToValue(const std::string_view opt_name) const
550
{
551
for (const Cheats::CodeOption& opt : options)
552
{
553
if (opt.first == opt_name)
554
return opt.second;
555
}
556
557
return options.empty() ? 0 : options.front().second;
558
}
559
560
Cheats::CodeInfoList Cheats::GetCodeInfoList(const std::string_view serial, std::optional<GameHash> hash, bool cheats,
561
bool load_from_database, bool sort_by_name)
562
{
563
CodeInfoList ret;
564
565
EnumerateChtFiles(serial, hash, cheats, true, true, load_from_database,
566
[&ret](const std::string& filename, const std::string& data, bool from_database) {
567
ExtractCodeInfo(&ret, data, from_database, false, nullptr);
568
});
569
570
if (sort_by_name)
571
{
572
std::sort(ret.begin(), ret.end(), [](const CodeInfo& lhs, const CodeInfo& rhs) {
573
// ungrouped cheats go together first
574
if (const int lhs_group = static_cast<int>(lhs.name.find('\\') != std::string::npos),
575
rhs_group = static_cast<int>(rhs.name.find('\\') != std::string::npos);
576
lhs_group != rhs_group)
577
{
578
return (lhs_group < rhs_group);
579
}
580
581
// sort special characters first
582
static constexpr auto is_special = [](char ch) {
583
return !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') ||
584
(ch >= 0x0A && ch <= 0x0D));
585
};
586
if (const int lhs_is_special = static_cast<int>(!lhs.name.empty() && is_special(lhs.name.front())),
587
rhs_is_special = static_cast<int>(!rhs.name.empty() && is_special(rhs.name.front()));
588
lhs_is_special != rhs_is_special)
589
{
590
return (lhs_is_special > rhs_is_special);
591
}
592
593
return lhs.name < rhs.name;
594
});
595
}
596
597
return ret;
598
}
599
600
std::vector<std::string_view> Cheats::GetCodeListUniquePrefixes(const CodeInfoList& list, bool include_empty)
601
{
602
std::vector<std::string_view> ret;
603
for (const Cheats::CodeInfo& code : list)
604
{
605
const std::string_view prefix = code.GetNameParentPart();
606
if (prefix.empty())
607
{
608
if (include_empty && (ret.empty() || !ret.front().empty()))
609
ret.insert(ret.begin(), std::string_view());
610
611
continue;
612
}
613
614
if (std::find(ret.begin(), ret.end(), prefix) == ret.end())
615
ret.push_back(prefix);
616
}
617
return ret;
618
}
619
620
const Cheats::CodeInfo* Cheats::FindCodeInInfoList(const CodeInfoList& list, const std::string_view name)
621
{
622
const auto it = std::find_if(list.cbegin(), list.cend(), [&name](const CodeInfo& rhs) { return name == rhs.name; });
623
return (it != list.end()) ? &(*it) : nullptr;
624
}
625
626
Cheats::CodeInfo* Cheats::FindCodeInInfoList(CodeInfoList& list, const std::string_view name)
627
{
628
const auto it = std::find_if(list.begin(), list.end(), [&name](const CodeInfo& rhs) { return name == rhs.name; });
629
return (it != list.end()) ? &(*it) : nullptr;
630
}
631
632
std::string Cheats::FormatCodeForFile(const CodeInfo& code)
633
{
634
fmt::memory_buffer buf;
635
auto appender = std::back_inserter(buf);
636
fmt::format_to(appender, "[{}]\n", code.name);
637
if (!code.author.empty())
638
fmt::format_to(appender, "Author = {}\n", code.author);
639
if (!code.description.empty())
640
fmt::format_to(appender, "Description = {}\n", code.description);
641
fmt::format_to(appender, "Type = {}\n", GetTypeName(code.type));
642
fmt::format_to(appender, "Activation = {}\n", GetActivationName(code.activation));
643
if (code.HasOptionChoices())
644
{
645
for (const CodeOption& opt : code.options)
646
fmt::format_to(appender, "Option = {}:{}\n", opt.first, opt.second);
647
}
648
else if (code.HasOptionRange())
649
{
650
fmt::format_to(appender, "OptionRange = {}:{}\n", code.option_range_start, code.option_range_end);
651
}
652
653
// remove trailing whitespace
654
std::string_view code_body = code.body;
655
while (!code_body.empty() && StringUtil::IsWhitespace(code_body.back()))
656
code_body = code_body.substr(0, code_body.length() - 1);
657
if (!code_body.empty())
658
buf.append(code_body);
659
660
buf.push_back('\n');
661
return std::string(buf.begin(), buf.end());
662
}
663
664
bool Cheats::UpdateCodeInFile(const char* path, const std::string_view name, const CodeInfo* code, Error* error)
665
{
666
std::string file_contents;
667
if (FileSystem::FileExists(path))
668
{
669
std::optional<std::string> ofile_contents = FileSystem::ReadFileToString(path, error);
670
if (!ofile_contents.has_value())
671
{
672
Error::AddPrefix(error, "Failed to read existing file: ");
673
return false;
674
}
675
file_contents = std::move(ofile_contents.value());
676
}
677
678
// This is a bit crap, we're allocating everything and then tossing it away.
679
// Hopefully it won't fragment too much at least, because it's freed in reverse order...
680
std::optional<size_t> replace_start, replace_end;
681
if (!file_contents.empty() && !name.empty())
682
{
683
CodeInfoList existing_codes_in_file;
684
ExtractCodeInfo(&existing_codes_in_file, file_contents, false, false, nullptr);
685
686
const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, name);
687
if (existing_code)
688
{
689
replace_start = existing_code->file_offset_start;
690
replace_end = existing_code->file_offset_end;
691
}
692
}
693
694
if (replace_start.has_value())
695
{
696
const auto start = file_contents.begin() + replace_start.value();
697
const auto end = file_contents.begin() + replace_end.value();
698
if (code)
699
file_contents.replace(start, end, FormatCodeForFile(*code));
700
else
701
file_contents.erase(start, end);
702
}
703
else if (code)
704
{
705
const std::string code_body = FormatCodeForFile(*code);
706
file_contents.reserve(file_contents.length() + 1 + code_body.length());
707
while (!file_contents.empty() && StringUtil::IsWhitespace(file_contents.back()))
708
file_contents.pop_back();
709
if (!file_contents.empty())
710
file_contents.append("\n\n");
711
file_contents.append(code_body);
712
}
713
714
INFO_LOG("Updating {}...", path);
715
if (!FileSystem::WriteStringToFile(path, file_contents, error))
716
{
717
Error::AddPrefix(error, "Failed to rewrite file: ");
718
return false;
719
}
720
721
return true;
722
}
723
724
bool Cheats::SaveCodesToFile(const char* path, const CodeInfoList& codes, Error* error)
725
{
726
std::string file_contents;
727
if (FileSystem::FileExists(path))
728
{
729
std::optional<std::string> ofile_contents = FileSystem::ReadFileToString(path, error);
730
if (!ofile_contents.has_value())
731
{
732
Error::AddPrefix(error, "Failed to read existing file: ");
733
return false;
734
}
735
file_contents = std::move(ofile_contents.value());
736
}
737
738
for (const CodeInfo& code : codes)
739
{
740
// This is _really_ crap.. but it's only on importing.
741
std::optional<size_t> replace_start, replace_end;
742
if (!file_contents.empty())
743
{
744
CodeInfoList existing_codes_in_file;
745
ExtractCodeInfo(&existing_codes_in_file, file_contents, false, false, nullptr);
746
747
const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, code.name);
748
if (existing_code)
749
{
750
replace_start = existing_code->file_offset_start;
751
replace_end = existing_code->file_offset_end;
752
}
753
}
754
755
if (replace_start.has_value())
756
{
757
const auto start = file_contents.begin() + replace_start.value();
758
const auto end = file_contents.begin() + replace_end.value();
759
file_contents.replace(start, end, FormatCodeForFile(code));
760
}
761
else
762
{
763
const std::string code_body = FormatCodeForFile(code);
764
file_contents.reserve(file_contents.length() + 1 + code_body.length());
765
while (!file_contents.empty() && StringUtil::IsWhitespace(file_contents.back()))
766
file_contents.pop_back();
767
if (!file_contents.empty())
768
file_contents.append("\n\n");
769
file_contents.append(code_body);
770
}
771
}
772
773
INFO_LOG("Updating {}...", path);
774
if (!FileSystem::WriteStringToFile(path, file_contents, error))
775
{
776
Error::AddPrefix(error, "Failed to rewrite file: ");
777
return false;
778
}
779
780
return true;
781
}
782
783
void Cheats::RemoveAllCodes(const std::string_view serial, const std::string_view title, std::optional<GameHash> hash)
784
{
785
Error error;
786
std::string path = GetChtFilename(serial, hash, true);
787
if (FileSystem::FileExists(path.c_str()))
788
{
789
if (!FileSystem::DeleteFile(path.c_str(), &error))
790
ERROR_LOG("Failed to remove cht file '{}': {}", Path::GetFileName(path), error.GetDescription());
791
}
792
793
// check for a non-hashed path and remove that too
794
path = GetChtFilename(serial, std::nullopt, true);
795
if (FileSystem::FileExists(path.c_str()))
796
{
797
if (!FileSystem::DeleteFile(path.c_str(), &error))
798
ERROR_LOG("Failed to remove cht file '{}': {}", Path::GetFileName(path), error.GetDescription());
799
}
800
801
// and a legacy cht file with the game title
802
if (!title.empty())
803
{
804
path = fmt::format("{}" FS_OSPATH_SEPARATOR_STR "{}.cht", EmuFolders::Cheats, Path::SanitizeFileName(title));
805
if (FileSystem::FileExists(path.c_str()))
806
{
807
if (!FileSystem::DeleteFile(path.c_str(), &error))
808
ERROR_LOG("Failed to remove cht file '{}': {}", Path::GetFileName(path), error.GetDescription());
809
}
810
}
811
}
812
813
bool Cheats::ValidateCodeBody(std::string_view name, CodeType type, CodeActivation activation, std::string_view body,
814
Error* error)
815
{
816
// don't need the full metadata, only enough to get through
817
CheatCode::Metadata metadata = {};
818
metadata.name = name;
819
metadata.type = type;
820
metadata.activation = activation;
821
822
std::unique_ptr<CheatCode> code = ParseCode(std::move(metadata), body, error);
823
return static_cast<bool>(code);
824
}
825
826
bool Cheats::ValidateCodeBody(const CodeInfo& code, Error* error)
827
{
828
return ValidateCodeBody(code.name, code.type, code.activation, code.body, error);
829
}
830
831
std::string Cheats::GetChtFilename(const std::string_view serial, std::optional<GameHash> hash, bool cheats)
832
{
833
return Path::Combine(cheats ? EmuFolders::Cheats : EmuFolders::Patches, GetChtTemplate(serial, hash, false));
834
}
835
836
bool Cheats::AreCheatsEnabled()
837
{
838
if (Achievements::IsHardcoreModeActive() || g_settings.disable_all_enhancements)
839
return false;
840
841
// Only in the gameini.
842
const SettingsInterface* sif = Core::GetGameSettingsLayer();
843
return (sif && sif->GetBoolValue("Cheats", "EnableCheats", false));
844
}
845
846
bool Cheats::ShouldLoadDatabaseCheats()
847
{
848
// Only in the gameini.
849
const SettingsInterface* sif = Core::GetGameSettingsLayer();
850
return (sif && sif->GetBoolValue("Cheats", "LoadCheatsFromDatabase", true));
851
}
852
853
std::optional<DisplayAspectRatio> Cheats::GetWantedAspectRatio()
854
{
855
if (!g_settings.gpu_widescreen_rendering)
856
return std::nullopt;
857
858
const DisplayAspectRatio ar = System::GetConfigurationAspectRatio();
859
return (ar.IsValid() && ar != DisplayAspectRatio{4, 3}) ? std::optional<DisplayAspectRatio>(ar) : std::nullopt;
860
}
861
862
bool Cheats::AreAnyPatchesEnabled()
863
{
864
if (g_settings.disable_all_enhancements)
865
return false;
866
867
// Look for widescreen patches.
868
if (GetWantedAspectRatio().has_value())
869
return true;
870
871
// Only in the gameini.
872
const SettingsInterface* sif = Core::GetGameSettingsLayer();
873
return (sif && sif->ContainsValue("Patches", "Enable"));
874
}
875
876
void Cheats::ReloadEnabledLists()
877
{
878
const SettingsInterface* sif = Core::GetGameSettingsLayer();
879
if (!sif)
880
{
881
// no gameini => nothing is going to be enabled.
882
s_locals.enabled_cheats = {};
883
s_locals.enabled_patches = {};
884
return;
885
}
886
887
if (AreCheatsEnabled())
888
s_locals.enabled_cheats = sif->GetStringList(CHEATS_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY);
889
else
890
s_locals.enabled_cheats = {};
891
892
s_locals.enabled_patches = sif->GetStringList(PATCHES_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY);
893
}
894
895
u32 Cheats::EnablePatches(const CheatCodeList& patches, const EnableCodeList& enable_list, const char* section,
896
bool hc_mode_active)
897
{
898
u32 count = 0;
899
for (const std::unique_ptr<CheatCode>& p : patches)
900
{
901
// ignore manually-activated codes
902
if (p->IsManuallyActivated())
903
continue;
904
905
// don't load banned patches
906
if (p->GetMetadata().disallow_for_achievements && hc_mode_active)
907
continue;
908
909
if (std::find(enable_list.begin(), enable_list.end(), p->GetName()) == enable_list.end())
910
continue;
911
912
INFO_LOG("Enabled code from {}: {}", section, p->GetName());
913
914
switch (p->GetActivation())
915
{
916
case CodeActivation::EndFrame:
917
s_locals.frame_end_codes.push_back(p.get());
918
break;
919
920
default:
921
break;
922
}
923
924
if (p->HasOptions())
925
{
926
// need to extract the option from the ini
927
SettingsInterface* sif = Core::GetGameSettingsLayer();
928
if (sif) [[likely]]
929
{
930
if (const std::optional<u32> value = sif->GetOptionalUIntValue(section, p->GetName().c_str(), std::nullopt))
931
{
932
DEV_LOG("Setting {} option value to 0x{:X}", p->GetName(), value.value());
933
p->SetOptionValue(value.value());
934
}
935
}
936
}
937
938
count++;
939
}
940
941
return count;
942
}
943
944
bool Cheats::EnableWidescreenPatch(const CheatCodeList& patches, bool hc_mode_active,
945
const DisplayAspectRatio& wanted_ar)
946
{
947
for (const std::unique_ptr<CheatCode>& p : patches)
948
{
949
// don't rely on the name, use the attribute instead
950
if (!p->GetMetadata().override_aspect_ratio.has_value() ||
951
p->GetMetadata().override_aspect_ratio.value() != wanted_ar)
952
{
953
continue;
954
}
955
956
// don't load banned patches
957
if (p->GetMetadata().disallow_for_achievements && hc_mode_active)
958
continue;
959
960
// already enabled?
961
if (std::find(s_locals.enabled_patches.begin(), s_locals.enabled_patches.end(), p->GetName()) !=
962
s_locals.enabled_patches.end())
963
{
964
return true;
965
}
966
967
INFO_LOG("Enabling widescreen patch: {}", p->GetName());
968
s_locals.enabled_patches.push_back(p->GetName());
969
return true;
970
}
971
972
WARNING_LOG("No widescreen patch found for aspect ratio {}.", Settings::GetDisplayAspectRatioName(wanted_ar));
973
return false;
974
}
975
976
void Cheats::ReloadCheats(bool reload_files, bool reload_enabled_list, bool verbose, bool verbose_if_changed,
977
bool show_disabled_codes)
978
{
979
for (const CheatCode* code : s_locals.frame_end_codes)
980
code->ApplyOnDisable();
981
982
// Reload files if cheats or patches are enabled, and they were not previously.
983
const bool patches_are_enabled = AreAnyPatchesEnabled();
984
const bool cheats_are_enabled = AreCheatsEnabled();
985
const bool cheatdb_is_enabled = cheats_are_enabled && ShouldLoadDatabaseCheats();
986
reload_files = reload_files || (s_locals.patches_enabled != patches_are_enabled);
987
reload_files = reload_files || (s_locals.cheats_enabled != cheats_are_enabled);
988
reload_files = reload_files || (s_locals.database_cheat_codes_enabled != cheatdb_is_enabled);
989
990
if (reload_files)
991
{
992
s_locals.patch_codes.clear();
993
s_locals.cheat_codes.clear();
994
995
if (const std::string& serial = System::GetGameSerial(); !serial.empty())
996
{
997
const GameHash hash = System::GetGameHash();
998
999
s_locals.patches_enabled = patches_are_enabled;
1000
if (patches_are_enabled)
1001
{
1002
EnumerateChtFiles(serial, hash, false, false, !Achievements::IsHardcoreModeActive(), true,
1003
[](const std::string& filename, const std::string& file_contents, bool from_database) {
1004
ParseFile(&s_locals.patch_codes, file_contents);
1005
if (s_locals.patch_codes.size() > 0)
1006
INFO_LOG("Found {} game patches in {}.", s_locals.patch_codes.size(), filename);
1007
});
1008
}
1009
1010
s_locals.cheats_enabled = cheats_are_enabled;
1011
s_locals.database_cheat_codes_enabled = cheatdb_is_enabled;
1012
if (cheats_are_enabled)
1013
{
1014
EnumerateChtFiles(serial, hash, true, false, true, cheatdb_is_enabled,
1015
[](const std::string& filename, const std::string& file_contents, bool from_database) {
1016
ParseFile(&s_locals.cheat_codes, file_contents);
1017
if (s_locals.cheat_codes.size() > 0)
1018
INFO_LOG("Found {} cheats in {}.", s_locals.cheat_codes.size(), filename);
1019
});
1020
}
1021
}
1022
}
1023
1024
UpdateActiveCodes(reload_enabled_list, verbose, verbose_if_changed, show_disabled_codes);
1025
1026
// Reapply frame end codes immediately. Otherwise you end up with a single frame where the old code is used.
1027
ApplyFrameEndCodes();
1028
}
1029
1030
void Cheats::UnloadAll()
1031
{
1032
s_locals.active_cheat_count = 0;
1033
s_locals.active_patch_count = 0;
1034
s_locals.frame_end_codes = ActiveCodeList();
1035
s_locals.enabled_patches = EnableCodeList();
1036
s_locals.enabled_cheats = EnableCodeList();
1037
s_locals.cheat_codes = CheatCodeList();
1038
s_locals.patch_codes = CheatCodeList();
1039
s_locals.patches_enabled = false;
1040
s_locals.cheats_enabled = false;
1041
s_locals.has_widescreen_patch = false;
1042
s_locals.database_cheat_codes_enabled = false;
1043
}
1044
1045
bool Cheats::HasAnySettingOverrides()
1046
{
1047
const bool hc_mode_active = Achievements::IsHardcoreModeActive();
1048
for (const std::string& name : s_locals.enabled_patches)
1049
{
1050
for (std::unique_ptr<CheatCode>& code : s_locals.patch_codes)
1051
{
1052
if (name == code->GetName())
1053
{
1054
if (!code->GetMetadata().disallow_for_achievements || !hc_mode_active)
1055
{
1056
if (code->HasAnySettingOverrides())
1057
return true;
1058
}
1059
1060
break;
1061
}
1062
}
1063
}
1064
1065
return false;
1066
}
1067
1068
void Cheats::ApplySettingOverrides()
1069
{
1070
// only need to check patches for this
1071
const bool hc_mode_active = Achievements::IsHardcoreModeActive();
1072
for (const std::string& name : s_locals.enabled_patches)
1073
{
1074
for (std::unique_ptr<CheatCode>& code : s_locals.patch_codes)
1075
{
1076
if (name == code->GetName())
1077
{
1078
if (!code->GetMetadata().disallow_for_achievements || !hc_mode_active)
1079
code->ApplySettingOverrides();
1080
1081
break;
1082
}
1083
}
1084
}
1085
}
1086
1087
void Cheats::UpdateActiveCodes(bool reload_enabled_list, bool verbose, bool verbose_if_changed,
1088
bool show_disabled_codes)
1089
{
1090
if (reload_enabled_list)
1091
ReloadEnabledLists();
1092
1093
const size_t prev_count = s_locals.frame_end_codes.size();
1094
s_locals.frame_end_codes.clear();
1095
1096
s_locals.active_patch_count = 0;
1097
s_locals.active_cheat_count = 0;
1098
1099
const bool hc_mode_active = Achievements::IsHardcoreModeActive();
1100
1101
if (!g_settings.disable_all_enhancements)
1102
{
1103
const std::optional<DisplayAspectRatio> wanted_ar = GetWantedAspectRatio();
1104
s_locals.has_widescreen_patch =
1105
wanted_ar.has_value() && EnableWidescreenPatch(s_locals.patch_codes, hc_mode_active, wanted_ar.value());
1106
s_locals.active_patch_count =
1107
EnablePatches(s_locals.patch_codes, s_locals.enabled_patches, "Patches", hc_mode_active);
1108
s_locals.active_cheat_count =
1109
AreCheatsEnabled() ? EnablePatches(s_locals.cheat_codes, s_locals.enabled_cheats, "Cheats", hc_mode_active) : 0;
1110
}
1111
1112
// Display message on first boot when we load patches.
1113
// Except when it's just GameDB.
1114
const size_t new_count = s_locals.frame_end_codes.size();
1115
if (verbose || (verbose_if_changed && prev_count != new_count))
1116
{
1117
if (s_locals.active_patch_count > 0)
1118
{
1119
System::SetTaint(System::Taint::Patches);
1120
Host::AddIconOSDMessage(
1121
OSDMessageType::Info, "LoadCheats", ICON_FA_BANDAGE,
1122
TRANSLATE_PLURAL_STR("Cheats", "%n game patches are active.", "OSD Message", s_locals.active_patch_count));
1123
}
1124
if (s_locals.active_cheat_count > 0)
1125
{
1126
System::SetTaint(System::Taint::Cheats);
1127
Host::AddIconOSDMessage(
1128
OSDMessageType::Warning, "LoadCheats", ICON_EMOJI_WARNING,
1129
TRANSLATE_PLURAL_STR("Cheats", "%n cheats are enabled.", "OSD Message", s_locals.active_cheat_count),
1130
TRANSLATE_STR("Cheats", "This may crash games."));
1131
}
1132
else if (s_locals.active_patch_count == 0)
1133
{
1134
Host::RemoveKeyedOSDMessage("LoadCheats");
1135
Host::AddIconOSDMessage(OSDMessageType::Info, "LoadCheats", ICON_FA_BANDAGE,
1136
TRANSLATE_STR("Cheats", "No cheats/patches are found or enabled."));
1137
}
1138
}
1139
1140
if (show_disabled_codes && (hc_mode_active || g_settings.disable_all_enhancements))
1141
{
1142
const SettingsInterface* sif = Core::GetGameSettingsLayer();
1143
const u32 requested_cheat_count = (sif && sif->GetBoolValue("Cheats", "EnableCheats", false)) ?
1144
static_cast<u32>(sif->GetStringList("Cheats", "Enable").size()) :
1145
0;
1146
const u32 requested_patches_count = sif ? static_cast<u32>(sif->GetStringList("Patches", "Enable").size()) : 0;
1147
const u32 blocked_cheats =
1148
(s_locals.active_cheat_count < requested_cheat_count) ? requested_cheat_count - s_locals.active_cheat_count : 0;
1149
const u32 blocked_patches = (s_locals.active_patch_count < requested_patches_count) ?
1150
requested_patches_count - s_locals.active_patch_count :
1151
0;
1152
if (blocked_cheats > 0 || blocked_patches > 0)
1153
{
1154
const SmallString blocked_cheats_msg =
1155
TRANSLATE_PLURAL_SSTR("Cheats", "%n cheats", "Cheats blocked by hardcore mode", blocked_cheats);
1156
const SmallString blocked_patches_msg =
1157
TRANSLATE_PLURAL_SSTR("Cheats", "%n patches", "Patches blocked by hardcore mode", blocked_patches);
1158
std::string message =
1159
(blocked_cheats > 0 && blocked_patches > 0) ?
1160
fmt::format(TRANSLATE_FS("Cheats", "{0} and {1} disabled by achievements hardcore mode/safe mode."),
1161
blocked_cheats_msg.view(), blocked_patches_msg.view()) :
1162
fmt::format(TRANSLATE_FS("Cheats", "{} disabled by achievements hardcore mode/safe mode."),
1163
(blocked_cheats > 0) ? blocked_cheats_msg.view() : blocked_patches_msg.view());
1164
Host::AddIconOSDMessage(OSDMessageType::Warning, "LoadCheats", ICON_EMOJI_WARNING, std::move(message));
1165
}
1166
}
1167
}
1168
1169
void Cheats::ApplyFrameEndCodes()
1170
{
1171
for (const CheatCode* code : s_locals.frame_end_codes)
1172
code->Apply();
1173
}
1174
1175
bool Cheats::EnumerateManualCodes(std::function<bool(const std::string& name)> callback)
1176
{
1177
for (const std::unique_ptr<CheatCode>& code : s_locals.cheat_codes)
1178
{
1179
if (code->IsManuallyActivated())
1180
{
1181
if (!callback(code->GetName()))
1182
return false;
1183
}
1184
}
1185
return true;
1186
}
1187
1188
bool Cheats::ApplyManualCode(const std::string_view name)
1189
{
1190
for (const std::unique_ptr<CheatCode>& code : s_locals.cheat_codes)
1191
{
1192
if (code->IsManuallyActivated() && code->GetName() == name)
1193
{
1194
Host::AddIconOSDMessage(OSDMessageType::Quick, code->GetName(), ICON_FA_BANDAGE,
1195
fmt::format(TRANSLATE_FS("Cheats", "Cheat '{}' applied."), code->GetName()));
1196
code->Apply();
1197
return true;
1198
}
1199
}
1200
1201
return false;
1202
}
1203
1204
u32 Cheats::GetActivePatchCount()
1205
{
1206
return s_locals.active_patch_count;
1207
}
1208
1209
u32 Cheats::GetActiveCheatCount()
1210
{
1211
return s_locals.active_cheat_count;
1212
}
1213
1214
bool Cheats::IsWidescreenPatchActive()
1215
{
1216
return s_locals.has_widescreen_patch;
1217
}
1218
1219
//////////////////////////////////////////////////////////////////////////
1220
// File Parsing
1221
//////////////////////////////////////////////////////////////////////////
1222
1223
bool Cheats::ExtractCodeInfo(CodeInfoList* dst, std::string_view file_data, bool from_database, bool stop_on_error,
1224
Error* error)
1225
{
1226
CodeInfo current_code;
1227
1228
std::optional<std::string> legacy_group;
1229
std::optional<CodeType> legacy_type;
1230
std::optional<CodeActivation> legacy_activation;
1231
bool ignore_this_code = false;
1232
1233
CheatFileReader reader(file_data);
1234
1235
const auto finish_code = [&dst, &file_data, &stop_on_error, &error, &current_code, &ignore_this_code, &reader]() {
1236
if (current_code.file_offset_end > current_code.file_offset_body_start)
1237
{
1238
current_code.body = StringUtil::StripWhitespace(file_data.substr(
1239
current_code.file_offset_body_start, current_code.file_offset_end - current_code.file_offset_body_start));
1240
}
1241
else
1242
{
1243
if (!reader.LogError(error, stop_on_error, "Empty body for cheat '{}'", current_code.name))
1244
return false;
1245
}
1246
1247
if (!ignore_this_code)
1248
AppendCheatToList(dst, std::move(current_code));
1249
1250
return true;
1251
};
1252
1253
std::string_view line;
1254
while (reader.GetLine(&line))
1255
{
1256
std::string_view linev = StringUtil::StripWhitespace(line);
1257
if (linev.empty())
1258
continue;
1259
1260
// legacy metadata parsing
1261
if (linev.starts_with("#group="))
1262
{
1263
legacy_group = StringUtil::StripWhitespace(linev.substr(7));
1264
continue;
1265
}
1266
else if (linev.starts_with("#type="))
1267
{
1268
legacy_type = ParseTypeName(StringUtil::StripWhitespace(linev.substr(6)));
1269
if (!legacy_type.has_value()) [[unlikely]]
1270
{
1271
if (!reader.LogError(error, stop_on_error, "Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line))
1272
return false;
1273
1274
continue;
1275
}
1276
}
1277
else if (linev.starts_with("#activation="))
1278
{
1279
legacy_activation = ParseActivationName(StringUtil::StripWhitespace(linev.substr(12)));
1280
if (!legacy_activation.has_value()) [[unlikely]]
1281
{
1282
if (!reader.LogError(error, stop_on_error, "Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line))
1283
return false;
1284
1285
continue;
1286
}
1287
}
1288
1289
// skip comments
1290
if (linev[0] == '#' || linev[0] == ';')
1291
continue;
1292
1293
if (linev.front() == '[')
1294
{
1295
if (linev.size() < 3 || linev.back() != ']')
1296
{
1297
if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(),
1298
line))
1299
{
1300
return false;
1301
}
1302
1303
continue;
1304
}
1305
1306
const std::string_view name = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2));
1307
if (name.empty())
1308
{
1309
if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(),
1310
line))
1311
{
1312
return false;
1313
}
1314
1315
continue;
1316
}
1317
1318
// new code.
1319
if (!current_code.name.empty())
1320
{
1321
// overwrite existing codes with the same name.
1322
finish_code();
1323
current_code = CodeInfo();
1324
ignore_this_code = false;
1325
}
1326
1327
current_code.name =
1328
legacy_group.has_value() ? fmt::format("{}\\{}", legacy_group.value(), name) : std::string(name);
1329
current_code.type = legacy_type.value_or(CodeType::Gameshark);
1330
current_code.activation = legacy_activation.value_or(CodeActivation::EndFrame);
1331
current_code.file_offset_start = static_cast<u32>(reader.GetCurrentLineOffset());
1332
current_code.file_offset_end = current_code.file_offset_start;
1333
current_code.file_offset_body_start = current_code.file_offset_start;
1334
current_code.from_database = from_database;
1335
continue;
1336
}
1337
1338
// strip comments off end of lines
1339
const std::string_view::size_type comment_pos = linev.find_last_of("#;");
1340
if (comment_pos != std::string_view::npos)
1341
{
1342
linev = StringUtil::StripWhitespace(linev.substr(0, comment_pos));
1343
if (linev.empty())
1344
continue;
1345
}
1346
1347
// metadata?
1348
if (linev.find('=') != std::string_view::npos)
1349
{
1350
std::string_view key, value;
1351
if (!StringUtil::ParseAssignmentString(linev, &key, &value))
1352
{
1353
if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(),
1354
line))
1355
{
1356
return false;
1357
}
1358
1359
continue;
1360
}
1361
1362
if (key == "Description")
1363
{
1364
current_code.description = value;
1365
}
1366
else if (key == "Author")
1367
{
1368
current_code.author = value;
1369
}
1370
else if (key == "Type")
1371
{
1372
const std::optional<CodeType> type = ParseTypeName(value);
1373
if (type.has_value()) [[unlikely]]
1374
{
1375
current_code.type = type.value();
1376
}
1377
else
1378
{
1379
if (!reader.LogError(error, stop_on_error, "Unknown code type at line {}: {}", reader.GetCurrentLineNumber(),
1380
line))
1381
{
1382
return false;
1383
}
1384
}
1385
}
1386
else if (key == "Activation")
1387
{
1388
const std::optional<CodeActivation> activation = ParseActivationName(value);
1389
if (activation.has_value()) [[unlikely]]
1390
{
1391
current_code.activation = activation.value();
1392
}
1393
else
1394
{
1395
if (!reader.LogError(error, stop_on_error, "Unknown code activation at line {}: {}",
1396
reader.GetCurrentLineNumber(), line))
1397
{
1398
return false;
1399
}
1400
}
1401
}
1402
else if (key == "Option")
1403
{
1404
if (std::optional<Cheats::CodeOption> opt = ParseOption(value))
1405
{
1406
current_code.options.push_back(std::move(opt.value()));
1407
}
1408
else
1409
{
1410
if (!reader.LogError(error, stop_on_error, "Invalid option declaration at line {}: {}",
1411
reader.GetCurrentLineNumber(), line))
1412
{
1413
return false;
1414
}
1415
}
1416
}
1417
else if (key == "OptionRange")
1418
{
1419
if (!ParseOptionRange(value, &current_code.option_range_start, &current_code.option_range_end))
1420
{
1421
if (!reader.LogError(error, stop_on_error, "Invalid option range declaration at line {}: {}",
1422
reader.GetCurrentLineNumber(), line))
1423
{
1424
return false;
1425
}
1426
}
1427
}
1428
else if (key == "DisallowForAchievements")
1429
{
1430
current_code.disallow_for_achievements = StringUtil::FromChars<bool>(value).value_or(false);
1431
}
1432
else if (key == "Ignore")
1433
{
1434
ignore_this_code = StringUtil::FromChars<bool>(value).value_or(false);
1435
}
1436
1437
// ignore other keys when we're only grabbing info
1438
continue;
1439
}
1440
1441
if (current_code.name.empty())
1442
{
1443
if (!reader.LogError(error, stop_on_error, "Code data specified without name at line {}: {}",
1444
reader.GetCurrentLineNumber(), line))
1445
{
1446
return false;
1447
}
1448
1449
continue;
1450
}
1451
1452
if (current_code.file_offset_body_start == current_code.file_offset_start)
1453
current_code.file_offset_body_start = static_cast<u32>(reader.GetCurrentLineOffset());
1454
1455
// if it's a code line, update the ending point
1456
current_code.file_offset_end = static_cast<u32>(reader.GetCurrentOffset());
1457
}
1458
1459
// last code.
1460
if (!current_code.name.empty())
1461
return finish_code();
1462
else
1463
return true;
1464
}
1465
1466
void Cheats::AppendCheatToList(CodeInfoList* dst, CodeInfo code)
1467
{
1468
const auto iter =
1469
std::find_if(dst->begin(), dst->end(), [&code](const CodeInfo& rhs) { return code.name == rhs.name; });
1470
if (iter != dst->end())
1471
*iter = std::move(code);
1472
else
1473
dst->push_back(std::move(code));
1474
}
1475
1476
void Cheats::ParseFile(CheatCodeList* dst_list, const std::string_view file_contents)
1477
{
1478
CheatFileReader reader(file_contents);
1479
1480
std::string_view next_code_group;
1481
CheatCode::Metadata next_code_metadata = {};
1482
bool next_code_ignored = false;
1483
std::optional<size_t> code_body_start;
1484
1485
const auto finish_code = [&dst_list, &file_contents, &reader, &next_code_group, &next_code_metadata,
1486
&next_code_ignored, &code_body_start]() {
1487
if (!code_body_start.has_value())
1488
{
1489
WARNING_LOG("Empty cheat body at line {}", reader.GetCurrentLineNumber());
1490
next_code_metadata = {};
1491
return;
1492
}
1493
1494
const SmallString code_name(next_code_metadata.name);
1495
const std::string_view code_body =
1496
file_contents.substr(code_body_start.value(), reader.GetCurrentLineOffset() - code_body_start.value());
1497
1498
Error error;
1499
std::unique_ptr<CheatCode> code = ParseCode(std::move(next_code_metadata), code_body, &error);
1500
if (!code)
1501
{
1502
WARNING_LOG("Failed to parse code ending on line {}: {}", reader.GetCurrentLineNumber(), error.GetDescription());
1503
1504
Host::AddIconOSDMessage(OSDMessageType::Error, fmt::format("cheat_parse_error_{}", code_name), ICON_EMOJI_WARNING,
1505
fmt::format("{} '{}':\n{}", TRANSLATE_SV("Cheats", "Failed to parse cheat code"),
1506
code_name, error.GetDescription()));
1507
}
1508
1509
next_code_group = {};
1510
next_code_metadata = {};
1511
code_body_start.reset();
1512
if (std::exchange(next_code_ignored, false) || !code)
1513
return;
1514
1515
// overwrite existing codes with the same name.
1516
const auto iter = std::find_if(dst_list->begin(), dst_list->end(), [&code](const std::unique_ptr<CheatCode>& rhs) {
1517
return code->GetName() == rhs->GetName();
1518
});
1519
if (iter != dst_list->end())
1520
*iter = std::move(code);
1521
else
1522
dst_list->push_back(std::move(code));
1523
};
1524
1525
std::string_view line;
1526
while (reader.GetLine(&line))
1527
{
1528
std::string_view linev = StringUtil::StripWhitespace(line);
1529
if (linev.empty())
1530
continue;
1531
1532
// legacy metadata parsing
1533
if (linev.starts_with("#group="))
1534
{
1535
next_code_group = StringUtil::StripWhitespace(linev.substr(7));
1536
continue;
1537
}
1538
else if (linev.starts_with("#type="))
1539
{
1540
const std::optional<CodeType> type = ParseTypeName(StringUtil::StripWhitespace(linev.substr(6)));
1541
if (!type.has_value())
1542
WARNING_LOG("Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line);
1543
else
1544
next_code_metadata.type = type.value();
1545
1546
continue;
1547
}
1548
else if (linev.starts_with("#activation="))
1549
{
1550
const std::optional<CodeActivation> activation =
1551
ParseActivationName(StringUtil::StripWhitespace(linev.substr(12)));
1552
if (!activation.has_value())
1553
WARNING_LOG("Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line);
1554
else
1555
next_code_metadata.activation = activation.value();
1556
1557
continue;
1558
}
1559
1560
// skip comments
1561
if (linev[0] == '#' || linev[0] == ';')
1562
continue;
1563
1564
if (linev.front() == '[')
1565
{
1566
if (linev.size() < 3 || linev.back() != ']')
1567
{
1568
WARNING_LOG("Malformed code at line {}: {}", reader.GetCurrentLineNumber(), line);
1569
continue;
1570
}
1571
1572
const std::string_view name = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2));
1573
if (name.empty())
1574
{
1575
WARNING_LOG("Empty cheat code name at line {}: {}", reader.GetCurrentLineNumber(), line);
1576
continue;
1577
}
1578
1579
if (!next_code_metadata.name.empty())
1580
finish_code();
1581
1582
// new code.
1583
next_code_metadata.name =
1584
next_code_group.empty() ? std::string(name) : fmt::format("{}\\{}", next_code_group, name);
1585
continue;
1586
}
1587
1588
// strip comments off end of lines
1589
const std::string_view::size_type comment_pos = linev.find_last_of("#;");
1590
if (comment_pos != std::string_view::npos)
1591
{
1592
linev = StringUtil::StripWhitespace(linev.substr(0, comment_pos));
1593
if (linev.empty())
1594
continue;
1595
}
1596
1597
// metadata?
1598
if (linev.find('=') != std::string_view::npos)
1599
{
1600
std::string_view key, value;
1601
if (!StringUtil::ParseAssignmentString(linev, &key, &value))
1602
{
1603
WARNING_LOG("Malformed code at line {}: {}", reader.GetCurrentLineNumber(), line);
1604
continue;
1605
}
1606
1607
if (key == "Type")
1608
{
1609
const std::optional<CodeType> type = ParseTypeName(value);
1610
if (!type.has_value())
1611
WARNING_LOG("Unknown code type at line {}: {}", reader.GetCurrentLineNumber(), line);
1612
else
1613
next_code_metadata.type = type.value();
1614
}
1615
else if (key == "Activation")
1616
{
1617
const std::optional<CodeActivation> activation = ParseActivationName(value);
1618
if (!activation.has_value())
1619
WARNING_LOG("Unknown code activation at line {}: {}", reader.GetCurrentLineNumber(), line);
1620
else
1621
next_code_metadata.activation = activation.value();
1622
}
1623
else if (key == "OverrideAspectRatio")
1624
{
1625
const std::optional<DisplayAspectRatio> aspect_ratio =
1626
Settings::ParseDisplayAspectRatio(TinyString(value).c_str());
1627
if (!aspect_ratio.has_value())
1628
WARNING_LOG("Unknown aspect ratio at line {}: {}", reader.GetCurrentLineNumber(), line);
1629
else
1630
next_code_metadata.override_aspect_ratio = aspect_ratio;
1631
}
1632
else if (key == "OverrideCPUOverclock")
1633
{
1634
const std::optional<u32> ocvalue = StringUtil::FromChars<u32>(value);
1635
if (!ocvalue.has_value() || ocvalue.value() == 0)
1636
WARNING_LOG("Invalid CPU overclock at line {}: {}", reader.GetCurrentLineNumber(), line);
1637
else
1638
next_code_metadata.override_cpu_overclock = ocvalue.value();
1639
}
1640
else if (key == "DisableWidescreenRendering")
1641
{
1642
next_code_metadata.disable_widescreen_rendering = StringUtil::FromChars<bool>(value).value_or(false);
1643
}
1644
else if (key == "Enable8MBRAM")
1645
{
1646
next_code_metadata.enable_8mb_ram = StringUtil::FromChars<bool>(value).value_or(false);
1647
}
1648
else if (key == "DisallowForAchievements")
1649
{
1650
next_code_metadata.disallow_for_achievements = StringUtil::FromChars<bool>(value).value_or(false);
1651
}
1652
else if (key == "Option" || key == "OptionRange")
1653
{
1654
// we don't care about the actual values, we load them from the config
1655
next_code_metadata.has_options = true;
1656
}
1657
else if (key == "Author" || key == "Description")
1658
{
1659
// ignored when loading
1660
}
1661
else if (key == "Ignore")
1662
{
1663
next_code_ignored = StringUtil::FromChars<bool>(value).value_or(false);
1664
}
1665
else
1666
{
1667
WARNING_LOG("Unknown parameter {} at line {}", key, reader.GetCurrentLineNumber());
1668
}
1669
1670
continue;
1671
}
1672
1673
if (!code_body_start.has_value())
1674
code_body_start = reader.GetCurrentLineOffset();
1675
}
1676
1677
finish_code();
1678
}
1679
1680
std::optional<Cheats::CodeOption> Cheats::ParseOption(const std::string_view value)
1681
{
1682
// Option = Value1:0x1
1683
std::optional<CodeOption> ret;
1684
if (const std::string_view::size_type pos = value.rfind(':'); pos != std::string_view::npos)
1685
{
1686
const std::string_view opt_name = StringUtil::StripWhitespace(value.substr(0, pos));
1687
const std::optional<u32> opt_value =
1688
StringUtil::FromCharsWithOptionalBase<u32>(StringUtil::StripWhitespace(value.substr(pos + 1)));
1689
if (opt_value.has_value())
1690
ret = CodeOption(opt_name, opt_value.value());
1691
}
1692
return ret;
1693
}
1694
1695
bool Cheats::ParseOptionRange(const std::string_view value, u16* out_range_start, u16* out_range_end)
1696
{
1697
// OptionRange = 0:255
1698
if (const std::string_view::size_type pos = value.rfind(':'); pos != std::string_view::npos)
1699
{
1700
const std::optional<u32> start =
1701
StringUtil::FromCharsWithOptionalBase<u32>(StringUtil::StripWhitespace(value.substr(0, pos)));
1702
const std::optional<u32> end =
1703
StringUtil::FromCharsWithOptionalBase<u32>(StringUtil::StripWhitespace(value.substr(pos + 1)));
1704
if (start.has_value() && end.has_value() && start.value() <= std::numeric_limits<u16>::max() &&
1705
end.value() <= std::numeric_limits<u16>::max() && end.value() > start.value())
1706
{
1707
*out_range_start = static_cast<u16>(start.value());
1708
*out_range_end = static_cast<u16>(end.value());
1709
return true;
1710
}
1711
}
1712
1713
return false;
1714
}
1715
1716
//////////////////////////////////////////////////////////////////////////
1717
// File Importing
1718
//////////////////////////////////////////////////////////////////////////
1719
1720
bool Cheats::ExportCodesToFile(std::string path, const CodeInfoList& codes, Error* error)
1721
{
1722
if (codes.empty())
1723
{
1724
Error::SetStringView(error, "Code list is empty.");
1725
return false;
1726
}
1727
1728
auto fp = FileSystem::CreateAtomicRenamedFile(std::move(path), error);
1729
if (!fp)
1730
return false;
1731
1732
for (const CodeInfo& code : codes)
1733
{
1734
const std::string code_body = FormatCodeForFile(code);
1735
if (std::fwrite(code_body.data(), code_body.length(), 1, fp.get()) != 1 || std::fputc('\n', fp.get()) == EOF)
1736
{
1737
Error::SetErrno(error, "fwrite() failed: ", errno);
1738
FileSystem::DiscardAtomicRenamedFile(fp);
1739
return false;
1740
}
1741
}
1742
1743
return FileSystem::CommitAtomicRenamedFile(fp, error);
1744
}
1745
1746
bool Cheats::ImportCodesFromString(CodeInfoList* dst, const std::string_view file_contents, FileFormat file_format,
1747
bool stop_on_error, Error* error)
1748
{
1749
if (file_format == FileFormat::Unknown)
1750
file_format = DetectFileFormat(file_contents);
1751
1752
if (file_format == FileFormat::DuckStation)
1753
{
1754
if (!ExtractCodeInfo(dst, file_contents, false, stop_on_error, error))
1755
return false;
1756
}
1757
else if (file_format == FileFormat::PCSX)
1758
{
1759
if (!ImportPCSXFile(dst, file_contents, stop_on_error, error))
1760
return false;
1761
}
1762
else if (file_format == FileFormat::Libretro)
1763
{
1764
if (!ImportLibretroFile(dst, file_contents, stop_on_error, error))
1765
return false;
1766
}
1767
else if (file_format == FileFormat::EPSXe)
1768
{
1769
if (!ImportEPSXeFile(dst, file_contents, stop_on_error, error))
1770
return false;
1771
}
1772
else
1773
{
1774
Error::SetStringView(error, "Unknown file format.");
1775
return false;
1776
}
1777
1778
if (dst->empty())
1779
{
1780
Error::SetStringView(error, "No codes found in file.");
1781
return false;
1782
}
1783
1784
return true;
1785
}
1786
1787
Cheats::FileFormat Cheats::DetectFileFormat(const std::string_view file_contents)
1788
{
1789
CheatFileReader reader(file_contents);
1790
std::string_view line;
1791
while (reader.GetLine(&line))
1792
{
1793
// skip comments/empty lines
1794
std::string_view linev = StringUtil::StripWhitespace(line);
1795
if (linev.empty() || linev[0] == ';' || linev[0] == '#')
1796
continue;
1797
1798
if (linev.starts_with("cheats"))
1799
return FileFormat::Libretro;
1800
1801
// native if we see brackets and a type string
1802
if (linev[0] == '[' && file_contents.find("\nType ="))
1803
return FileFormat::DuckStation;
1804
1805
// pcsxr if we see brackets
1806
if (linev[0] == '[')
1807
return FileFormat::PCSX;
1808
1809
// otherwise if it's a code, it's probably epsxe
1810
if (StringUtil::IsHexDigit(linev[0]))
1811
return FileFormat::EPSXe;
1812
}
1813
1814
return FileFormat::Unknown;
1815
}
1816
1817
bool Cheats::ImportPCSXFile(CodeInfoList* dst, const std::string_view file_contents, bool stop_on_error, Error* error)
1818
{
1819
CheatFileReader reader(file_contents);
1820
CodeInfo current_code;
1821
1822
const auto finish_code = [&dst, &file_contents, &stop_on_error, &error, &current_code, &reader]() {
1823
if (current_code.file_offset_end <= current_code.file_offset_body_start)
1824
{
1825
if (!reader.LogError(error, stop_on_error, "Empty body for cheat '{}'", current_code.name))
1826
return false;
1827
}
1828
1829
current_code.body =
1830
StringUtil::StripWhitespace(std::string_view(file_contents)
1831
.substr(current_code.file_offset_body_start,
1832
current_code.file_offset_end - current_code.file_offset_body_start));
1833
1834
AppendCheatToList(dst, std::move(current_code));
1835
return true;
1836
};
1837
1838
std::string_view line;
1839
while (reader.GetLine(&line))
1840
{
1841
std::string_view linev = StringUtil::StripWhitespace(line);
1842
if (linev.empty() || linev[0] == '#' || linev[0] == ';')
1843
continue;
1844
1845
if (linev.front() == '[')
1846
{
1847
if (linev.size() < 3 || linev.back() != ']' || (linev[1] == '*' && linev.size() < 4))
1848
{
1849
if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(),
1850
line))
1851
{
1852
return false;
1853
}
1854
1855
continue;
1856
}
1857
1858
std::string_view name_part = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2));
1859
if (!name_part.empty() && name_part.front() == '*')
1860
name_part = name_part.substr(1);
1861
if (name_part.empty())
1862
{
1863
if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(),
1864
line))
1865
{
1866
return false;
1867
}
1868
1869
continue;
1870
}
1871
1872
// new code.
1873
if (!current_code.name.empty() && !finish_code())
1874
return false;
1875
1876
current_code = CodeInfo();
1877
current_code.name = name_part;
1878
current_code.file_offset_start = static_cast<u32>(reader.GetCurrentLineOffset());
1879
current_code.file_offset_end = current_code.file_offset_start;
1880
current_code.file_offset_body_start = current_code.file_offset_start;
1881
current_code.type = CodeType::Gameshark;
1882
current_code.activation = CodeActivation::EndFrame;
1883
current_code.from_database = false;
1884
continue;
1885
}
1886
1887
// strip comments off end of lines
1888
const std::string_view::size_type comment_pos = linev.find_last_of("#;");
1889
if (comment_pos != std::string_view::npos)
1890
{
1891
linev = StringUtil::StripWhitespace(linev.substr(0, comment_pos));
1892
if (linev.empty())
1893
continue;
1894
}
1895
1896
if (current_code.name.empty())
1897
{
1898
if (!reader.LogError(error, stop_on_error, "Code data specified without name at line {}: {}",
1899
reader.GetCurrentLineNumber(), line))
1900
{
1901
return false;
1902
}
1903
1904
continue;
1905
}
1906
1907
if (current_code.file_offset_body_start == current_code.file_offset_start)
1908
current_code.file_offset_body_start = static_cast<u32>(reader.GetCurrentLineOffset());
1909
1910
// if it's a code line, update the ending point
1911
current_code.file_offset_end = static_cast<u32>(reader.GetCurrentOffset());
1912
}
1913
1914
// last code.
1915
if (!current_code.name.empty() && !finish_code())
1916
return false;
1917
1918
return true;
1919
}
1920
1921
bool Cheats::ImportLibretroFile(CodeInfoList* dst, const std::string_view file_contents, bool stop_on_error,
1922
Error* error)
1923
{
1924
std::vector<std::pair<std::string_view, std::string_view>> kvp;
1925
1926
static constexpr auto FindKey = [](const std::vector<std::pair<std::string_view, std::string_view>>& kvp,
1927
const std::string_view search) -> const std::string_view* {
1928
for (const auto& it : kvp)
1929
{
1930
if (StringUtil::EqualNoCase(search, it.first))
1931
return &it.second;
1932
}
1933
1934
return nullptr;
1935
};
1936
1937
CheatFileReader reader(file_contents);
1938
std::string_view line;
1939
while (reader.GetLine(&line))
1940
{
1941
const std::string_view linev = StringUtil::StripWhitespace(line);
1942
if (linev.empty())
1943
continue;
1944
1945
// skip comments
1946
if (linev[0] == '#' || linev[0] == ';')
1947
continue;
1948
1949
std::string_view key, value;
1950
if (!StringUtil::ParseAssignmentString(linev, &key, &value))
1951
{
1952
if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(), line))
1953
return false;
1954
1955
continue;
1956
}
1957
1958
kvp.emplace_back(key, value);
1959
}
1960
1961
if (kvp.empty())
1962
{
1963
reader.LogError(error, stop_on_error, "No key/values found.");
1964
return false;
1965
}
1966
1967
const std::string_view* num_cheats_value = FindKey(kvp, "cheats");
1968
const u32 num_cheats = num_cheats_value ? StringUtil::FromChars<u32>(*num_cheats_value).value_or(0) : 0;
1969
if (num_cheats == 0)
1970
return false;
1971
1972
for (u32 i = 0; i < num_cheats; i++)
1973
{
1974
const std::string_view* desc = FindKey(kvp, TinyString::from_format("cheat{}_desc", i));
1975
const std::string_view* code = FindKey(kvp, TinyString::from_format("cheat{}_code", i));
1976
if (!desc || desc->empty() || !code || code->empty())
1977
{
1978
if (!reader.LogError(error, stop_on_error, "Missing desc/code for cheat {}", i))
1979
return false;
1980
1981
continue;
1982
}
1983
1984
// Need to convert + to newlines.
1985
CodeInfo info;
1986
info.name = *desc;
1987
info.body = StringUtil::ReplaceAll(*code, '+', '\n');
1988
info.file_offset_start = 0;
1989
info.file_offset_end = 0;
1990
info.file_offset_body_start = 0;
1991
info.type = CodeType::Gameshark;
1992
info.activation = CodeActivation::EndFrame;
1993
info.from_database = false;
1994
AppendCheatToList(dst, std::move(info));
1995
}
1996
1997
return true;
1998
}
1999
2000
bool Cheats::ImportEPSXeFile(CodeInfoList* dst, const std::string_view file_contents, bool stop_on_error, Error* error)
2001
{
2002
CheatFileReader reader(file_contents);
2003
CodeInfo current_code;
2004
2005
const auto finish_code = [&dst, &file_contents, &stop_on_error, &error, &current_code, &reader]() {
2006
if (current_code.file_offset_end <= current_code.file_offset_body_start)
2007
{
2008
if (!reader.LogError(error, stop_on_error, "Empty body for cheat '{}'", current_code.name))
2009
return false;
2010
}
2011
2012
current_code.body =
2013
StringUtil::StripWhitespace(std::string_view(file_contents)
2014
.substr(current_code.file_offset_body_start,
2015
current_code.file_offset_end - current_code.file_offset_body_start));
2016
2017
AppendCheatToList(dst, std::move(current_code));
2018
return true;
2019
};
2020
2021
std::string_view line;
2022
while (reader.GetLine(&line))
2023
{
2024
std::string_view linev = StringUtil::StripWhitespace(line);
2025
if (linev.empty() || linev[0] == ';')
2026
continue;
2027
2028
if (linev.front() == '#')
2029
{
2030
if (linev.size() < 2)
2031
{
2032
if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(),
2033
line))
2034
{
2035
return false;
2036
}
2037
2038
continue;
2039
}
2040
2041
const std::string_view name_part = StringUtil::StripWhitespace(linev.substr(1));
2042
if (name_part.empty())
2043
{
2044
if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(),
2045
line))
2046
{
2047
return false;
2048
}
2049
2050
continue;
2051
}
2052
2053
if (!current_code.name.empty() && !finish_code())
2054
return false;
2055
2056
// new code.
2057
current_code = CodeInfo();
2058
current_code.name = name_part;
2059
current_code.file_offset_start = static_cast<u32>(reader.GetCurrentOffset());
2060
current_code.file_offset_end = current_code.file_offset_start;
2061
current_code.file_offset_body_start = current_code.file_offset_start;
2062
current_code.type = CodeType::Gameshark;
2063
current_code.activation = CodeActivation::EndFrame;
2064
current_code.from_database = false;
2065
continue;
2066
}
2067
2068
if (current_code.name.empty())
2069
{
2070
if (!reader.LogError(error, stop_on_error, "Code data specified without name at line {}: {}",
2071
reader.GetCurrentLineNumber(), line))
2072
{
2073
return false;
2074
}
2075
2076
continue;
2077
}
2078
2079
// if it's a code line, update the ending point
2080
current_code.file_offset_end = static_cast<u32>(reader.GetCurrentOffset());
2081
}
2082
2083
// last code.
2084
if (!current_code.name.empty() && !finish_code())
2085
return false;
2086
2087
return true;
2088
}
2089
2090
bool Cheats::ImportOldChtFile(const std::string_view serial)
2091
{
2092
const GameDatabase::Entry* dbentry = GameDatabase::GetEntryForSerial(serial);
2093
if (!dbentry || dbentry->title.empty())
2094
return false;
2095
2096
const std::string old_path =
2097
fmt::format("{}" FS_OSPATH_SEPARATOR_STR "{}.cht", EmuFolders::Cheats, dbentry->GetSaveTitle());
2098
if (!FileSystem::FileExists(old_path.c_str()))
2099
return false;
2100
2101
Error error;
2102
std::optional<std::string> old_data = FileSystem::ReadFileToString(old_path.c_str(), &error);
2103
if (!old_data.has_value())
2104
{
2105
ERROR_LOG("Failed to open old cht file '{}' for importing: {}", Path::GetFileName(old_path),
2106
error.GetDescription());
2107
return false;
2108
}
2109
2110
CodeInfoList new_codes;
2111
if (!ImportCodesFromString(&new_codes, old_data.value(), FileFormat::Unknown, false, &error) || new_codes.empty())
2112
{
2113
ERROR_LOG("Failed to import old cht file '{}': {}", Path::GetFileName(old_path), error.GetDescription());
2114
return false;
2115
}
2116
2117
const std::string new_path = GetChtFilename(serial, std::nullopt, true);
2118
if (!SaveCodesToFile(new_path.c_str(), new_codes, &error))
2119
{
2120
ERROR_LOG("Failed to write new cht file '{}': {}", Path::GetFileName(new_path), error.GetDescription());
2121
return false;
2122
}
2123
2124
INFO_LOG("Imported {} codes from {}.", new_codes.size(), Path::GetFileName(old_path));
2125
return true;
2126
}
2127
2128
//////////////////////////////////////////////////////////////////////////
2129
// Gameshark codes
2130
//////////////////////////////////////////////////////////////////////////
2131
2132
namespace Cheats {
2133
namespace {
2134
2135
class GamesharkCheatCode final : public CheatCode
2136
{
2137
public:
2138
explicit GamesharkCheatCode(Metadata metadata);
2139
~GamesharkCheatCode() override;
2140
2141
static std::unique_ptr<GamesharkCheatCode> Parse(Metadata metadata, const std::string_view data, Error* error);
2142
2143
void SetOptionValue(u32 value) override;
2144
2145
void Apply() const override;
2146
void ApplyOnDisable() const override;
2147
2148
private:
2149
enum class InstructionCode : u8
2150
{
2151
Nop = 0x00,
2152
ConstantWrite8 = 0x30,
2153
ConstantWrite16 = 0x80,
2154
ScratchpadWrite16 = 0x1F,
2155
Increment16 = 0x10,
2156
Decrement16 = 0x11,
2157
Increment8 = 0x20,
2158
Decrement8 = 0x21,
2159
DelayActivation = 0xC1,
2160
SkipIfNotEqual16 = 0xC0,
2161
SkipIfButtonsNotEqual = 0xD5,
2162
SkipIfButtonsEqual = 0xD6,
2163
CompareButtons = 0xD4,
2164
CompareEqual16 = 0xD0,
2165
CompareNotEqual16 = 0xD1,
2166
CompareLess16 = 0xD2,
2167
CompareGreater16 = 0xD3,
2168
CompareEqual8 = 0xE0,
2169
CompareNotEqual8 = 0xE1,
2170
CompareLess8 = 0xE2,
2171
CompareGreater8 = 0xE3,
2172
Slide = 0x50,
2173
MemoryCopy = 0xC2,
2174
ExtImprovedSlide = 0x53,
2175
2176
// Extension opcodes, not present on original GameShark.
2177
ExtConstantWrite32 = 0x90,
2178
ExtScratchpadWrite32 = 0xA5,
2179
ExtCompareEqual32 = 0xA0,
2180
ExtCompareNotEqual32 = 0xA1,
2181
ExtCompareLess32 = 0xA2,
2182
ExtCompareGreater32 = 0xA3,
2183
ExtSkipIfNotEqual32 = 0xA4,
2184
ExtIncrement32 = 0x60,
2185
ExtDecrement32 = 0x61,
2186
ExtConstantWriteIfMatch16 = 0xA6,
2187
ExtConstantWriteIfMatchWithRestore16 = 0xA7,
2188
ExtConstantWriteIfMatchWithRestore8 = 0xA8,
2189
ExtConstantForceRange8 = 0xF0,
2190
ExtConstantForceRangeLimits16 = 0xF1,
2191
ExtConstantForceRangeRollRound16 = 0xF2,
2192
ExtConstantForceRange16 = 0xF3,
2193
ExtFindAndReplace = 0xF4,
2194
ExtConstantSwap16 = 0xF5,
2195
2196
ExtConstantBitSet8 = 0x31,
2197
ExtConstantBitClear8 = 0x32,
2198
ExtConstantBitSet16 = 0x81,
2199
ExtConstantBitClear16 = 0x82,
2200
ExtConstantBitSet32 = 0x91,
2201
ExtConstantBitClear32 = 0x92,
2202
2203
ExtBitCompareButtons = 0xD7,
2204
ExtSkipIfNotLess8 = 0xC3,
2205
ExtSkipIfNotGreater8 = 0xC4,
2206
ExtSkipIfNotLess16 = 0xC5,
2207
ExtSkipIfNotGreater16 = 0xC6,
2208
ExtMultiConditionals = 0xF6,
2209
2210
ExtCheatRegisters = 0x51,
2211
ExtCheatRegistersCompare = 0x52,
2212
2213
ExtCompareBitsSet8 = 0xE4, // Only used inside ExtMultiConditionals
2214
ExtCompareBitsClear8 = 0xE5, // Only used inside ExtMultiConditionals
2215
};
2216
2217
union Instruction
2218
{
2219
u64 bits;
2220
2221
struct
2222
{
2223
u32 second;
2224
u32 first;
2225
};
2226
2227
BitField<u64, InstructionCode, 32 + 24, 8> code;
2228
BitField<u64, u32, 32, 24> address;
2229
BitField<u64, u32, 0, 32> value32;
2230
BitField<u64, u16, 0, 16> value16;
2231
BitField<u64, u8, 0, 8> value8;
2232
};
2233
2234
std::vector<Instruction> instructions;
2235
std::vector<std::tuple<u32, u8, u8>> option_instruction_values;
2236
2237
u32 GetNextNonConditionalInstruction(u32 index) const;
2238
2239
static bool IsConditionalInstruction(InstructionCode code);
2240
};
2241
2242
} // namespace
2243
2244
} // namespace Cheats
2245
2246
Cheats::GamesharkCheatCode::GamesharkCheatCode(Metadata metadata) : CheatCode(std::move(metadata))
2247
{
2248
}
2249
2250
Cheats::GamesharkCheatCode::~GamesharkCheatCode() = default;
2251
2252
static std::optional<u32> ParseHexOptionMask(const std::string_view str, u8* out_option_start, u8* out_option_count)
2253
{
2254
if (str.length() > 8)
2255
return std::nullopt;
2256
2257
const u32 num_nibbles = static_cast<u32>(str.size());
2258
std::array<char, 8> nibble_values;
2259
u32 option_nibble_start = 0;
2260
u32 option_nibble_count = 0;
2261
bool last_was_option = false;
2262
for (u32 i = 0; i < num_nibbles; i++)
2263
{
2264
if (str[i] == '?')
2265
{
2266
if (option_nibble_count == 0)
2267
{
2268
option_nibble_start = i;
2269
}
2270
else if (!last_was_option)
2271
{
2272
// ? must be consecutive
2273
return false;
2274
}
2275
2276
option_nibble_count++;
2277
last_was_option = true;
2278
nibble_values[i] = '0';
2279
}
2280
else if (StringUtil::IsHexDigit(str[i]))
2281
{
2282
last_was_option = false;
2283
nibble_values[i] = str[i];
2284
}
2285
else
2286
{
2287
// not a valid hex digit
2288
return false;
2289
}
2290
}
2291
2292
// use stringutil to decode it, it has zeros in the place
2293
const std::optional<u32> parsed = StringUtil::FromChars<u32>(std::string_view(nibble_values.data(), num_nibbles), 16);
2294
if (!parsed.has_value()) [[unlikely]]
2295
return std::nullopt;
2296
2297
// LSB comes first, so reverse
2298
*out_option_start = static_cast<u8>((num_nibbles - option_nibble_start - option_nibble_count) * 4);
2299
*out_option_count = static_cast<u8>(option_nibble_count * 4);
2300
return parsed;
2301
}
2302
2303
std::unique_ptr<Cheats::GamesharkCheatCode> Cheats::GamesharkCheatCode::Parse(Metadata metadata,
2304
const std::string_view data, Error* error)
2305
{
2306
std::unique_ptr<GamesharkCheatCode> code = std::make_unique<GamesharkCheatCode>(std::move(metadata));
2307
CheatFileReader reader(data);
2308
std::string_view line;
2309
while (reader.GetLine(&line))
2310
{
2311
// skip comments/empty lines
2312
std::string_view linev = StringUtil::StripWhitespace(line);
2313
if (linev.empty() || !StringUtil::IsHexDigit(linev[0]))
2314
continue;
2315
2316
std::string_view next;
2317
const std::optional<u32> first = StringUtil::FromChars<u32>(linev, 16, &next);
2318
if (!first.has_value())
2319
{
2320
Error::SetStringFmt(error, "Malformed instruction at line {}: {}", reader.GetCurrentLineNumber(), linev);
2321
code.reset();
2322
break;
2323
}
2324
2325
size_t next_offset = 0;
2326
while (next_offset < next.size() && next[next_offset] != '?' && !StringUtil::IsHexDigit(next[next_offset]))
2327
next_offset++;
2328
next = (next_offset < next.size()) ? next.substr(next_offset) : std::string_view();
2329
2330
std::optional<u32> second;
2331
if (next.find('?') != std::string_view::npos)
2332
{
2333
u8 option_bitpos = 0, option_bitcount = 0;
2334
second = ParseHexOptionMask(next, &option_bitpos, &option_bitcount);
2335
if (second.has_value())
2336
{
2337
code->option_instruction_values.emplace_back(static_cast<u32>(code->instructions.size()), option_bitpos,
2338
option_bitcount);
2339
}
2340
}
2341
else
2342
{
2343
second = StringUtil::FromChars<u32>(next, 16);
2344
}
2345
2346
if (!second.has_value())
2347
{
2348
Error::SetStringFmt(error, "Malformed instruction at line {}: {}", reader.GetCurrentLineNumber(), linev);
2349
code.reset();
2350
break;
2351
}
2352
2353
Instruction inst;
2354
inst.first = first.value();
2355
inst.second = second.value();
2356
code->instructions.push_back(inst);
2357
}
2358
2359
if (code && code->instructions.empty())
2360
{
2361
Error::SetStringFmt(error, "No instructions in code.");
2362
code.reset();
2363
}
2364
2365
return code;
2366
}
2367
2368
static std::array<u32, 256> cht_register; // Used for D7 ,51 & 52 cheat types
2369
2370
template<typename T>
2371
NEVER_INLINE static T DoMemoryRead(VirtualMemoryAddress address)
2372
{
2373
using UnsignedType = typename std::make_unsigned_t<T>;
2374
static_assert(std::is_same_v<UnsignedType, u8> || std::is_same_v<UnsignedType, u16> ||
2375
std::is_same_v<UnsignedType, u32>);
2376
2377
T result;
2378
if constexpr (std::is_same_v<UnsignedType, u8>)
2379
return CPU::SafeReadMemoryByte(address, &result) ? result : static_cast<T>(0);
2380
else if constexpr (std::is_same_v<UnsignedType, u16>)
2381
return CPU::SafeReadMemoryHalfWord(address, &result) ? result : static_cast<T>(0);
2382
else // if constexpr (std::is_same_v<UnsignedType, u32>)
2383
return CPU::SafeReadMemoryWord(address, &result) ? result : static_cast<T>(0);
2384
}
2385
2386
template<typename T>
2387
NEVER_INLINE static void DoMemoryWrite(PhysicalMemoryAddress address, T value)
2388
{
2389
using UnsignedType = typename std::make_unsigned_t<T>;
2390
static_assert(std::is_same_v<UnsignedType, u8> || std::is_same_v<UnsignedType, u16> ||
2391
std::is_same_v<UnsignedType, u32>);
2392
2393
if constexpr (std::is_same_v<UnsignedType, u8>)
2394
CPU::SafeWriteMemoryByte(address, value);
2395
else if constexpr (std::is_same_v<UnsignedType, u16>)
2396
CPU::SafeWriteMemoryHalfWord(address, value);
2397
else // if constexpr (std::is_same_v<UnsignedType, u32>)
2398
CPU::SafeWriteMemoryWord(address, value);
2399
}
2400
2401
NEVER_INLINE static u32 GetControllerButtonBits()
2402
{
2403
static constexpr std::array<u16, 16> button_mapping = {{
2404
0x0100, // Select
2405
0x0200, // L3
2406
0x0400, // R3
2407
0x0800, // Start
2408
0x1000, // Up
2409
0x2000, // Right
2410
0x4000, // Down
2411
0x8000, // Left
2412
0x0001, // L2
2413
0x0002, // R2
2414
0x0004, // L1
2415
0x0008, // R1
2416
0x0010, // Triangle
2417
0x0020, // Circle
2418
0x0040, // Cross
2419
0x0080, // Square
2420
}};
2421
2422
u32 bits = 0;
2423
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
2424
{
2425
Controller* controller = System::GetController(i);
2426
if (!controller)
2427
continue;
2428
2429
bits |= controller->GetButtonStateBits();
2430
}
2431
2432
u32 translated_bits = 0;
2433
for (u32 i = 0, bit = 1; i < static_cast<u32>(button_mapping.size()); i++, bit <<= 1)
2434
{
2435
if (bits & bit)
2436
translated_bits |= button_mapping[i];
2437
}
2438
2439
return translated_bits;
2440
}
2441
2442
NEVER_INLINE static u32 GetControllerAnalogBits()
2443
{
2444
// 0x010000 - Right Thumb Up
2445
// 0x020000 - Right Thumb Right
2446
// 0x040000 - Right Thumb Down
2447
// 0x080000 - Right Thumb Left
2448
// 0x100000 - Left Thumb Up
2449
// 0x200000 - Left Thumb Right
2450
// 0x400000 - Left Thumb Down
2451
// 0x800000 - Left Thumb Left
2452
2453
u32 bits = 0;
2454
u8 l_ypos = 0;
2455
u8 l_xpos = 0;
2456
u8 r_ypos = 0;
2457
u8 r_xpos = 0;
2458
2459
std::optional<u32> analog = 0;
2460
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
2461
{
2462
Controller* controller = System::GetController(i);
2463
if (!controller)
2464
continue;
2465
2466
analog = controller->GetAnalogInputBytes();
2467
if (analog.has_value())
2468
{
2469
l_ypos = Truncate8((analog.value() & 0xFF000000u) >> 24);
2470
l_xpos = Truncate8((analog.value() & 0x00FF0000u) >> 16);
2471
r_ypos = Truncate8((analog.value() & 0x0000FF00u) >> 8);
2472
r_xpos = Truncate8(analog.value() & 0x000000FFu);
2473
if (l_ypos < 0x50)
2474
bits |= 0x100000;
2475
else if (l_ypos > 0xA0)
2476
bits |= 0x400000;
2477
if (l_xpos < 0x50)
2478
bits |= 0x800000;
2479
else if (l_xpos > 0xA0)
2480
bits |= 0x200000;
2481
if (r_ypos < 0x50)
2482
bits |= 0x10000;
2483
else if (r_ypos > 0xA0)
2484
bits |= 0x40000;
2485
if (r_xpos < 0x50)
2486
bits |= 0x80000;
2487
else if (r_xpos > 0xA0)
2488
bits |= 0x20000;
2489
}
2490
}
2491
return bits;
2492
}
2493
2494
bool Cheats::GamesharkCheatCode::IsConditionalInstruction(InstructionCode code)
2495
{
2496
switch (code)
2497
{
2498
case InstructionCode::CompareEqual16: // D0
2499
case InstructionCode::CompareNotEqual16: // D1
2500
case InstructionCode::CompareLess16: // D2
2501
case InstructionCode::CompareGreater16: // D3
2502
case InstructionCode::CompareEqual8: // E0
2503
case InstructionCode::CompareNotEqual8: // E1
2504
case InstructionCode::CompareLess8: // E2
2505
case InstructionCode::CompareGreater8: // E3
2506
case InstructionCode::CompareButtons: // D4
2507
case InstructionCode::ExtCompareEqual32: // A0
2508
case InstructionCode::ExtCompareNotEqual32: // A1
2509
case InstructionCode::ExtCompareLess32: // A2
2510
case InstructionCode::ExtCompareGreater32: // A3
2511
return true;
2512
2513
default:
2514
return false;
2515
}
2516
}
2517
2518
u32 Cheats::GamesharkCheatCode::GetNextNonConditionalInstruction(u32 index) const
2519
{
2520
const u32 count = static_cast<u32>(instructions.size());
2521
for (; index < count; index++)
2522
{
2523
if (!IsConditionalInstruction(instructions[index].code))
2524
{
2525
// we've found the first non conditional instruction in the chain, so skip over the instruction following it
2526
return index + 1;
2527
}
2528
}
2529
2530
return index;
2531
}
2532
2533
void Cheats::GamesharkCheatCode::Apply() const
2534
{
2535
const u32 count = static_cast<u32>(instructions.size());
2536
u32 index = 0;
2537
for (; index < count;)
2538
{
2539
const Instruction& inst = instructions[index];
2540
switch (inst.code)
2541
{
2542
case InstructionCode::Nop:
2543
{
2544
index++;
2545
}
2546
break;
2547
2548
case InstructionCode::ConstantWrite8:
2549
{
2550
DoMemoryWrite<u8>(inst.address, inst.value8);
2551
index++;
2552
}
2553
break;
2554
2555
case InstructionCode::ConstantWrite16:
2556
{
2557
DoMemoryWrite<u16>(inst.address, inst.value16);
2558
index++;
2559
}
2560
break;
2561
2562
case InstructionCode::ExtConstantWrite32:
2563
{
2564
DoMemoryWrite<u32>(inst.address, inst.value32);
2565
index++;
2566
}
2567
break;
2568
2569
case InstructionCode::ExtConstantBitSet8:
2570
{
2571
const u8 value = DoMemoryRead<u8>(inst.address) | inst.value8;
2572
DoMemoryWrite<u8>(inst.address, value);
2573
index++;
2574
}
2575
break;
2576
2577
case InstructionCode::ExtConstantBitSet16:
2578
{
2579
const u16 value = DoMemoryRead<u16>(inst.address) | inst.value16;
2580
DoMemoryWrite<u16>(inst.address, value);
2581
index++;
2582
}
2583
break;
2584
2585
case InstructionCode::ExtConstantBitSet32:
2586
{
2587
const u32 value = DoMemoryRead<u32>(inst.address) | inst.value32;
2588
DoMemoryWrite<u32>(inst.address, value);
2589
index++;
2590
}
2591
break;
2592
2593
case InstructionCode::ExtConstantBitClear8:
2594
{
2595
const u8 value = DoMemoryRead<u8>(inst.address) & ~inst.value8;
2596
DoMemoryWrite<u8>(inst.address, value);
2597
index++;
2598
}
2599
break;
2600
2601
case InstructionCode::ExtConstantBitClear16:
2602
{
2603
const u16 value = DoMemoryRead<u16>(inst.address) & ~inst.value16;
2604
DoMemoryWrite<u16>(inst.address, value);
2605
index++;
2606
}
2607
break;
2608
2609
case InstructionCode::ExtConstantBitClear32:
2610
{
2611
const u32 value = DoMemoryRead<u32>(inst.address) & ~inst.value32;
2612
DoMemoryWrite<u32>(inst.address, value);
2613
index++;
2614
}
2615
break;
2616
2617
case InstructionCode::ScratchpadWrite16:
2618
{
2619
DoMemoryWrite<u16>(CPU::SCRATCHPAD_ADDR | (inst.address & CPU::SCRATCHPAD_OFFSET_MASK), inst.value16);
2620
index++;
2621
}
2622
break;
2623
2624
case InstructionCode::ExtScratchpadWrite32:
2625
{
2626
DoMemoryWrite<u32>(CPU::SCRATCHPAD_ADDR | (inst.address & CPU::SCRATCHPAD_OFFSET_MASK), inst.value32);
2627
index++;
2628
}
2629
break;
2630
2631
case InstructionCode::ExtIncrement32:
2632
{
2633
const u32 value = DoMemoryRead<u32>(inst.address);
2634
DoMemoryWrite<u32>(inst.address, value + inst.value32);
2635
index++;
2636
}
2637
break;
2638
2639
case InstructionCode::ExtDecrement32:
2640
{
2641
const u32 value = DoMemoryRead<u32>(inst.address);
2642
DoMemoryWrite<u32>(inst.address, value - inst.value32);
2643
index++;
2644
}
2645
break;
2646
2647
case InstructionCode::Increment16:
2648
{
2649
const u16 value = DoMemoryRead<u16>(inst.address);
2650
DoMemoryWrite<u16>(inst.address, value + inst.value16);
2651
index++;
2652
}
2653
break;
2654
2655
case InstructionCode::Decrement16:
2656
{
2657
const u16 value = DoMemoryRead<u16>(inst.address);
2658
DoMemoryWrite<u16>(inst.address, value - inst.value16);
2659
index++;
2660
}
2661
break;
2662
2663
case InstructionCode::Increment8:
2664
{
2665
const u8 value = DoMemoryRead<u8>(inst.address);
2666
DoMemoryWrite<u8>(inst.address, value + inst.value8);
2667
index++;
2668
}
2669
break;
2670
2671
case InstructionCode::Decrement8:
2672
{
2673
const u8 value = DoMemoryRead<u8>(inst.address);
2674
DoMemoryWrite<u8>(inst.address, value - inst.value8);
2675
index++;
2676
}
2677
break;
2678
2679
case InstructionCode::ExtCompareEqual32:
2680
{
2681
const u32 value = DoMemoryRead<u32>(inst.address);
2682
if (value == inst.value32)
2683
index++;
2684
else
2685
index = GetNextNonConditionalInstruction(index);
2686
}
2687
break;
2688
2689
case InstructionCode::ExtCompareNotEqual32:
2690
{
2691
const u32 value = DoMemoryRead<u32>(inst.address);
2692
if (value != inst.value32)
2693
index++;
2694
else
2695
index = GetNextNonConditionalInstruction(index);
2696
}
2697
break;
2698
2699
case InstructionCode::ExtCompareLess32:
2700
{
2701
const u32 value = DoMemoryRead<u32>(inst.address);
2702
if (value < inst.value32)
2703
index++;
2704
else
2705
index = GetNextNonConditionalInstruction(index);
2706
}
2707
break;
2708
2709
case InstructionCode::ExtCompareGreater32:
2710
{
2711
const u32 value = DoMemoryRead<u32>(inst.address);
2712
if (value > inst.value32)
2713
index++;
2714
else
2715
index = GetNextNonConditionalInstruction(index);
2716
}
2717
break;
2718
2719
case InstructionCode::ExtConstantWriteIfMatch16:
2720
case InstructionCode::ExtConstantWriteIfMatchWithRestore16:
2721
{
2722
const u16 value = DoMemoryRead<u16>(inst.address);
2723
const u16 comparevalue = Truncate16(inst.value32 >> 16);
2724
const u16 newvalue = Truncate16(inst.value32 & 0xFFFFu);
2725
if (value == comparevalue)
2726
DoMemoryWrite<u16>(inst.address, newvalue);
2727
2728
index++;
2729
}
2730
break;
2731
2732
case InstructionCode::ExtConstantWriteIfMatchWithRestore8:
2733
{
2734
const u8 value = DoMemoryRead<u8>(inst.address);
2735
const u8 comparevalue = Truncate8(inst.value16 >> 8);
2736
const u8 newvalue = Truncate8(inst.value16 & 0xFFu);
2737
if (value == comparevalue)
2738
DoMemoryWrite<u8>(inst.address, newvalue);
2739
2740
index++;
2741
}
2742
break;
2743
2744
case InstructionCode::ExtConstantForceRange8:
2745
{
2746
const u8 value = DoMemoryRead<u8>(inst.address);
2747
const u8 min = Truncate8(inst.value32 & 0x000000FFu);
2748
const u8 max = Truncate8((inst.value32 & 0x0000FF00u) >> 8);
2749
const u8 overmin = Truncate8((inst.value32 & 0x00FF0000u) >> 16);
2750
const u8 overmax = Truncate8((inst.value32 & 0xFF000000u) >> 24);
2751
if ((value < min) || (value < min && min == 0x00u && max < 0xFEu))
2752
DoMemoryWrite<u8>(inst.address, overmin); // also handles a min value of 0x00
2753
else if (value > max)
2754
DoMemoryWrite<u8>(inst.address, overmax);
2755
index++;
2756
}
2757
break;
2758
2759
case InstructionCode::ExtConstantForceRangeLimits16:
2760
{
2761
const u16 value = DoMemoryRead<u16>(inst.address);
2762
const u16 min = Truncate16(inst.value32 & 0x0000FFFFu);
2763
const u16 max = Truncate16((inst.value32 & 0xFFFF0000u) >> 16);
2764
if ((value < min) || (value < min && min == 0x0000u && max < 0xFFFEu))
2765
DoMemoryWrite<u16>(inst.address, min); // also handles a min value of 0x0000
2766
else if (value > max)
2767
DoMemoryWrite<u16>(inst.address, max);
2768
index++;
2769
}
2770
break;
2771
2772
case InstructionCode::ExtConstantForceRangeRollRound16:
2773
{
2774
const u16 value = DoMemoryRead<u16>(inst.address);
2775
const u16 min = Truncate16(inst.value32 & 0x0000FFFFu);
2776
const u16 max = Truncate16((inst.value32 & 0xFFFF0000u) >> 16);
2777
if ((value < min) || (value < min && min == 0x0000u && max < 0xFFFEu))
2778
DoMemoryWrite<u16>(inst.address, max); // also handles a min value of 0x0000
2779
else if (value > max)
2780
DoMemoryWrite<u16>(inst.address, min);
2781
index++;
2782
}
2783
break;
2784
2785
case InstructionCode::ExtConstantForceRange16:
2786
{
2787
const u16 min = Truncate16(inst.value32 & 0x0000FFFFu);
2788
const u16 max = Truncate16((inst.value32 & 0xFFFF0000u) >> 16);
2789
const u16 value = DoMemoryRead<u16>(inst.address);
2790
const Instruction& inst2 = instructions[index + 1];
2791
const u16 overmin = Truncate16(inst2.value32 & 0x0000FFFFu);
2792
const u16 overmax = Truncate16((inst2.value32 & 0xFFFF0000u) >> 16);
2793
2794
if ((value < min) || (value < min && min == 0x0000u && max < 0xFFFEu))
2795
DoMemoryWrite<u16>(inst.address, overmin); // also handles a min value of 0x0000
2796
else if (value > max)
2797
DoMemoryWrite<u16>(inst.address, overmax);
2798
index += 2;
2799
}
2800
break;
2801
2802
case InstructionCode::ExtConstantSwap16:
2803
{
2804
const u16 value1 = Truncate16(inst.value32 & 0x0000FFFFu);
2805
const u16 value2 = Truncate16((inst.value32 & 0xFFFF0000u) >> 16);
2806
const u16 value = DoMemoryRead<u16>(inst.address);
2807
2808
if (value == value1)
2809
DoMemoryWrite<u16>(inst.address, value2);
2810
else if (value == value2)
2811
DoMemoryWrite<u16>(inst.address, value1);
2812
index++;
2813
}
2814
break;
2815
2816
case InstructionCode::ExtFindAndReplace:
2817
{
2818
2819
if ((index + 4) >= instructions.size())
2820
{
2821
ERROR_LOG("Incomplete find/replace instruction");
2822
return;
2823
}
2824
const Instruction& inst2 = instructions[index + 1];
2825
const Instruction& inst3 = instructions[index + 2];
2826
const Instruction& inst4 = instructions[index + 3];
2827
const Instruction& inst5 = instructions[index + 4];
2828
2829
const u32 offset = Truncate16(inst.value32 & 0x0000FFFFu) << 1;
2830
const u8 wildcard = Truncate8((inst.value32 & 0x00FF0000u) >> 16);
2831
const u32 minaddress = inst.address - offset;
2832
const u32 maxaddress = inst.address + offset;
2833
const u8 f1 = Truncate8((inst2.first & 0xFF000000u) >> 24);
2834
const u8 f2 = Truncate8((inst2.first & 0x00FF0000u) >> 16);
2835
const u8 f3 = Truncate8((inst2.first & 0x0000FF00u) >> 8);
2836
const u8 f4 = Truncate8(inst2.first & 0x000000FFu);
2837
const u8 f5 = Truncate8((inst2.value32 & 0xFF000000u) >> 24);
2838
const u8 f6 = Truncate8((inst2.value32 & 0x00FF0000u) >> 16);
2839
const u8 f7 = Truncate8((inst2.value32 & 0x0000FF00u) >> 8);
2840
const u8 f8 = Truncate8(inst2.value32 & 0x000000FFu);
2841
const u8 f9 = Truncate8((inst3.first & 0xFF000000u) >> 24);
2842
const u8 f10 = Truncate8((inst3.first & 0x00FF0000u) >> 16);
2843
const u8 f11 = Truncate8((inst3.first & 0x0000FF00u) >> 8);
2844
const u8 f12 = Truncate8(inst3.first & 0x000000FFu);
2845
const u8 f13 = Truncate8((inst3.value32 & 0xFF000000u) >> 24);
2846
const u8 f14 = Truncate8((inst3.value32 & 0x00FF0000u) >> 16);
2847
const u8 f15 = Truncate8((inst3.value32 & 0x0000FF00u) >> 8);
2848
const u8 f16 = Truncate8(inst3.value32 & 0x000000FFu);
2849
const u8 r1 = Truncate8((inst4.first & 0xFF000000u) >> 24);
2850
const u8 r2 = Truncate8((inst4.first & 0x00FF0000u) >> 16);
2851
const u8 r3 = Truncate8((inst4.first & 0x0000FF00u) >> 8);
2852
const u8 r4 = Truncate8(inst4.first & 0x000000FFu);
2853
const u8 r5 = Truncate8((inst4.value32 & 0xFF000000u) >> 24);
2854
const u8 r6 = Truncate8((inst4.value32 & 0x00FF0000u) >> 16);
2855
const u8 r7 = Truncate8((inst4.value32 & 0x0000FF00u) >> 8);
2856
const u8 r8 = Truncate8(inst4.value32 & 0x000000FFu);
2857
const u8 r9 = Truncate8((inst5.first & 0xFF000000u) >> 24);
2858
const u8 r10 = Truncate8((inst5.first & 0x00FF0000u) >> 16);
2859
const u8 r11 = Truncate8((inst5.first & 0x0000FF00u) >> 8);
2860
const u8 r12 = Truncate8(inst5.first & 0x000000FFu);
2861
const u8 r13 = Truncate8((inst5.value32 & 0xFF000000u) >> 24);
2862
const u8 r14 = Truncate8((inst5.value32 & 0x00FF0000u) >> 16);
2863
const u8 r15 = Truncate8((inst5.value32 & 0x0000FF00u) >> 8);
2864
const u8 r16 = Truncate8(inst5.value32 & 0x000000FFu);
2865
2866
for (u32 address = minaddress; address <= maxaddress; address += 2)
2867
{
2868
if ((DoMemoryRead<u8>(address) == f1 || f1 == wildcard) &&
2869
(DoMemoryRead<u8>(address + 1) == f2 || f2 == wildcard) &&
2870
(DoMemoryRead<u8>(address + 2) == f3 || f3 == wildcard) &&
2871
(DoMemoryRead<u8>(address + 3) == f4 || f4 == wildcard) &&
2872
(DoMemoryRead<u8>(address + 4) == f5 || f5 == wildcard) &&
2873
(DoMemoryRead<u8>(address + 5) == f6 || f6 == wildcard) &&
2874
(DoMemoryRead<u8>(address + 6) == f7 || f7 == wildcard) &&
2875
(DoMemoryRead<u8>(address + 7) == f8 || f8 == wildcard) &&
2876
(DoMemoryRead<u8>(address + 8) == f9 || f9 == wildcard) &&
2877
(DoMemoryRead<u8>(address + 9) == f10 || f10 == wildcard) &&
2878
(DoMemoryRead<u8>(address + 10) == f11 || f11 == wildcard) &&
2879
(DoMemoryRead<u8>(address + 11) == f12 || f12 == wildcard) &&
2880
(DoMemoryRead<u8>(address + 12) == f13 || f13 == wildcard) &&
2881
(DoMemoryRead<u8>(address + 13) == f14 || f14 == wildcard) &&
2882
(DoMemoryRead<u8>(address + 14) == f15 || f15 == wildcard) &&
2883
(DoMemoryRead<u8>(address + 15) == f16 || f16 == wildcard))
2884
{
2885
if (r1 != wildcard)
2886
DoMemoryWrite<u8>(address, r1);
2887
if (r2 != wildcard)
2888
DoMemoryWrite<u8>(address + 1, r2);
2889
if (r3 != wildcard)
2890
DoMemoryWrite<u8>(address + 2, r3);
2891
if (r4 != wildcard)
2892
DoMemoryWrite<u8>(address + 3, r4);
2893
if (r5 != wildcard)
2894
DoMemoryWrite<u8>(address + 4, r5);
2895
if (r6 != wildcard)
2896
DoMemoryWrite<u8>(address + 5, r6);
2897
if (r7 != wildcard)
2898
DoMemoryWrite<u8>(address + 6, r7);
2899
if (r8 != wildcard)
2900
DoMemoryWrite<u8>(address + 7, r8);
2901
if (r9 != wildcard)
2902
DoMemoryWrite<u8>(address + 8, r9);
2903
if (r10 != wildcard)
2904
DoMemoryWrite<u8>(address + 9, r10);
2905
if (r11 != wildcard)
2906
DoMemoryWrite<u8>(address + 10, r11);
2907
if (r12 != wildcard)
2908
DoMemoryWrite<u8>(address + 11, r12);
2909
if (r13 != wildcard)
2910
DoMemoryWrite<u8>(address + 12, r13);
2911
if (r14 != wildcard)
2912
DoMemoryWrite<u8>(address + 13, r14);
2913
if (r15 != wildcard)
2914
DoMemoryWrite<u8>(address + 14, r15);
2915
if (r16 != wildcard)
2916
DoMemoryWrite<u8>(address + 15, r16);
2917
address = address + 16;
2918
}
2919
}
2920
index += 5;
2921
}
2922
break;
2923
2924
case InstructionCode::CompareEqual16:
2925
{
2926
const u16 value = DoMemoryRead<u16>(inst.address);
2927
if (value == inst.value16)
2928
index++;
2929
else
2930
index = GetNextNonConditionalInstruction(index);
2931
}
2932
break;
2933
2934
case InstructionCode::CompareNotEqual16:
2935
{
2936
const u16 value = DoMemoryRead<u16>(inst.address);
2937
if (value != inst.value16)
2938
index++;
2939
else
2940
index = GetNextNonConditionalInstruction(index);
2941
}
2942
break;
2943
2944
case InstructionCode::CompareLess16:
2945
{
2946
const u16 value = DoMemoryRead<u16>(inst.address);
2947
if (value < inst.value16)
2948
index++;
2949
else
2950
index = GetNextNonConditionalInstruction(index);
2951
}
2952
break;
2953
2954
case InstructionCode::CompareGreater16:
2955
{
2956
const u16 value = DoMemoryRead<u16>(inst.address);
2957
if (value > inst.value16)
2958
index++;
2959
else
2960
index = GetNextNonConditionalInstruction(index);
2961
}
2962
break;
2963
2964
case InstructionCode::CompareEqual8:
2965
{
2966
const u8 value = DoMemoryRead<u8>(inst.address);
2967
if (value == inst.value8)
2968
index++;
2969
else
2970
index = GetNextNonConditionalInstruction(index);
2971
}
2972
break;
2973
2974
case InstructionCode::CompareNotEqual8:
2975
{
2976
const u8 value = DoMemoryRead<u8>(inst.address);
2977
if (value != inst.value8)
2978
index++;
2979
else
2980
index = GetNextNonConditionalInstruction(index);
2981
}
2982
break;
2983
2984
case InstructionCode::CompareLess8:
2985
{
2986
const u8 value = DoMemoryRead<u8>(inst.address);
2987
if (value < inst.value8)
2988
index++;
2989
else
2990
index = GetNextNonConditionalInstruction(index);
2991
}
2992
break;
2993
2994
case InstructionCode::CompareGreater8:
2995
{
2996
const u8 value = DoMemoryRead<u8>(inst.address);
2997
if (value > inst.value8)
2998
index++;
2999
else
3000
index = GetNextNonConditionalInstruction(index);
3001
}
3002
break;
3003
3004
case InstructionCode::CompareButtons: // D4
3005
{
3006
if (inst.value16 == GetControllerButtonBits())
3007
index++;
3008
else
3009
index = GetNextNonConditionalInstruction(index);
3010
}
3011
break;
3012
3013
case InstructionCode::ExtCheatRegisters: // 51
3014
{
3015
const u32 poke_value = inst.value32;
3016
const u8 cht_reg_no1 = Truncate8(inst.address & 0xFFu);
3017
const u8 cht_reg_no2 = Truncate8((inst.address & 0xFF00u) >> 8);
3018
const u8 cht_reg_no3 = Truncate8(inst.value32 & 0xFFu);
3019
const u8 sub_type = Truncate8((inst.address & 0xFF0000u) >> 16);
3020
const u16 cht_offset = Truncate16((inst.value32 & 0xFFFF0000u) >> 16);
3021
3022
switch (sub_type)
3023
{
3024
case 0x00: // Write the u8 from cht_register[cht_reg_no1] to address
3025
DoMemoryWrite<u8>(inst.value32, Truncate8(cht_register[cht_reg_no1]) & 0xFFu);
3026
break;
3027
case 0x01: // Read the u8 from address to cht_register[cht_reg_no1]
3028
cht_register[cht_reg_no1] = DoMemoryRead<u8>(inst.value32);
3029
break;
3030
case 0x02: // Write the u8 from address field to the address stored in cht_register[cht_reg_no1]
3031
DoMemoryWrite<u8>(cht_register[cht_reg_no1], Truncate8(poke_value & 0xFFu));
3032
break;
3033
case 0x03: // Write the u8 from cht_register[cht_reg_no2] to cht_register[cht_reg_no1]
3034
// and add the u8 from the address field to it
3035
cht_register[cht_reg_no1] = Truncate8(cht_register[cht_reg_no2] & 0xFFu) + Truncate8(poke_value & 0xFFu);
3036
break;
3037
case 0x04: // Write the u8 from the value stored in cht_register[cht_reg_no2] + poke_value to the address
3038
// stored in cht_register[cht_reg_no1]
3039
DoMemoryWrite<u8>(cht_register[cht_reg_no1],
3040
Truncate8(cht_register[cht_reg_no2] & 0xFFu) + Truncate8(poke_value & 0xFFu));
3041
break;
3042
case 0x05: // Write the u8 poke value to cht_register[cht_reg_no1]
3043
cht_register[cht_reg_no1] = Truncate8(poke_value & 0xFFu);
3044
break;
3045
case 0x06: // Read the u8 value from the address (cht_register[cht_reg_no2] + poke_value) to
3046
// cht_register[cht_reg_no1]
3047
cht_register[cht_reg_no1] = DoMemoryRead<u8>(cht_register[cht_reg_no2] + poke_value);
3048
break;
3049
case 0x07: // Write the u8 poke_value to a specific index of a single array in a series of consecutive arrays
3050
// This cheat type requires a separate cheat to set up 4 consecutive cht_arrays before this will work
3051
// cht_register[cht_reg_no1] = the base address of the first element of the first array
3052
// cht_register[cht_reg_no1+1] = the array size (basically the address diff between the start of each array)
3053
// cht_register[cht_reg_no1+2] = the index of which array in the series to poke (this must be greater than
3054
// 0) cht_register[cht_reg_no1+3] must == 0xD0D0 to ensure it only pokes when the above cht_regs have been
3055
// set
3056
// (safety valve)
3057
// cht_offset = the index of the individual array to change (so must be 0 to cht_register[cht_reg_no1+1])
3058
if ((cht_reg_no1 <= (std::size(cht_register) - 4)) && cht_register[cht_reg_no1 + 3] == 0xD0D0 &&
3059
cht_register[cht_reg_no1 + 2] > 0 && cht_register[cht_reg_no1 + 1] >= cht_offset)
3060
{
3061
DoMemoryWrite<u8>((cht_register[cht_reg_no1] - cht_register[cht_reg_no1 + 1]) +
3062
(cht_register[cht_reg_no1 + 1] * cht_register[cht_reg_no1 + 2]) + cht_offset,
3063
Truncate8(poke_value & 0xFFu));
3064
}
3065
break;
3066
3067
case 0x40: // Write the u16 from cht_register[cht_reg_no1] to address
3068
DoMemoryWrite<u16>(inst.value32, Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3069
break;
3070
case 0x41: // Read the u16 from address to cht_register[cht_reg_no1]
3071
cht_register[cht_reg_no1] = DoMemoryRead<u16>(inst.value32);
3072
break;
3073
case 0x42: // Write the u16 from address field to the address stored in cht_register[cht_reg_no1]
3074
DoMemoryWrite<u16>(cht_register[cht_reg_no1], Truncate16(poke_value & 0xFFFFu));
3075
break;
3076
case 0x43: // Write the u16 from cht_register[cht_reg_no2] to cht_register[cht_reg_no1]
3077
// and add the u16 from the address field to it
3078
cht_register[cht_reg_no1] =
3079
Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) + Truncate16(poke_value & 0xFFFFu);
3080
break;
3081
case 0x44: // Write the u16 from the value stored in cht_register[cht_reg_no2] + poke_value to the address
3082
// stored in cht_register[cht_reg_no1]
3083
DoMemoryWrite<u16>(cht_register[cht_reg_no1],
3084
Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) + Truncate16(poke_value & 0xFFFFu));
3085
break;
3086
case 0x45: // Write the u16 poke value to cht_register[cht_reg_no1]
3087
cht_register[cht_reg_no1] = Truncate16(poke_value & 0xFFFFu);
3088
break;
3089
case 0x46: // Read the u16 value from the address (cht_register[cht_reg_no2] + poke_value) to
3090
// cht_register[cht_reg_no1]
3091
cht_register[cht_reg_no1] = DoMemoryRead<u16>(cht_register[cht_reg_no2] + poke_value);
3092
break;
3093
case 0x47: // Write the u16 poke_value to a specific index of a single array in a series of consecutive arrays
3094
// This cheat type requires a separate cheat to set up 4 consecutive cht_arrays before this will work
3095
// cht_register[cht_reg_no1] = the base address of the first element of the first array
3096
// cht_register[cht_reg_no1+1] = the array size (basically the address diff between the start of each array)
3097
// cht_register[cht_reg_no1+2] = the index of which array in the series to poke (this must be greater than
3098
// 0) cht_register[cht_reg_no1+3] must == 0xD0D0 to ensure it only pokes when the above cht_regs have been
3099
// set
3100
// (safety valve)
3101
// cht_offset = the index of the individual array to change (so must be 0 to cht_register[cht_reg_no1+1])
3102
if ((cht_reg_no1 <= (std::size(cht_register) - 4)) && cht_register[cht_reg_no1 + 3] == 0xD0D0 &&
3103
cht_register[cht_reg_no1 + 2] > 0 && cht_register[cht_reg_no1 + 1] >= cht_offset)
3104
{
3105
DoMemoryWrite<u16>((cht_register[cht_reg_no1] - cht_register[cht_reg_no1 + 1]) +
3106
(cht_register[cht_reg_no1 + 1] * cht_register[cht_reg_no1 + 2]) + cht_offset,
3107
Truncate16(poke_value & 0xFFFFu));
3108
}
3109
break;
3110
3111
case 0x80: // Write the u32 from cht_register[cht_reg_no1] to address
3112
DoMemoryWrite<u32>(inst.value32, cht_register[cht_reg_no1]);
3113
break;
3114
case 0x81: // Read the u32 from address to cht_register[cht_reg_no1]
3115
cht_register[cht_reg_no1] = DoMemoryRead<u32>(inst.value32);
3116
break;
3117
case 0x82: // Write the u32 from address field to the address stored in cht_register[cht_reg_no]
3118
DoMemoryWrite<u32>(cht_register[cht_reg_no1], poke_value);
3119
break;
3120
case 0x83: // Write the u32 from cht_register[cht_reg_no2] to cht_register[cht_reg_no1]
3121
// and add the u32 from the address field to it
3122
cht_register[cht_reg_no1] = cht_register[cht_reg_no2] + poke_value;
3123
break;
3124
case 0x84: // Write the u32 from the value stored in cht_register[cht_reg_no2] + poke_value to the address
3125
// stored in cht_register[cht_reg_no1]
3126
DoMemoryWrite<u32>(cht_register[cht_reg_no1], cht_register[cht_reg_no2] + poke_value);
3127
break;
3128
case 0x85: // Write the u32 poke value to cht_register[cht_reg_no1]
3129
cht_register[cht_reg_no1] = poke_value;
3130
break;
3131
case 0x86: // Read the u32 value from the address (cht_register[cht_reg_no2] + poke_value) to
3132
// cht_register[cht_reg_no1]
3133
cht_register[cht_reg_no1] = DoMemoryRead<u32>(cht_register[cht_reg_no2] + poke_value);
3134
break;
3135
// Do not use 0x87 as it's not possible to duplicate 0x07, 0x47 for a 32 bit write as not enough characters
3136
3137
case 0xC0: // Reg3 = Reg2 + Reg1
3138
cht_register[cht_reg_no3] = cht_register[cht_reg_no2] + cht_register[cht_reg_no1];
3139
break;
3140
case 0xC1: // Reg3 = Reg2 - Reg1
3141
cht_register[cht_reg_no3] = cht_register[cht_reg_no2] - cht_register[cht_reg_no1];
3142
break;
3143
case 0xC2: // Reg3 = Reg2 * Reg1
3144
cht_register[cht_reg_no3] = cht_register[cht_reg_no2] * cht_register[cht_reg_no1];
3145
break;
3146
case 0xC3: // Reg3 = Reg2 / Reg1 with DIV0 handling
3147
if (cht_register[cht_reg_no1] == 0)
3148
cht_register[cht_reg_no3] = 0;
3149
else
3150
cht_register[cht_reg_no3] = cht_register[cht_reg_no2] / cht_register[cht_reg_no1];
3151
break;
3152
case 0xC4: // Reg3 = Reg2 % Reg1 (with DIV0 handling)
3153
if (cht_register[cht_reg_no1] == 0)
3154
cht_register[cht_reg_no3] = cht_register[cht_reg_no2];
3155
else
3156
cht_register[cht_reg_no3] = cht_register[cht_reg_no2] % cht_register[cht_reg_no1];
3157
break;
3158
case 0xC5: // Reg3 = Reg2 & Reg1
3159
cht_register[cht_reg_no3] = cht_register[cht_reg_no2] & cht_register[cht_reg_no1];
3160
break;
3161
case 0xC6: // Reg3 = Reg2 | Reg1
3162
cht_register[cht_reg_no3] = cht_register[cht_reg_no2] | cht_register[cht_reg_no1];
3163
break;
3164
case 0xC7: // Reg3 = Reg2 ^ Reg1
3165
cht_register[cht_reg_no3] = cht_register[cht_reg_no2] ^ cht_register[cht_reg_no1];
3166
break;
3167
case 0xC8: // Reg3 = ~Reg1
3168
cht_register[cht_reg_no3] = ~cht_register[cht_reg_no1];
3169
break;
3170
case 0xC9: // Reg3 = Reg1 << X
3171
cht_register[cht_reg_no3] = cht_register[cht_reg_no1] << cht_reg_no2;
3172
break;
3173
case 0xCA: // Reg3 = Reg1 >> X
3174
cht_register[cht_reg_no3] = cht_register[cht_reg_no1] >> cht_reg_no2;
3175
break;
3176
// Lots of options exist for expanding into this space
3177
default:
3178
break;
3179
}
3180
index++;
3181
}
3182
break;
3183
3184
case InstructionCode::SkipIfNotEqual16: // C0
3185
case InstructionCode::ExtSkipIfNotEqual32: // A4
3186
case InstructionCode::SkipIfButtonsNotEqual: // D5
3187
case InstructionCode::SkipIfButtonsEqual: // D6
3188
case InstructionCode::ExtSkipIfNotLess8: // C3
3189
case InstructionCode::ExtSkipIfNotGreater8: // C4
3190
case InstructionCode::ExtSkipIfNotLess16: // C5
3191
case InstructionCode::ExtSkipIfNotGreater16: // C6
3192
case InstructionCode::ExtMultiConditionals: // F6
3193
{
3194
index++;
3195
3196
bool activate_codes;
3197
switch (inst.code)
3198
{
3199
case InstructionCode::SkipIfNotEqual16: // C0
3200
activate_codes = (DoMemoryRead<u16>(inst.address) == inst.value16);
3201
break;
3202
case InstructionCode::ExtSkipIfNotEqual32: // A4
3203
activate_codes = (DoMemoryRead<u32>(inst.address) == inst.value32);
3204
break;
3205
case InstructionCode::SkipIfButtonsNotEqual: // D5
3206
activate_codes = (GetControllerButtonBits() == inst.value16);
3207
break;
3208
case InstructionCode::SkipIfButtonsEqual: // D6
3209
activate_codes = (GetControllerButtonBits() != inst.value16);
3210
break;
3211
case InstructionCode::ExtSkipIfNotLess8: // C3
3212
activate_codes = (DoMemoryRead<u8>(inst.address) < inst.value8);
3213
break;
3214
case InstructionCode::ExtSkipIfNotGreater8: // C4
3215
activate_codes = (DoMemoryRead<u8>(inst.address) > inst.value8);
3216
break;
3217
case InstructionCode::ExtSkipIfNotLess16: // C5
3218
activate_codes = (DoMemoryRead<u16>(inst.address) < inst.value16);
3219
break;
3220
case InstructionCode::ExtSkipIfNotGreater16: // C6
3221
activate_codes = (DoMemoryRead<u16>(inst.address) > inst.value16);
3222
break;
3223
case InstructionCode::ExtMultiConditionals: // F6
3224
{
3225
// Ensure any else if or else that are hit outside the if context are skipped
3226
if ((inst.value32 & 0xFFFFFF00u) != 0x1F000000)
3227
{
3228
activate_codes = false;
3229
break;
3230
}
3231
for (;;)
3232
{
3233
const u8 totalConds = Truncate8(instructions[index - 1].value32 & 0x000000FFu);
3234
const u8 conditionType = Truncate8(instructions[index - 1].address & 0x000000FFu);
3235
3236
bool conditions_check;
3237
3238
if (conditionType == 0x00 && totalConds > 0) // AND
3239
{
3240
conditions_check = true;
3241
3242
for (int i = 1; totalConds >= i; index++, i++)
3243
{
3244
switch (instructions[index].code)
3245
{
3246
case InstructionCode::CompareEqual16: // D0
3247
conditions_check &=
3248
(DoMemoryRead<u16>(instructions[index].address) == instructions[index].value16);
3249
break;
3250
case InstructionCode::CompareNotEqual16: // D1
3251
conditions_check &=
3252
(DoMemoryRead<u16>(instructions[index].address) != instructions[index].value16);
3253
break;
3254
case InstructionCode::CompareLess16: // D2
3255
conditions_check &=
3256
(DoMemoryRead<u16>(instructions[index].address) < instructions[index].value16);
3257
break;
3258
case InstructionCode::CompareGreater16: // D3
3259
conditions_check &=
3260
(DoMemoryRead<u16>(instructions[index].address) > instructions[index].value16);
3261
break;
3262
case InstructionCode::CompareEqual8: // E0
3263
conditions_check &= (DoMemoryRead<u8>(instructions[index].address) == instructions[index].value8);
3264
break;
3265
case InstructionCode::CompareNotEqual8: // E1
3266
conditions_check &= (DoMemoryRead<u8>(instructions[index].address) != instructions[index].value8);
3267
break;
3268
case InstructionCode::CompareLess8: // E2
3269
conditions_check &= (DoMemoryRead<u8>(instructions[index].address) < instructions[index].value8);
3270
break;
3271
case InstructionCode::CompareGreater8: // E3
3272
conditions_check &= (DoMemoryRead<u8>(instructions[index].address) > instructions[index].value8);
3273
break;
3274
case InstructionCode::ExtCompareEqual32: // A0
3275
conditions_check &=
3276
(DoMemoryRead<u32>(instructions[index].address) == instructions[index].value32);
3277
break;
3278
case InstructionCode::ExtCompareNotEqual32: // A1
3279
conditions_check &=
3280
(DoMemoryRead<u32>(instructions[index].address) != instructions[index].value32);
3281
break;
3282
case InstructionCode::ExtCompareLess32: // A2
3283
conditions_check &=
3284
(DoMemoryRead<u32>(instructions[index].address) < instructions[index].value32);
3285
break;
3286
case InstructionCode::ExtCompareGreater32: // A3
3287
conditions_check &=
3288
(DoMemoryRead<u32>(instructions[index].address) > instructions[index].value32);
3289
break;
3290
case InstructionCode::ExtCompareBitsSet8: // E4 Internal to F6
3291
conditions_check &=
3292
(instructions[index].value8 ==
3293
(DoMemoryRead<u8>(instructions[index].address) & instructions[index].value8));
3294
break;
3295
case InstructionCode::ExtCompareBitsClear8: // E5 Internal to F6
3296
conditions_check &=
3297
((DoMemoryRead<u8>(instructions[index].address) & instructions[index].value8) == 0);
3298
break;
3299
case InstructionCode::ExtBitCompareButtons: // D7
3300
{
3301
const u32 frame_compare_value = instructions[index].address & 0xFFFFu;
3302
const u8 cht_reg_no = Truncate8((instructions[index].value32 & 0xFF000000u) >> 24);
3303
const bool bit_comparison_type = ((instructions[index].address & 0x100000u) >> 20);
3304
const u8 frame_comparison = Truncate8((instructions[index].address & 0xF0000u) >> 16);
3305
const u32 check_value = (instructions[index].value32 & 0xFFFFFFu);
3306
const u32 value1 = GetControllerButtonBits();
3307
const u32 value2 = GetControllerAnalogBits();
3308
u32 value = value1 | value2;
3309
3310
if ((bit_comparison_type == false && check_value == (value & check_value)) // Check Bits are set
3311
||
3312
(bit_comparison_type == true && check_value != (value & check_value))) // Check Bits are clear
3313
{
3314
cht_register[cht_reg_no] += 1;
3315
switch (frame_comparison)
3316
{
3317
case 0x0: // No comparison on frame count, just do it
3318
conditions_check &= true;
3319
break;
3320
case 0x1: // Check if frame_compare_value == current count
3321
conditions_check &= (cht_register[cht_reg_no] == frame_compare_value);
3322
break;
3323
case 0x2: // Check if frame_compare_value < current count
3324
conditions_check &= (cht_register[cht_reg_no] < frame_compare_value);
3325
break;
3326
case 0x3: // Check if frame_compare_value > current count
3327
conditions_check &= (cht_register[cht_reg_no] > frame_compare_value);
3328
break;
3329
case 0x4: // Check if frame_compare_value != current count
3330
conditions_check &= (cht_register[cht_reg_no] != frame_compare_value);
3331
break;
3332
default:
3333
conditions_check &= false;
3334
break;
3335
}
3336
}
3337
else
3338
{
3339
cht_register[cht_reg_no] = 0;
3340
conditions_check &= false;
3341
}
3342
break;
3343
}
3344
default:
3345
ERROR_LOG("Incorrect conditional instruction (see chtdb.txt for supported instructions)");
3346
return;
3347
}
3348
}
3349
}
3350
else if (conditionType == 0x01 && totalConds > 0) // OR
3351
{
3352
conditions_check = false;
3353
3354
for (int i = 1; totalConds >= i; index++, i++)
3355
{
3356
switch (instructions[index].code)
3357
{
3358
case InstructionCode::CompareEqual16: // D0
3359
conditions_check |=
3360
(DoMemoryRead<u16>(instructions[index].address) == instructions[index].value16);
3361
break;
3362
case InstructionCode::CompareNotEqual16: // D1
3363
conditions_check |=
3364
(DoMemoryRead<u16>(instructions[index].address) != instructions[index].value16);
3365
break;
3366
case InstructionCode::CompareLess16: // D2
3367
conditions_check |=
3368
(DoMemoryRead<u16>(instructions[index].address) < instructions[index].value16);
3369
break;
3370
case InstructionCode::CompareGreater16: // D3
3371
conditions_check |=
3372
(DoMemoryRead<u16>(instructions[index].address) > instructions[index].value16);
3373
break;
3374
case InstructionCode::CompareEqual8: // E0
3375
conditions_check |= (DoMemoryRead<u8>(instructions[index].address) == instructions[index].value8);
3376
break;
3377
case InstructionCode::CompareNotEqual8: // E1
3378
conditions_check |= (DoMemoryRead<u8>(instructions[index].address) != instructions[index].value8);
3379
break;
3380
case InstructionCode::CompareLess8: // E2
3381
conditions_check |= (DoMemoryRead<u8>(instructions[index].address) < instructions[index].value8);
3382
break;
3383
case InstructionCode::CompareGreater8: // E3
3384
conditions_check |= (DoMemoryRead<u8>(instructions[index].address) > instructions[index].value8);
3385
break;
3386
case InstructionCode::ExtCompareEqual32: // A0
3387
conditions_check |=
3388
(DoMemoryRead<u32>(instructions[index].address) == instructions[index].value32);
3389
break;
3390
case InstructionCode::ExtCompareNotEqual32: // A1
3391
conditions_check |=
3392
(DoMemoryRead<u32>(instructions[index].address) != instructions[index].value32);
3393
break;
3394
case InstructionCode::ExtCompareLess32: // A2
3395
conditions_check |=
3396
(DoMemoryRead<u32>(instructions[index].address) < instructions[index].value32);
3397
break;
3398
case InstructionCode::ExtCompareGreater32: // A3
3399
conditions_check |=
3400
(DoMemoryRead<u32>(instructions[index].address) > instructions[index].value32);
3401
break;
3402
case InstructionCode::ExtCompareBitsSet8: // E4 Internal to F6
3403
conditions_check |=
3404
(instructions[index].value8 ==
3405
(DoMemoryRead<u8>(instructions[index].address) & instructions[index].value8));
3406
break;
3407
case InstructionCode::ExtCompareBitsClear8: // E5 Internal to F6
3408
conditions_check |=
3409
((DoMemoryRead<u8>(instructions[index].address) & instructions[index].value8) == 0);
3410
break;
3411
case InstructionCode::ExtBitCompareButtons: // D7
3412
{
3413
const u32 frame_compare_value = instructions[index].address & 0xFFFFu;
3414
const u8 cht_reg_no = Truncate8((instructions[index].value32 & 0xFF000000u) >> 24);
3415
const bool bit_comparison_type = ((instructions[index].address & 0x100000u) >> 20);
3416
const u8 frame_comparison = Truncate8((instructions[index].address & 0xF0000u) >> 16);
3417
const u32 check_value = (instructions[index].value32 & 0xFFFFFFu);
3418
const u32 value1 = GetControllerButtonBits();
3419
const u32 value2 = GetControllerAnalogBits();
3420
u32 value = value1 | value2;
3421
3422
if ((bit_comparison_type == false && check_value == (value & check_value)) // Check Bits are set
3423
||
3424
(bit_comparison_type == true && check_value != (value & check_value))) // Check Bits are clear
3425
{
3426
cht_register[cht_reg_no] += 1;
3427
switch (frame_comparison)
3428
{
3429
case 0x0: // No comparison on frame count, just do it
3430
conditions_check |= true;
3431
break;
3432
case 0x1: // Check if frame_compare_value == current count
3433
conditions_check |= (cht_register[cht_reg_no] == frame_compare_value);
3434
break;
3435
case 0x2: // Check if frame_compare_value < current count
3436
conditions_check |= (cht_register[cht_reg_no] < frame_compare_value);
3437
break;
3438
case 0x3: // Check if frame_compare_value > current count
3439
conditions_check |= (cht_register[cht_reg_no] > frame_compare_value);
3440
break;
3441
case 0x4: // Check if frame_compare_value != current count
3442
conditions_check |= (cht_register[cht_reg_no] != frame_compare_value);
3443
break;
3444
default:
3445
conditions_check |= false;
3446
break;
3447
}
3448
}
3449
else
3450
{
3451
cht_register[cht_reg_no] = 0;
3452
conditions_check |= false;
3453
}
3454
break;
3455
}
3456
default:
3457
ERROR_LOG("Incorrect conditional instruction (see chtdb.txt for supported instructions)");
3458
return;
3459
}
3460
}
3461
}
3462
else
3463
{
3464
ERROR_LOG("Incomplete multi conditional instruction");
3465
return;
3466
}
3467
if (conditions_check == true)
3468
{
3469
activate_codes = true;
3470
break;
3471
}
3472
else
3473
{ // parse through to 00000000 FFFF and peek if next line is a F6 type associated with a ELSE
3474
activate_codes = false;
3475
// skip to the next separator (00000000 FFFF), or end
3476
constexpr u64 separator_value = UINT64_C(0x000000000000FFFF);
3477
constexpr u64 else_value = UINT64_C(0x00000000E15E0000);
3478
constexpr u64 elseif_value = UINT64_C(0x00000000E15E1F00);
3479
while (index < count)
3480
{
3481
const u64 bits = instructions[index++].bits;
3482
if (bits == separator_value)
3483
{
3484
const u64 bits_ahead = instructions[index].bits;
3485
if ((bits_ahead & 0xFFFFFF00u) == elseif_value)
3486
{
3487
break;
3488
}
3489
if ((bits_ahead & 0xFFFF0000u) == else_value)
3490
{
3491
// index++;
3492
activate_codes = true;
3493
break;
3494
}
3495
index--;
3496
break;
3497
}
3498
if ((bits & 0xFFFFFF00u) == elseif_value)
3499
{
3500
// index--;
3501
break;
3502
}
3503
if ((bits & 0xFFFFFFFFu) == else_value)
3504
{
3505
// index++;
3506
activate_codes = true;
3507
break;
3508
}
3509
}
3510
if (activate_codes == true)
3511
break;
3512
}
3513
}
3514
break;
3515
}
3516
default:
3517
activate_codes = false;
3518
break;
3519
}
3520
3521
if (activate_codes)
3522
{
3523
// execute following instructions
3524
continue;
3525
}
3526
3527
// skip to the next separator (00000000 FFFF), or end
3528
constexpr u64 separator_value = UINT64_C(0x000000000000FFFF);
3529
while (index < count)
3530
{
3531
// we don't want to execute the separator instruction
3532
const u64 bits = instructions[index++].bits;
3533
if (bits == separator_value)
3534
break;
3535
}
3536
}
3537
break;
3538
3539
case InstructionCode::ExtBitCompareButtons: // D7
3540
{
3541
index++;
3542
bool activate_codes;
3543
const u32 frame_compare_value = inst.address & 0xFFFFu;
3544
const u8 cht_reg_no = Truncate8((inst.value32 & 0xFF000000u) >> 24);
3545
const bool bit_comparison_type = ((inst.address & 0x100000u) >> 20);
3546
const u8 frame_comparison = Truncate8((inst.address & 0xF0000u) >> 16);
3547
const u32 check_value = (inst.value32 & 0xFFFFFFu);
3548
const u32 value1 = GetControllerButtonBits();
3549
const u32 value2 = GetControllerAnalogBits();
3550
u32 value = value1 | value2;
3551
3552
if ((bit_comparison_type == false && check_value == (value & check_value)) // Check Bits are set
3553
|| (bit_comparison_type == true && check_value != (value & check_value))) // Check Bits are clear
3554
{
3555
cht_register[cht_reg_no] += 1;
3556
switch (frame_comparison)
3557
{
3558
case 0x0: // No comparison on frame count, just do it
3559
activate_codes = true;
3560
break;
3561
case 0x1: // Check if frame_compare_value == current count
3562
activate_codes = (cht_register[cht_reg_no] == frame_compare_value);
3563
break;
3564
case 0x2: // Check if frame_compare_value < current count
3565
activate_codes = (cht_register[cht_reg_no] < frame_compare_value);
3566
break;
3567
case 0x3: // Check if frame_compare_value > current count
3568
activate_codes = (cht_register[cht_reg_no] > frame_compare_value);
3569
break;
3570
case 0x4: // Check if frame_compare_value != current count
3571
activate_codes = (cht_register[cht_reg_no] != frame_compare_value);
3572
break;
3573
default:
3574
activate_codes = false;
3575
break;
3576
}
3577
}
3578
else
3579
{
3580
cht_register[cht_reg_no] = 0;
3581
activate_codes = false;
3582
}
3583
3584
if (activate_codes)
3585
{
3586
// execute following instructions
3587
continue;
3588
}
3589
3590
// skip to the next separator (00000000 FFFF), or end
3591
constexpr u64 separator_value = UINT64_C(0x000000000000FFFF);
3592
while (index < count)
3593
{
3594
// we don't want to execute the separator instruction
3595
const u64 bits = instructions[index++].bits;
3596
if (bits == separator_value)
3597
break;
3598
}
3599
}
3600
break;
3601
3602
case InstructionCode::ExtCheatRegistersCompare: // 52
3603
{
3604
index++;
3605
bool activate_codes = false;
3606
const u8 cht_reg_no1 = Truncate8(inst.address & 0xFFu);
3607
const u8 cht_reg_no2 = Truncate8((inst.address & 0xFF00u) >> 8);
3608
const u8 sub_type = Truncate8((inst.first & 0xFF0000u) >> 16);
3609
3610
switch (sub_type)
3611
{
3612
case 0x00:
3613
activate_codes =
3614
(Truncate8(cht_register[cht_reg_no2] & 0xFFu) == Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3615
break;
3616
case 0x01:
3617
activate_codes =
3618
(Truncate8(cht_register[cht_reg_no2] & 0xFFu) != Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3619
break;
3620
case 0x02:
3621
activate_codes =
3622
(Truncate8(cht_register[cht_reg_no2] & 0xFFu) > Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3623
break;
3624
case 0x03:
3625
activate_codes =
3626
(Truncate8(cht_register[cht_reg_no2] & 0xFFu) >= Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3627
break;
3628
case 0x04:
3629
activate_codes =
3630
(Truncate8(cht_register[cht_reg_no2] & 0xFFu) < Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3631
break;
3632
case 0x05:
3633
activate_codes =
3634
(Truncate8(cht_register[cht_reg_no2] & 0xFFu) <= Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3635
break;
3636
case 0x06:
3637
activate_codes =
3638
((Truncate8(cht_register[cht_reg_no2] & 0xFFu) & Truncate8(cht_register[cht_reg_no1] & 0xFFu)) ==
3639
(Truncate8(cht_register[cht_reg_no1] & 0xFFu)));
3640
break;
3641
case 0x07:
3642
activate_codes =
3643
((Truncate8(cht_register[cht_reg_no2] & 0xFFu) & Truncate8(cht_register[cht_reg_no1] & 0xFFu)) !=
3644
(Truncate8(cht_register[cht_reg_no1] & 0xFFu)));
3645
break;
3646
case 0x0A:
3647
activate_codes =
3648
((Truncate8(cht_register[cht_reg_no2] & 0xFFu) & Truncate8(cht_register[cht_reg_no1] & 0xFFu)) ==
3649
(Truncate8(cht_register[cht_reg_no2] & 0xFFu)));
3650
break;
3651
case 0x0B:
3652
activate_codes =
3653
((Truncate8(cht_register[cht_reg_no2] & 0xFFu) & Truncate8(cht_register[cht_reg_no1] & 0xFFu)) !=
3654
(Truncate8(cht_register[cht_reg_no2] & 0xFFu)));
3655
break;
3656
case 0x10:
3657
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) == inst.value8);
3658
break;
3659
case 0x11:
3660
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) != inst.value8);
3661
break;
3662
case 0x12:
3663
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) > inst.value8);
3664
break;
3665
case 0x13:
3666
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) >= inst.value8);
3667
break;
3668
case 0x14:
3669
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) < inst.value8);
3670
break;
3671
case 0x15:
3672
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) <= inst.value8);
3673
break;
3674
case 0x16:
3675
activate_codes = ((Truncate8(cht_register[cht_reg_no1] & 0xFFu) & inst.value8) == inst.value8);
3676
break;
3677
case 0x17:
3678
activate_codes = ((Truncate8(cht_register[cht_reg_no1] & 0xFFu) & inst.value8) != inst.value8);
3679
break;
3680
case 0x18:
3681
activate_codes =
3682
((Truncate8(cht_register[cht_reg_no1] & 0xFFu) > inst.value8) &&
3683
(Truncate8(cht_register[cht_reg_no1] & 0xFFu) < Truncate8((inst.value32 & 0xFF0000u) >> 16)));
3684
break;
3685
case 0x19:
3686
activate_codes =
3687
((Truncate8(cht_register[cht_reg_no1] & 0xFFu) >= inst.value8) &&
3688
(Truncate8(cht_register[cht_reg_no1] & 0xFFu) <= Truncate8((inst.value32 & 0xFF0000u) >> 16)));
3689
break;
3690
case 0x1A:
3691
activate_codes = ((Truncate8(cht_register[cht_reg_no2] & 0xFFu) & inst.value8) ==
3692
Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3693
break;
3694
case 0x1B:
3695
activate_codes = ((Truncate8(cht_register[cht_reg_no1] & 0xFFu) & inst.value8) !=
3696
Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3697
break;
3698
case 0x20:
3699
activate_codes =
3700
(DoMemoryRead<u8>(cht_register[cht_reg_no2]) == DoMemoryRead<u8>(cht_register[cht_reg_no1]));
3701
break;
3702
case 0x21:
3703
activate_codes =
3704
(DoMemoryRead<u8>(cht_register[cht_reg_no2]) != DoMemoryRead<u8>(cht_register[cht_reg_no1]));
3705
break;
3706
case 0x22:
3707
activate_codes =
3708
(DoMemoryRead<u8>(cht_register[cht_reg_no2]) > DoMemoryRead<u8>(cht_register[cht_reg_no1]));
3709
break;
3710
case 0x23:
3711
activate_codes =
3712
(DoMemoryRead<u8>(cht_register[cht_reg_no2]) >= DoMemoryRead<u8>(cht_register[cht_reg_no1]));
3713
break;
3714
case 0x24:
3715
activate_codes =
3716
(DoMemoryRead<u8>(cht_register[cht_reg_no2]) < DoMemoryRead<u8>(cht_register[cht_reg_no1]));
3717
break;
3718
case 0x25:
3719
activate_codes =
3720
(DoMemoryRead<u8>(cht_register[cht_reg_no2]) <= DoMemoryRead<u8>(cht_register[cht_reg_no1]));
3721
break;
3722
case 0x26:
3723
activate_codes = ((DoMemoryRead<u8>(cht_register[cht_reg_no1]) & inst.value8) == inst.value8);
3724
break;
3725
case 0x27:
3726
activate_codes = ((DoMemoryRead<u8>(cht_register[cht_reg_no1]) & inst.value8) != inst.value8);
3727
break;
3728
case 0x28:
3729
activate_codes =
3730
((DoMemoryRead<u8>(cht_register[cht_reg_no1]) > inst.value8) &&
3731
(DoMemoryRead<u8>(cht_register[cht_reg_no1]) < Truncate8((inst.value32 & 0xFF0000u) >> 16)));
3732
break;
3733
case 0x29:
3734
activate_codes =
3735
((DoMemoryRead<u8>(cht_register[cht_reg_no1]) >= inst.value8) &&
3736
(DoMemoryRead<u8>(cht_register[cht_reg_no1]) <= Truncate8((inst.value32 & 0xFF0000u) >> 16)));
3737
break;
3738
case 0x2A:
3739
activate_codes = ((DoMemoryRead<u8>(cht_register[cht_reg_no1]) & inst.value8) ==
3740
DoMemoryRead<u8>(cht_register[cht_reg_no1]));
3741
break;
3742
case 0x2B:
3743
activate_codes = ((DoMemoryRead<u8>(cht_register[cht_reg_no1]) & inst.value8) !=
3744
DoMemoryRead<u8>(cht_register[cht_reg_no1]));
3745
break;
3746
case 0x30:
3747
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) == DoMemoryRead<u8>(inst.value32));
3748
break;
3749
case 0x31:
3750
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) != DoMemoryRead<u8>(inst.value32));
3751
break;
3752
case 0x32:
3753
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) > DoMemoryRead<u8>(inst.value32));
3754
break;
3755
case 0x33:
3756
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) >= DoMemoryRead<u8>(inst.value32));
3757
break;
3758
case 0x34:
3759
activate_codes = (Truncate8(cht_register[cht_reg_no1] & 0xFFu) < DoMemoryRead<u8>(inst.value32));
3760
break;
3761
case 0x36:
3762
activate_codes = ((Truncate8(cht_register[cht_reg_no1] & 0xFFu) & DoMemoryRead<u8>(inst.value32)) ==
3763
DoMemoryRead<u8>(inst.value32));
3764
break;
3765
case 0x37:
3766
activate_codes = ((Truncate8(cht_register[cht_reg_no1] & 0xFFu) & DoMemoryRead<u8>(inst.value32)) !=
3767
DoMemoryRead<u8>(inst.value32));
3768
break;
3769
case 0x3A:
3770
activate_codes = ((Truncate8(cht_register[cht_reg_no1] & 0xFFu) & DoMemoryRead<u8>(inst.value32)) ==
3771
Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3772
break;
3773
case 0x3B:
3774
activate_codes = ((Truncate8(cht_register[cht_reg_no1] & 0xFFu) & DoMemoryRead<u8>(inst.value32)) !=
3775
Truncate8(cht_register[cht_reg_no1] & 0xFFu));
3776
break;
3777
case 0x40:
3778
activate_codes =
3779
(Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) == Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3780
break;
3781
case 0x41:
3782
activate_codes =
3783
(Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) != Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3784
break;
3785
case 0x42:
3786
activate_codes =
3787
(Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) > Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3788
break;
3789
case 0x43:
3790
activate_codes =
3791
(Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) >= Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3792
break;
3793
case 0x44:
3794
activate_codes =
3795
(Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) < Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3796
break;
3797
case 0x45:
3798
activate_codes =
3799
(Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) <= Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3800
break;
3801
case 0x46:
3802
activate_codes =
3803
((Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) & Truncate16(cht_register[cht_reg_no1] & 0xFFFFu)) ==
3804
Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3805
break;
3806
case 0x47:
3807
activate_codes =
3808
((Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) & Truncate16(cht_register[cht_reg_no1] & 0xFFFFu)) !=
3809
Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3810
break;
3811
case 0x4A:
3812
activate_codes =
3813
((Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) & Truncate16(cht_register[cht_reg_no1] & 0xFFFFu)) ==
3814
Truncate16(cht_register[cht_reg_no2] & 0xFFFFu));
3815
break;
3816
case 0x4B:
3817
activate_codes =
3818
((Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) & Truncate16(cht_register[cht_reg_no1] & 0xFFFFu)) !=
3819
Truncate16(cht_register[cht_reg_no2] & 0xFFFFu));
3820
break;
3821
case 0x50:
3822
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) == inst.value16);
3823
break;
3824
case 0x51:
3825
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) != inst.value16);
3826
break;
3827
case 0x52:
3828
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) > inst.value16);
3829
break;
3830
case 0x53:
3831
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) >= inst.value16);
3832
break;
3833
case 0x54:
3834
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) < inst.value16);
3835
break;
3836
case 0x55:
3837
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) <= inst.value16);
3838
break;
3839
case 0x56:
3840
activate_codes = ((Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) & inst.value16) == inst.value16);
3841
break;
3842
case 0x57:
3843
activate_codes = ((Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) & inst.value16) != inst.value16);
3844
break;
3845
case 0x58:
3846
activate_codes =
3847
((Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) > inst.value16) &&
3848
(Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) < Truncate16((inst.value32 & 0xFFFF0000u) >> 16)));
3849
break;
3850
case 0x59:
3851
activate_codes =
3852
((Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) >= inst.value16) &&
3853
(Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) <= Truncate16((inst.value32 & 0xFFFF0000u) >> 16)));
3854
break;
3855
case 0x5A:
3856
activate_codes = ((Truncate16(cht_register[cht_reg_no2] & 0xFFFFu) & inst.value16) ==
3857
Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3858
break;
3859
case 0x5B:
3860
activate_codes = ((Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) & inst.value16) !=
3861
Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3862
break;
3863
case 0x60:
3864
activate_codes =
3865
(DoMemoryRead<u16>(cht_register[cht_reg_no2]) == DoMemoryRead<u16>(cht_register[cht_reg_no1]));
3866
break;
3867
case 0x61:
3868
activate_codes =
3869
(DoMemoryRead<u16>(cht_register[cht_reg_no2]) != DoMemoryRead<u16>(cht_register[cht_reg_no1]));
3870
break;
3871
case 0x62:
3872
activate_codes =
3873
(DoMemoryRead<u16>(cht_register[cht_reg_no2]) > DoMemoryRead<u16>(cht_register[cht_reg_no1]));
3874
break;
3875
case 0x63:
3876
activate_codes =
3877
(DoMemoryRead<u16>(cht_register[cht_reg_no2]) >= DoMemoryRead<u16>(cht_register[cht_reg_no1]));
3878
break;
3879
case 0x64:
3880
activate_codes =
3881
(DoMemoryRead<u16>(cht_register[cht_reg_no2]) < DoMemoryRead<u16>(cht_register[cht_reg_no1]));
3882
break;
3883
case 0x65:
3884
activate_codes =
3885
(DoMemoryRead<u16>(cht_register[cht_reg_no2]) <= DoMemoryRead<u16>(cht_register[cht_reg_no1]));
3886
break;
3887
case 0x66:
3888
activate_codes = ((DoMemoryRead<u16>(cht_register[cht_reg_no1]) & inst.value16) == inst.value16);
3889
break;
3890
case 0x67:
3891
activate_codes = ((DoMemoryRead<u16>(cht_register[cht_reg_no1]) & inst.value16) != inst.value16);
3892
break;
3893
case 0x68:
3894
activate_codes =
3895
((DoMemoryRead<u16>(cht_register[cht_reg_no1]) > inst.value16) &&
3896
(DoMemoryRead<u16>(cht_register[cht_reg_no1]) < Truncate16((inst.value32 & 0xFFFF0000u) >> 16)));
3897
break;
3898
case 0x69:
3899
activate_codes =
3900
((DoMemoryRead<u16>(cht_register[cht_reg_no1]) >= inst.value16) &&
3901
(DoMemoryRead<u16>(cht_register[cht_reg_no1]) <= Truncate16((inst.value32 & 0xFFFF0000u) >> 16)));
3902
break;
3903
case 0x6A:
3904
activate_codes = ((DoMemoryRead<u16>(cht_register[cht_reg_no1]) & inst.value16) ==
3905
DoMemoryRead<u16>(cht_register[cht_reg_no1]));
3906
break;
3907
case 0x6B:
3908
activate_codes = ((DoMemoryRead<u16>(cht_register[cht_reg_no1]) & inst.value16) !=
3909
DoMemoryRead<u16>(cht_register[cht_reg_no1]));
3910
break;
3911
case 0x70:
3912
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) == DoMemoryRead<u16>(inst.value32));
3913
break;
3914
case 0x71:
3915
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) != DoMemoryRead<u16>(inst.value32));
3916
break;
3917
case 0x72:
3918
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) > DoMemoryRead<u16>(inst.value32));
3919
break;
3920
case 0x73:
3921
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) >= DoMemoryRead<u16>(inst.value32));
3922
break;
3923
case 0x74:
3924
activate_codes = (Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) < DoMemoryRead<u16>(inst.value32));
3925
break;
3926
case 0x76:
3927
activate_codes = ((Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) & DoMemoryRead<u16>(inst.value32)) ==
3928
DoMemoryRead<u16>(inst.value32));
3929
break;
3930
case 0x77:
3931
activate_codes = ((Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) & DoMemoryRead<u16>(inst.value32)) !=
3932
DoMemoryRead<u16>(inst.value32));
3933
break;
3934
case 0x7A:
3935
activate_codes = ((Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) & DoMemoryRead<u16>(inst.value32)) ==
3936
Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3937
break;
3938
case 0x7B:
3939
activate_codes = ((Truncate16(cht_register[cht_reg_no1] & 0xFFFFu) & DoMemoryRead<u16>(inst.value32)) !=
3940
Truncate16(cht_register[cht_reg_no1] & 0xFFFFu));
3941
break;
3942
case 0x80:
3943
activate_codes = (cht_register[cht_reg_no2] == cht_register[cht_reg_no1]);
3944
break;
3945
case 0x81:
3946
activate_codes = (cht_register[cht_reg_no2] != cht_register[cht_reg_no1]);
3947
break;
3948
case 0x82:
3949
activate_codes = (cht_register[cht_reg_no2] > cht_register[cht_reg_no1]);
3950
break;
3951
case 0x83:
3952
activate_codes = (cht_register[cht_reg_no2] >= cht_register[cht_reg_no1]);
3953
break;
3954
case 0x84:
3955
activate_codes = (cht_register[cht_reg_no2] < cht_register[cht_reg_no1]);
3956
break;
3957
case 0x85:
3958
activate_codes = (cht_register[cht_reg_no2] <= cht_register[cht_reg_no1]);
3959
break;
3960
case 0x86:
3961
activate_codes = ((cht_register[cht_reg_no2] & cht_register[cht_reg_no1]) == cht_register[cht_reg_no1]);
3962
break;
3963
case 0x87:
3964
activate_codes = ((cht_register[cht_reg_no2] & cht_register[cht_reg_no1]) != cht_register[cht_reg_no1]);
3965
break;
3966
case 0x8A:
3967
activate_codes = ((cht_register[cht_reg_no2] & cht_register[cht_reg_no1]) == cht_register[cht_reg_no2]);
3968
break;
3969
case 0x8B:
3970
activate_codes = ((cht_register[cht_reg_no2] & cht_register[cht_reg_no1]) != cht_register[cht_reg_no2]);
3971
break;
3972
case 0x90:
3973
activate_codes = (cht_register[cht_reg_no1] == inst.value32);
3974
break;
3975
case 0x91:
3976
activate_codes = (cht_register[cht_reg_no1] != inst.value32);
3977
break;
3978
case 0x92:
3979
activate_codes = (cht_register[cht_reg_no1] > inst.value32);
3980
break;
3981
case 0x93:
3982
activate_codes = (cht_register[cht_reg_no1] >= inst.value32);
3983
break;
3984
case 0x94:
3985
activate_codes = (cht_register[cht_reg_no1] < inst.value32);
3986
break;
3987
case 0x95:
3988
activate_codes = (cht_register[cht_reg_no1] <= inst.value32);
3989
break;
3990
case 0x96:
3991
activate_codes = ((cht_register[cht_reg_no1] & inst.value32) == inst.value32);
3992
break;
3993
case 0x97:
3994
activate_codes = ((cht_register[cht_reg_no1] & inst.value32) != inst.value32);
3995
break;
3996
case 0x9A:
3997
activate_codes = ((cht_register[cht_reg_no2] & inst.value32) == cht_register[cht_reg_no1]);
3998
break;
3999
case 0x9B:
4000
activate_codes = ((cht_register[cht_reg_no1] & inst.value32) != cht_register[cht_reg_no1]);
4001
break;
4002
case 0xA0:
4003
activate_codes =
4004
(DoMemoryRead<u32>(cht_register[cht_reg_no2]) == DoMemoryRead<u32>(cht_register[cht_reg_no1]));
4005
break;
4006
case 0xA1:
4007
activate_codes =
4008
(DoMemoryRead<u32>(cht_register[cht_reg_no2]) != DoMemoryRead<u32>(cht_register[cht_reg_no1]));
4009
break;
4010
case 0xA2:
4011
activate_codes =
4012
(DoMemoryRead<u32>(cht_register[cht_reg_no2]) > DoMemoryRead<u32>(cht_register[cht_reg_no1]));
4013
break;
4014
case 0xA3:
4015
activate_codes =
4016
(DoMemoryRead<u32>(cht_register[cht_reg_no2]) >= DoMemoryRead<u32>(cht_register[cht_reg_no1]));
4017
break;
4018
case 0xA4:
4019
activate_codes =
4020
(DoMemoryRead<u32>(cht_register[cht_reg_no2]) < DoMemoryRead<u32>(cht_register[cht_reg_no1]));
4021
break;
4022
case 0xA5:
4023
activate_codes =
4024
(DoMemoryRead<u32>(cht_register[cht_reg_no2]) <= DoMemoryRead<u32>(cht_register[cht_reg_no1]));
4025
break;
4026
case 0xA6:
4027
activate_codes = ((DoMemoryRead<u32>(cht_register[cht_reg_no1]) & inst.value32) == inst.value32);
4028
break;
4029
case 0xA7:
4030
activate_codes = ((DoMemoryRead<u32>(cht_register[cht_reg_no1]) & inst.value32) != inst.value32);
4031
break;
4032
case 0xAA:
4033
activate_codes = ((DoMemoryRead<u32>(cht_register[cht_reg_no1]) & inst.value32) ==
4034
DoMemoryRead<u32>(cht_register[cht_reg_no1]));
4035
break;
4036
case 0xAB:
4037
activate_codes = ((DoMemoryRead<u32>(cht_register[cht_reg_no1]) & inst.value32) !=
4038
DoMemoryRead<u32>(cht_register[cht_reg_no1]));
4039
break;
4040
case 0xB0:
4041
activate_codes = (cht_register[cht_reg_no1] == DoMemoryRead<u32>(inst.value32));
4042
break;
4043
case 0xB1:
4044
activate_codes = (cht_register[cht_reg_no1] != DoMemoryRead<u32>(inst.value32));
4045
break;
4046
case 0xB2:
4047
activate_codes = (cht_register[cht_reg_no1] > DoMemoryRead<u32>(inst.value32));
4048
break;
4049
case 0xB3:
4050
activate_codes = (cht_register[cht_reg_no1] >= DoMemoryRead<u32>(inst.value32));
4051
break;
4052
case 0xB4:
4053
activate_codes = (cht_register[cht_reg_no1] < DoMemoryRead<u32>(inst.value32));
4054
break;
4055
case 0xB6:
4056
activate_codes =
4057
((cht_register[cht_reg_no1] & DoMemoryRead<u32>(inst.value32)) == DoMemoryRead<u32>(inst.value32));
4058
break;
4059
case 0xB7:
4060
activate_codes =
4061
((cht_register[cht_reg_no1] & DoMemoryRead<u32>(inst.value32)) != DoMemoryRead<u32>(inst.value32));
4062
break;
4063
case 0xBA:
4064
activate_codes =
4065
((cht_register[cht_reg_no1] & DoMemoryRead<u32>(inst.value32)) == cht_register[cht_reg_no1]);
4066
break;
4067
case 0xBB:
4068
activate_codes =
4069
((cht_register[cht_reg_no1] & DoMemoryRead<u32>(inst.value32)) != cht_register[cht_reg_no1]);
4070
break;
4071
default:
4072
activate_codes = false;
4073
break;
4074
}
4075
if (activate_codes)
4076
{
4077
// execute following instructions
4078
continue;
4079
}
4080
4081
// skip to the next separator (00000000 FFFF), or end
4082
constexpr u64 separator_value = UINT64_C(0x000000000000FFFF);
4083
while (index < count)
4084
{
4085
// we don't want to execute the separator instruction
4086
const u64 bits = instructions[index++].bits;
4087
if (bits == separator_value)
4088
break;
4089
}
4090
}
4091
break;
4092
4093
case InstructionCode::DelayActivation: // C1
4094
{
4095
// A value of around 4000 or 5000 will usually give you a good 20-30 second delay before codes are activated.
4096
// Frame number * 0.3 -> (20 * 60) * 10 / 3 => 4000
4097
const u32 comp_value = (System::GetFrameNumber() * 10) / 3;
4098
if (comp_value < inst.value16)
4099
index = count;
4100
else
4101
index++;
4102
}
4103
break;
4104
4105
case InstructionCode::Slide:
4106
{
4107
if ((index + 1) >= instructions.size())
4108
{
4109
ERROR_LOG("Incomplete slide instruction");
4110
return;
4111
}
4112
4113
const u32 slide_count = (inst.first >> 8) & 0xFFu;
4114
const u32 address_increment = inst.first & 0xFFu;
4115
const u16 value_increment = Truncate16(inst.second);
4116
const Instruction& inst2 = instructions[index + 1];
4117
const InstructionCode write_type = inst2.code;
4118
u32 address = inst2.address;
4119
u16 value = inst2.value16;
4120
4121
if (write_type == InstructionCode::ConstantWrite8)
4122
{
4123
for (u32 i = 0; i < slide_count; i++)
4124
{
4125
DoMemoryWrite<u8>(address, Truncate8(value));
4126
address += address_increment;
4127
value += value_increment;
4128
}
4129
}
4130
else if (write_type == InstructionCode::ConstantWrite16)
4131
{
4132
for (u32 i = 0; i < slide_count; i++)
4133
{
4134
DoMemoryWrite<u16>(address, value);
4135
address += address_increment;
4136
value += value_increment;
4137
}
4138
}
4139
else if (write_type == InstructionCode::ExtConstantWrite32)
4140
{
4141
for (u32 i = 0; i < slide_count; i++)
4142
{
4143
DoMemoryWrite<u32>(address, value);
4144
address += address_increment;
4145
value += value_increment;
4146
}
4147
}
4148
else
4149
{
4150
ERROR_LOG("Invalid command in second slide parameter 0x{:02X}", static_cast<unsigned>(write_type));
4151
}
4152
4153
index += 2;
4154
}
4155
break;
4156
4157
case InstructionCode::ExtImprovedSlide:
4158
{
4159
if ((index + 1) >= instructions.size())
4160
{
4161
ERROR_LOG("Incomplete slide instruction");
4162
return;
4163
}
4164
4165
const u32 slide_count = inst.first & 0xFFFFu;
4166
const u32 address_change = (inst.second >> 16) & 0xFFFFu;
4167
const u16 value_change = Truncate16(inst.second);
4168
const Instruction& inst2 = instructions[index + 1];
4169
const InstructionCode write_type = inst2.code;
4170
const bool address_change_negative = (inst.first >> 20) & 0x1u;
4171
const bool value_change_negative = (inst.first >> 16) & 0x1u;
4172
u32 address = inst2.address;
4173
u32 value = inst2.value32;
4174
4175
if (write_type == InstructionCode::ConstantWrite8)
4176
{
4177
for (u32 i = 0; i < slide_count; i++)
4178
{
4179
DoMemoryWrite<u8>(address, Truncate8(value));
4180
if (address_change_negative)
4181
address -= address_change;
4182
else
4183
address += address_change;
4184
if (value_change_negative)
4185
value -= value_change;
4186
else
4187
value += value_change;
4188
}
4189
}
4190
else if (write_type == InstructionCode::ConstantWrite16)
4191
{
4192
for (u32 i = 0; i < slide_count; i++)
4193
{
4194
DoMemoryWrite<u16>(address, Truncate16(value));
4195
if (address_change_negative)
4196
address -= address_change;
4197
else
4198
address += address_change;
4199
if (value_change_negative)
4200
value -= value_change;
4201
else
4202
value += value_change;
4203
}
4204
}
4205
else if (write_type == InstructionCode::ExtConstantWrite32)
4206
{
4207
for (u32 i = 0; i < slide_count; i++)
4208
{
4209
DoMemoryWrite<u32>(address, value);
4210
if (address_change_negative)
4211
address -= address_change;
4212
else
4213
address += address_change;
4214
if (value_change_negative)
4215
value -= value_change;
4216
else
4217
value += value_change;
4218
}
4219
}
4220
else if (write_type == InstructionCode::ExtConstantBitClear8)
4221
{
4222
for (u32 i = 0; i < slide_count; i++)
4223
{
4224
const u8 new_value = DoMemoryRead<u8>(address) & ~Truncate8(value);
4225
DoMemoryWrite<u8>(address, new_value);
4226
if (address_change_negative)
4227
address -= address_change;
4228
else
4229
address += address_change;
4230
if (value_change_negative)
4231
value -= value_change;
4232
else
4233
value += value_change;
4234
}
4235
}
4236
else if (write_type == InstructionCode::ExtConstantBitSet8)
4237
{
4238
for (u32 i = 0; i < slide_count; i++)
4239
{
4240
const u8 new_value = DoMemoryRead<u8>(address) | Truncate8(value);
4241
DoMemoryWrite<u8>(address, new_value);
4242
if (address_change_negative)
4243
address -= address_change;
4244
else
4245
address += address_change;
4246
if (value_change_negative)
4247
value -= value_change;
4248
else
4249
value += value_change;
4250
}
4251
}
4252
else if (write_type == InstructionCode::ExtConstantBitClear16)
4253
{
4254
for (u32 i = 0; i < slide_count; i++)
4255
{
4256
const u16 new_value = DoMemoryRead<u16>(address) & ~Truncate16(value);
4257
DoMemoryWrite<u16>(address, new_value);
4258
if (address_change_negative)
4259
address -= address_change;
4260
else
4261
address += address_change;
4262
if (value_change_negative)
4263
value -= value_change;
4264
else
4265
value += value_change;
4266
}
4267
}
4268
else if (write_type == InstructionCode::ExtConstantBitSet16)
4269
{
4270
for (u32 i = 0; i < slide_count; i++)
4271
{
4272
const u16 new_value = DoMemoryRead<u16>(address) | Truncate16(value);
4273
DoMemoryWrite<u16>(address, new_value);
4274
if (address_change_negative)
4275
address -= address_change;
4276
else
4277
address += address_change;
4278
if (value_change_negative)
4279
value -= value_change;
4280
else
4281
value += value_change;
4282
}
4283
}
4284
else if (write_type == InstructionCode::ExtConstantBitClear32)
4285
{
4286
for (u32 i = 0; i < slide_count; i++)
4287
{
4288
const u32 newValue = DoMemoryRead<u32>(address) & ~value;
4289
DoMemoryWrite<u32>(address, newValue);
4290
if (address_change_negative)
4291
address -= address_change;
4292
else
4293
address += address_change;
4294
if (value_change_negative)
4295
value -= value_change;
4296
else
4297
value += value_change;
4298
}
4299
}
4300
else if (write_type == InstructionCode::ExtConstantBitSet32)
4301
{
4302
for (u32 i = 0; i < slide_count; i++)
4303
{
4304
const u32 newValue = DoMemoryRead<u32>(address) | value;
4305
DoMemoryWrite<u32>(address, newValue);
4306
if (address_change_negative)
4307
address -= address_change;
4308
else
4309
address += address_change;
4310
if (value_change_negative)
4311
value -= value_change;
4312
else
4313
value += value_change;
4314
}
4315
}
4316
else
4317
{
4318
ERROR_LOG("Invalid command in second slide parameter 0x{:02X}", static_cast<unsigned>(write_type));
4319
}
4320
4321
index += 2;
4322
}
4323
break;
4324
4325
case InstructionCode::MemoryCopy:
4326
{
4327
if ((index + 1) >= instructions.size())
4328
{
4329
ERROR_LOG("Incomplete memory copy instruction");
4330
return;
4331
}
4332
4333
const Instruction& inst2 = instructions[index + 1];
4334
const u32 byte_count = inst.value16;
4335
u32 src_address = inst.address;
4336
u32 dst_address = inst2.address;
4337
4338
for (u32 i = 0; i < byte_count; i++)
4339
{
4340
u8 value = DoMemoryRead<u8>(src_address);
4341
DoMemoryWrite<u8>(dst_address, value);
4342
src_address++;
4343
dst_address++;
4344
}
4345
4346
index += 2;
4347
}
4348
break;
4349
4350
default:
4351
{
4352
ERROR_LOG("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast<u8>(inst.code.GetValue()),
4353
inst.first, inst.second);
4354
index++;
4355
}
4356
break;
4357
}
4358
}
4359
}
4360
4361
void Cheats::GamesharkCheatCode::ApplyOnDisable() const
4362
{
4363
const u32 count = static_cast<u32>(instructions.size());
4364
u32 index = 0;
4365
for (; index < count;)
4366
{
4367
const Instruction& inst = instructions[index];
4368
switch (inst.code)
4369
{
4370
case InstructionCode::Nop:
4371
case InstructionCode::ConstantWrite8:
4372
case InstructionCode::ConstantWrite16:
4373
case InstructionCode::ExtConstantWrite32:
4374
case InstructionCode::ExtConstantBitSet8:
4375
case InstructionCode::ExtConstantBitSet16:
4376
case InstructionCode::ExtConstantBitSet32:
4377
case InstructionCode::ExtConstantBitClear8:
4378
case InstructionCode::ExtConstantBitClear16:
4379
case InstructionCode::ExtConstantBitClear32:
4380
case InstructionCode::ScratchpadWrite16:
4381
case InstructionCode::ExtScratchpadWrite32:
4382
case InstructionCode::ExtIncrement32:
4383
case InstructionCode::ExtDecrement32:
4384
case InstructionCode::Increment16:
4385
case InstructionCode::Decrement16:
4386
case InstructionCode::Increment8:
4387
case InstructionCode::Decrement8:
4388
case InstructionCode::ExtConstantForceRange8:
4389
case InstructionCode::ExtConstantForceRangeLimits16:
4390
case InstructionCode::ExtConstantForceRangeRollRound16:
4391
case InstructionCode::ExtConstantSwap16:
4392
case InstructionCode::DelayActivation: // C1
4393
case InstructionCode::ExtConstantWriteIfMatch16:
4394
case InstructionCode::ExtCheatRegisters:
4395
index++;
4396
break;
4397
4398
case InstructionCode::ExtConstantForceRange16:
4399
case InstructionCode::Slide:
4400
case InstructionCode::ExtImprovedSlide:
4401
case InstructionCode::MemoryCopy:
4402
index += 2;
4403
break;
4404
case InstructionCode::ExtFindAndReplace:
4405
index += 5;
4406
break;
4407
// for conditionals, we don't want to skip over in case it changed at some point
4408
case InstructionCode::ExtCompareEqual32:
4409
case InstructionCode::ExtCompareNotEqual32:
4410
case InstructionCode::ExtCompareLess32:
4411
case InstructionCode::ExtCompareGreater32:
4412
case InstructionCode::CompareEqual16:
4413
case InstructionCode::CompareNotEqual16:
4414
case InstructionCode::CompareLess16:
4415
case InstructionCode::CompareGreater16:
4416
case InstructionCode::CompareEqual8:
4417
case InstructionCode::CompareNotEqual8:
4418
case InstructionCode::CompareLess8:
4419
case InstructionCode::CompareGreater8:
4420
case InstructionCode::CompareButtons: // D4
4421
index++;
4422
break;
4423
4424
// same deal for block conditionals
4425
case InstructionCode::SkipIfNotEqual16: // C0
4426
case InstructionCode::ExtSkipIfNotEqual32: // A4
4427
case InstructionCode::SkipIfButtonsNotEqual: // D5
4428
case InstructionCode::SkipIfButtonsEqual: // D6
4429
case InstructionCode::ExtBitCompareButtons: // D7
4430
case InstructionCode::ExtSkipIfNotLess8: // C3
4431
case InstructionCode::ExtSkipIfNotGreater8: // C4
4432
case InstructionCode::ExtSkipIfNotLess16: // C5
4433
case InstructionCode::ExtSkipIfNotGreater16: // C6
4434
case InstructionCode::ExtMultiConditionals: // F6
4435
case InstructionCode::ExtCheatRegistersCompare: // 52
4436
index++;
4437
break;
4438
4439
case InstructionCode::ExtConstantWriteIfMatchWithRestore16:
4440
{
4441
const u16 value = DoMemoryRead<u16>(inst.address);
4442
const u16 comparevalue = Truncate16(inst.value32 >> 16);
4443
const u16 newvalue = Truncate16(inst.value32 & 0xFFFFu);
4444
if (value == newvalue)
4445
DoMemoryWrite<u16>(inst.address, comparevalue);
4446
4447
index++;
4448
}
4449
break;
4450
4451
case InstructionCode::ExtConstantWriteIfMatchWithRestore8:
4452
{
4453
const u8 value = DoMemoryRead<u8>(inst.address);
4454
const u8 comparevalue = Truncate8(inst.value16 >> 8);
4455
const u8 newvalue = Truncate8(inst.value16 & 0xFFu);
4456
if (value == newvalue)
4457
DoMemoryWrite<u8>(inst.address, comparevalue);
4458
4459
index++;
4460
}
4461
break;
4462
4463
[[unlikely]] default:
4464
{
4465
ERROR_LOG("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast<u8>(inst.code.GetValue()),
4466
inst.first, inst.second);
4467
index++;
4468
}
4469
break;
4470
}
4471
}
4472
}
4473
4474
void Cheats::GamesharkCheatCode::SetOptionValue(u32 value)
4475
{
4476
for (const auto& [index, bitpos_start, bit_count] : option_instruction_values)
4477
{
4478
Instruction& inst = instructions[index];
4479
const u32 value_mask = ((1u << bit_count) - 1);
4480
const u32 fixed_mask = ~(value_mask << bitpos_start);
4481
inst.second = (inst.second & fixed_mask) | ((value & value_mask) << bitpos_start);
4482
}
4483
}
4484
4485
std::unique_ptr<Cheats::CheatCode> Cheats::ParseCode(CheatCode::Metadata metadata, const std::string_view data,
4486
Error* error)
4487
{
4488
std::unique_ptr<Cheats::CheatCode> ret;
4489
4490
switch (metadata.type)
4491
{
4492
case CodeType::Gameshark:
4493
ret = GamesharkCheatCode::Parse(std::move(metadata), data, error);
4494
break;
4495
4496
default:
4497
Error::SetStringView(error, "Unknown code type");
4498
break;
4499
}
4500
4501
return ret;
4502
}
4503
4504