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