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