Cheats: Rewrite cheat management system

This commit is contained in:
Stenzek 2024-10-07 01:44:13 +10:00
parent d70ed4225d
commit 179044c246
No known key found for this signature in database
44 changed files with 3442 additions and 2836 deletions

View File

@ -85,6 +85,8 @@ add_library(core
memory_card.h
memory_card_image.cpp
memory_card_image.h
memory_scanner.cpp
memory_scanner.h
mips_encoder.h
multitap.cpp
multitap.h

File diff suppressed because it is too large Load Diff

View File

@ -7,316 +7,129 @@
#include "types.h"
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
struct CheatCode
class Error;
namespace Cheats {
enum class CodeType : u8
{
enum class Type : u8
{
Gameshark,
Count
};
Gameshark,
Count
};
enum class Activation : u8
{
Manual,
EndFrame,
Count,
};
enum class CodeActivation : u8
{
Manual,
EndFrame,
Count,
};
enum class InstructionCode : u8
{
Nop = 0x00,
ConstantWrite8 = 0x30,
ConstantWrite16 = 0x80,
ScratchpadWrite16 = 0x1F,
Increment16 = 0x10,
Decrement16 = 0x11,
Increment8 = 0x20,
Decrement8 = 0x21,
DelayActivation = 0xC1,
SkipIfNotEqual16 = 0xC0,
SkipIfButtonsNotEqual = 0xD5,
SkipIfButtonsEqual = 0xD6,
CompareButtons = 0xD4,
CompareEqual16 = 0xD0,
CompareNotEqual16 = 0xD1,
CompareLess16 = 0xD2,
CompareGreater16 = 0xD3,
CompareEqual8 = 0xE0,
CompareNotEqual8 = 0xE1,
CompareLess8 = 0xE2,
CompareGreater8 = 0xE3,
Slide = 0x50,
MemoryCopy = 0xC2,
ExtImprovedSlide = 0x53,
enum class FileFormat : u8
{
Unknown,
PCSX,
Libretro,
EPSXe,
Count
};
// Extension opcodes, not present on original GameShark.
ExtConstantWrite32 = 0x90,
ExtScratchpadWrite32 = 0xA5,
ExtCompareEqual32 = 0xA0,
ExtCompareNotEqual32 = 0xA1,
ExtCompareLess32 = 0xA2,
ExtCompareGreater32 = 0xA3,
ExtSkipIfNotEqual32 = 0xA4,
ExtIncrement32 = 0x60,
ExtDecrement32 = 0x61,
ExtConstantWriteIfMatch16 = 0xA6,
ExtConstantWriteIfMatchWithRestore16 = 0xA7,
ExtConstantForceRange8 = 0xF0,
ExtConstantForceRangeLimits16 = 0xF1,
ExtConstantForceRangeRollRound16 = 0xF2,
ExtConstantForceRange16 = 0xF3,
ExtFindAndReplace = 0xF4,
ExtConstantSwap16 = 0xF5,
ExtConstantBitSet8 = 0x31,
ExtConstantBitClear8 = 0x32,
ExtConstantBitSet16 = 0x81,
ExtConstantBitClear16 = 0x82,
ExtConstantBitSet32 = 0x91,
ExtConstantBitClear32 = 0x92,
ExtBitCompareButtons = 0xD7,
ExtSkipIfNotLess8 = 0xC3,
ExtSkipIfNotGreater8 = 0xC4,
ExtSkipIfNotLess16 = 0xC5,
ExtSkipIfNotGreater16 = 0xC6,
ExtMultiConditionals = 0xF6,
ExtCheatRegisters = 0x51,
ExtCheatRegistersCompare = 0x52,
ExtCompareBitsSet8 = 0xE4, //Only used inside ExtMultiConditionals
ExtCompareBitsClear8 = 0xE5, //Only used inside ExtMultiConditionals
};
union Instruction
{
u64 bits;
struct
{
u32 second;
u32 first;
};
BitField<u64, InstructionCode, 32 + 24, 8> code;
BitField<u64, u32, 32, 24> address;
BitField<u64, u32, 0, 32> value32;
BitField<u64, u16, 0, 16> value16;
BitField<u64, u8, 0, 8> value8;
};
std::string group;
/// Contains all the information required to present a cheat code to the user.
struct CodeInfo
{
std::string name;
std::string author;
std::string description;
std::vector<Instruction> instructions;
std::string comments;
Type type = Type::Gameshark;
Activation activation = Activation::EndFrame;
bool enabled = false;
std::string body;
u32 file_offset_start;
u32 file_offset_body_start;
u32 file_offset_end;
CodeType type;
CodeActivation activation;
bool from_database;
ALWAYS_INLINE bool Valid() const { return !instructions.empty() && !description.empty(); }
ALWAYS_INLINE bool IsManuallyActivated() const { return (activation == Activation::Manual); }
std::string GetInstructionsAsString() const;
bool SetInstructionsFromString(const std::string& str);
u32 GetNextNonConditionalInstruction(u32 index) const;
void Apply() const;
void ApplyOnDisable() const;
static const char* GetTypeName(Type type);
static const char* GetTypeDisplayName(Type type);
static std::optional<Type> ParseTypeName(const char* str);
static const char* GetActivationName(Activation activation);
static const char* GetActivationDisplayName(Activation activation);
static std::optional<Activation> ParseActivationName(const char* str);
std::string_view GetNamePart() const;
std::string_view GetNameParentPart() const;
};
class CheatList final
{
public:
enum class Format
{
Autodetect,
PCSXR,
Libretro,
EPSXe,
Count
};
using CodeInfoList = std::vector<CodeInfo>;
CheatList();
~CheatList();
/// Returns the internal identifier for a code type.
extern const char* GetTypeName(CodeType type);
ALWAYS_INLINE const CheatCode& GetCode(u32 i) const { return m_codes[i]; }
ALWAYS_INLINE CheatCode& GetCode(u32 i) { return m_codes[i]; }
ALWAYS_INLINE u32 GetCodeCount() const { return static_cast<u32>(m_codes.size()); }
ALWAYS_INLINE bool IsCodeEnabled(u32 index) const { return m_codes[index].enabled; }
/// Returns the human-readable name for a code type.
extern const char* GetTypeDisplayName(CodeType type);
ALWAYS_INLINE bool GetMasterEnable() const { return m_master_enable; }
ALWAYS_INLINE void SetMasterEnable(bool enable) { m_master_enable = enable; }
/// Parses an internal identifier, returning the code type.
extern std::optional<CodeType> ParseTypeName(const std::string_view str);
const CheatCode* FindCode(const char* name) const;
const CheatCode* FindCode(const char* group, const char* name) const;
/// Returns the internal identifier for a code activation.
extern const char* GetActivationName(CodeActivation activation);
void AddCode(CheatCode cc);
void SetCode(u32 index, CheatCode cc);
void RemoveCode(u32 i);
/// Returns the human-readable name for a code activation.
extern const char* GetActivationDisplayName(CodeActivation activation);
u32 GetEnabledCodeCount() const;
std::vector<std::string> GetCodeGroups() const;
void EnableCode(u32 index);
void DisableCode(u32 index);
void SetCodeEnabled(u32 index, bool state);
/// Parses an internal identifier, returning the activation type.
extern std::optional<CodeActivation> ParseActivationName(const std::string_view str);
static std::optional<Format> DetectFileFormat(const char* filename);
static Format DetectFileFormat(const std::string& str);
static bool ParseLibretroCheat(CheatCode* cc, const char* line);
/// Returns a list of all available cheats/patches for a given game.
extern CodeInfoList GetCodeInfoList(const std::string_view serial, std::optional<GameHash> hash, bool cheats,
bool load_from_database);
bool LoadFromFile(const char* filename, Format format);
bool LoadFromPCSXRFile(const char* filename);
bool LoadFromLibretroFile(const char* filename);
/// Searches for a given code by name.
extern const CodeInfo* FindCodeInInfoList(const CodeInfoList& list, const std::string_view name);
bool LoadFromString(const std::string& str, Format format);
bool LoadFromPCSXRString(const std::string& str);
bool LoadFromLibretroString(const std::string& str);
bool LoadFromEPSXeString(const std::string& str);
/// Searches for a given code by name.
extern CodeInfo* FindCodeInInfoList(CodeInfoList& list, const std::string_view name);
bool SaveToPCSXRFile(const char* filename);
/// Imports all codes from the provided string.
extern bool ImportCodesFromString(CodeInfoList* dst, const std::string_view file_contents, FileFormat file_format,
bool stop_on_error, Error* error);
bool LoadFromPackage(const std::string& serial);
/// Exports codes to the given file, in DuckStation format.
extern bool ExportCodesToFile(std::string path, const CodeInfoList& codes, Error* error);
void Apply();
/// Removes the specified code from the file, rewriting it.
extern bool RemoveCodeFromFile(const char* path, const std::string_view name, Error* error);
void ApplyCode(u32 index);
/// Adds or updates the specified code from the file, rewriting it.
extern bool SaveCodeToFile(const char* path, const CodeInfo& code, Error* error);
void MergeList(const CheatList& cl);
/// Updates or adds multiple codes to the file, rewriting it.
extern bool SaveCodesToFile(const char* path, const CodeInfoList& codes, Error* error);
private:
std::vector<CheatCode> m_codes;
bool m_master_enable = true;
};
/// Merges two cheat lists, with any duplicates in the new list taking precedence.
extern void MergeCheatList(CodeInfoList* dst, CodeInfoList src);
class MemoryScan
{
public:
enum class Operator
{
Any,
LessThanLast,
LessEqualLast,
GreaterThanLast,
GreaterEqualLast,
NotEqualLast,
EqualLast,
DecreasedBy,
IncreasedBy,
ChangedBy,
Equal,
NotEqual,
LessThan,
LessEqual,
GreaterThan,
GreaterEqual
};
/// Returns the path to a new cheat/patch cht for the specified serial and hash.
extern std::string GetChtFilename(const std::string_view serial, std::optional<GameHash> hash, bool cheats);
struct Result
{
PhysicalMemoryAddress address;
u32 value;
u32 last_value;
bool value_changed;
/// Reloads cheats and game patches. The parameters control the degree to which data is reloaded.
extern void ReloadCheats(bool reload_files, bool reload_enabled_list, bool verbose, bool verbose_if_changed);
bool Filter(Operator op, u32 comp_value, bool is_signed) const;
void UpdateValue(MemoryAccessSize size, bool is_signed);
};
/// Releases all cheat-related state.
extern void UnloadAll();
using ResultVector = std::vector<Result>;
/// Applies all currently-registered frame end cheat codes.
extern void ApplyFrameEndCodes();
MemoryScan();
~MemoryScan();
/// Returns true if cheats are enabled in the current game's configuration.
extern bool AreCheatsEnabled();
u32 GetValue() const { return m_value; }
bool GetValueSigned() const { return m_signed; }
MemoryAccessSize GetSize() const { return m_size; }
Operator GetOperator() const { return m_operator; }
PhysicalMemoryAddress GetStartAddress() const { return m_start_address; }
PhysicalMemoryAddress GetEndAddress() const { return m_end_address; }
const ResultVector& GetResults() const { return m_results; }
const Result& GetResult(u32 index) const { return m_results[index]; }
u32 GetResultCount() const { return static_cast<u32>(m_results.size()); }
/// Enumerates the names of all manually-activated codes.
extern bool EnumerateManualCodes(std::function<bool(const std::string& name)> callback);
void SetValue(u32 value) { m_value = value; }
void SetValueSigned(bool s) { m_signed = s; }
void SetSize(MemoryAccessSize size) { m_size = size; }
void SetOperator(Operator op) { m_operator = op; }
void SetStartAddress(PhysicalMemoryAddress addr) { m_start_address = addr; }
void SetEndAddress(PhysicalMemoryAddress addr) { m_end_address = addr; }
/// Invokes/applies the specified manually-activated code.
extern bool ApplyManualCode(const std::string_view name);
void ResetSearch();
void Search();
void SearchAgain();
void UpdateResultsValues();
// Config sections/keys to use to enable patches.
extern const char* PATCHES_CONFIG_SECTION;
extern const char* CHEATS_CONFIG_SECTION;
extern const char* PATCH_ENABLE_CONFIG_KEY;
void SetResultValue(u32 index, u32 value);
private:
void SearchBytes();
void SearchHalfwords();
void SearchWords();
u32 m_value = 0;
MemoryAccessSize m_size = MemoryAccessSize::HalfWord;
Operator m_operator = Operator::Equal;
PhysicalMemoryAddress m_start_address = 0;
PhysicalMemoryAddress m_end_address = 0x200000;
ResultVector m_results;
bool m_signed = false;
};
class MemoryWatchList
{
public:
MemoryWatchList();
~MemoryWatchList();
struct Entry
{
std::string description;
u32 address;
u32 value;
MemoryAccessSize size;
bool is_signed;
bool freeze;
bool changed;
};
using EntryVector = std::vector<Entry>;
const Entry* GetEntryByAddress(u32 address) const;
const EntryVector& GetEntries() const { return m_entries; }
const Entry& GetEntry(u32 index) const { return m_entries[index]; }
u32 GetEntryCount() const { return static_cast<u32>(m_entries.size()); }
bool AddEntry(std::string description, u32 address, MemoryAccessSize size, bool is_signed, bool freeze);
void RemoveEntry(u32 index);
bool RemoveEntryByDescription(const char* description);
bool RemoveEntryByAddress(u32 address);
void SetEntryDescription(u32 index, std::string description);
void SetEntryFreeze(u32 index, bool freeze);
void SetEntryValue(u32 index, u32 value);
void UpdateValues();
private:
static void SetEntryValue(Entry* entry, u32 value);
static void UpdateEntryValue(Entry* entry);
EntryVector m_entries;
};
} // namespace Cheats

View File

@ -65,6 +65,7 @@
<ClCompile Include="mdec.cpp" />
<ClCompile Include="memory_card.cpp" />
<ClCompile Include="memory_card_image.cpp" />
<ClCompile Include="memory_scanner.cpp" />
<ClCompile Include="multitap.cpp" />
<ClCompile Include="guncon.cpp" />
<ClCompile Include="negcon.cpp" />
@ -145,6 +146,7 @@
<ClInclude Include="mdec.h" />
<ClInclude Include="memory_card.h" />
<ClInclude Include="memory_card_image.h" />
<ClInclude Include="memory_scanner.h" />
<ClInclude Include="multitap.h" />
<ClInclude Include="guncon.h" />
<ClInclude Include="negcon.h" />

View File

@ -67,6 +67,7 @@
<ClCompile Include="gdb_server.cpp" />
<ClCompile Include="gpu_sw_rasterizer.cpp" />
<ClCompile Include="gpu_hw_texture_cache.cpp" />
<ClCompile Include="memory_scanner.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -140,6 +141,7 @@
<ClInclude Include="gdb_server.h" />
<ClInclude Include="gpu_sw_rasterizer.h" />
<ClInclude Include="gpu_hw_texture_cache.h" />
<ClInclude Include="memory_scanner.h" />
</ItemGroup>
<ItemGroup>
<None Include="gpu_sw_rasterizer.inl" />

View File

@ -1209,6 +1209,7 @@ void FullscreenUI::DoChangeDisc()
void FullscreenUI::DoCheatsMenu()
{
#if 0
CheatList* cl = System::GetCheatList();
if (!cl)
{
@ -1247,6 +1248,7 @@ void FullscreenUI::DoCheatsMenu()
System::SetCheatCodeState(static_cast<u32>(index), checked);
};
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_FROWN, "Cheat List"), true, std::move(options), std::move(callback));
#endif
}
void FullscreenUI::DoToggleAnalogMode()
@ -5349,12 +5351,14 @@ void FullscreenUI::DrawPauseMenu()
s_current_main_window = MainWindowType::None;
}
#if 0
if (ActiveButton(FSUI_ICONSTR(ICON_FA_FROWN_OPEN, "Cheat List"), false,
!System::GetGameSerial().empty() && g_settings.enable_cheats))
{
s_current_main_window = MainWindowType::None;
DoCheatsMenu();
}
#endif
if (ActiveButton(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Toggle Analog"), false))
{

View File

@ -214,7 +214,7 @@ std::string GameDatabase::GetSerialForPath(const char* path)
const GameDatabase::Entry* GameDatabase::GetEntryForDisc(CDImage* image)
{
std::string id;
System::GameHash hash;
GameHash hash;
System::GetGameDetailsFromImage(image, &id, &hash);
const Entry* entry = GetEntryForGameDetails(id, hash);
if (entry)

View File

@ -81,7 +81,7 @@ struct MemcardTimestampCacheEntry
using CacheMap = PreferUnorderedStringMap<Entry>;
using PlayedTimeMap = PreferUnorderedStringMap<PlayedTimeEntry>;
static_assert(std::is_same_v<decltype(Entry::hash), System::GameHash>);
static_assert(std::is_same_v<decltype(Entry::hash), GameHash>);
static bool GetExeListEntry(const std::string& path, Entry* entry);
static bool GetPsfListEntry(const std::string& path, Entry* entry);
@ -192,7 +192,7 @@ bool GameList::GetExeListEntry(const std::string& path, GameList::Entry* entry)
return false;
}
const System::GameHash hash = System::GetGameHashFromFile(path.c_str());
const GameHash hash = System::GetGameHashFromFile(path.c_str());
entry->serial = hash ? System::GetGameHashId(hash) : std::string();
entry->title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path));

View File

@ -285,20 +285,6 @@ DEFINE_HOTKEY("Rewind", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hot
System::SetRewindState(pressed > 0);
})
#ifndef __ANDROID__
DEFINE_HOTKEY("ToggleCheats", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Toggle Cheats"),
[](s32 pressed) {
if (!pressed)
System::DoToggleCheats();
})
#else
DEFINE_HOTKEY("TogglePatchCodes", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Toggle Patch Codes"),
[](s32 pressed) {
if (!pressed)
System::DoToggleCheats();
})
#endif
DEFINE_HOTKEY("ToggleOverclocking", TRANSLATE_NOOP("Hotkeys", "System"),
TRANSLATE_NOOP("Hotkeys", "Toggle Clock Speed Control (Overclocking)"), [](s32 pressed) {
if (!pressed && System::IsValid())

473
src/core/memory_scanner.cpp Normal file
View File

@ -0,0 +1,473 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "memory_scanner.h"
#include "bus.h"
#include "cpu_core.h"
#include "common/log.h"
#include "fmt/format.h"
LOG_CHANNEL(Cheats);
static bool IsValidScanAddress(PhysicalMemoryAddress address)
{
if ((address & CPU::SCRATCHPAD_ADDR_MASK) == CPU::SCRATCHPAD_ADDR &&
(address & CPU::SCRATCHPAD_OFFSET_MASK) < CPU::SCRATCHPAD_SIZE)
{
return true;
}
address &= CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
if (address < Bus::RAM_MIRROR_END)
return true;
if (address >= Bus::BIOS_BASE && address < (Bus::BIOS_BASE + Bus::BIOS_SIZE))
return true;
return false;
}
MemoryScan::MemoryScan() = default;
MemoryScan::~MemoryScan() = default;
void MemoryScan::ResetSearch()
{
m_results.clear();
}
void MemoryScan::Search()
{
m_results.clear();
switch (m_size)
{
case MemoryAccessSize::Byte:
SearchBytes();
break;
case MemoryAccessSize::HalfWord:
SearchHalfwords();
break;
case MemoryAccessSize::Word:
SearchWords();
break;
default:
break;
}
}
void MemoryScan::SearchBytes()
{
for (PhysicalMemoryAddress address = m_start_address; address < m_end_address; address++)
{
if (!IsValidScanAddress(address))
continue;
u8 bvalue = 0;
if (!CPU::SafeReadMemoryByte(address, &bvalue)) [[unlikely]]
continue;
Result res;
res.address = address;
res.value = m_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
res.last_value = res.value;
res.value_changed = false;
if (res.Filter(m_operator, m_value, m_signed))
m_results.push_back(res);
}
}
void MemoryScan::SearchHalfwords()
{
for (PhysicalMemoryAddress address = m_start_address; address < m_end_address; address += 2)
{
if (!IsValidScanAddress(address))
continue;
u16 bvalue = 0;
if (!CPU::SafeReadMemoryHalfWord(address, &bvalue)) [[unlikely]]
continue;
Result res;
res.address = address;
res.value = m_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
res.last_value = res.value;
res.value_changed = false;
if (res.Filter(m_operator, m_value, m_signed))
m_results.push_back(res);
}
}
void MemoryScan::SearchWords()
{
for (PhysicalMemoryAddress address = m_start_address; address < m_end_address; address += 4)
{
if (!IsValidScanAddress(address))
continue;
u32 bvalue = 0;
if (!CPU::SafeReadMemoryWord(address, &bvalue)) [[unlikely]]
continue;
Result res;
res.address = address;
res.value = bvalue;
res.last_value = res.value;
res.value_changed = false;
if (res.Filter(m_operator, m_value, m_signed))
m_results.push_back(res);
}
}
void MemoryScan::SearchAgain()
{
ResultVector new_results;
new_results.reserve(m_results.size());
for (Result& res : m_results)
{
res.UpdateValue(m_size, m_signed);
if (res.Filter(m_operator, m_value, m_signed))
{
res.last_value = res.value;
new_results.push_back(res);
}
}
m_results.swap(new_results);
}
void MemoryScan::UpdateResultsValues()
{
for (Result& res : m_results)
res.UpdateValue(m_size, m_signed);
}
void MemoryScan::SetResultValue(u32 index, u32 value)
{
if (index >= m_results.size())
return;
Result& res = m_results[index];
if (res.value == value)
return;
switch (m_size)
{
case MemoryAccessSize::Byte:
CPU::SafeWriteMemoryByte(res.address, Truncate8(value));
break;
case MemoryAccessSize::HalfWord:
CPU::SafeWriteMemoryHalfWord(res.address, Truncate16(value));
break;
case MemoryAccessSize::Word:
CPU::SafeWriteMemoryWord(res.address, value);
break;
}
res.value = value;
res.value_changed = true;
}
bool MemoryScan::Result::Filter(Operator op, u32 comp_value, bool is_signed) const
{
switch (op)
{
case Operator::Equal:
{
return (value == comp_value);
}
case Operator::NotEqual:
{
return (value != comp_value);
}
case Operator::GreaterThan:
{
return is_signed ? (static_cast<s32>(value) > static_cast<s32>(comp_value)) : (value > comp_value);
}
case Operator::GreaterEqual:
{
return is_signed ? (static_cast<s32>(value) >= static_cast<s32>(comp_value)) : (value >= comp_value);
}
case Operator::LessThan:
{
return is_signed ? (static_cast<s32>(value) < static_cast<s32>(comp_value)) : (value < comp_value);
}
case Operator::LessEqual:
{
return is_signed ? (static_cast<s32>(value) <= static_cast<s32>(comp_value)) : (value <= comp_value);
}
case Operator::IncreasedBy:
{
return is_signed ? ((static_cast<s32>(value) - static_cast<s32>(last_value)) == static_cast<s32>(comp_value)) :
((value - last_value) == comp_value);
}
case Operator::DecreasedBy:
{
return is_signed ? ((static_cast<s32>(last_value) - static_cast<s32>(value)) == static_cast<s32>(comp_value)) :
((last_value - value) == comp_value);
}
case Operator::ChangedBy:
{
if (is_signed)
return (std::abs(static_cast<s32>(last_value) - static_cast<s32>(value)) == static_cast<s32>(comp_value));
else
return ((last_value > value) ? (last_value - value) : (value - last_value)) == comp_value;
}
case Operator::EqualLast:
{
return (value == last_value);
}
case Operator::NotEqualLast:
{
return (value != last_value);
}
case Operator::GreaterThanLast:
{
return is_signed ? (static_cast<s32>(value) > static_cast<s32>(last_value)) : (value > last_value);
}
case Operator::GreaterEqualLast:
{
return is_signed ? (static_cast<s32>(value) >= static_cast<s32>(last_value)) : (value >= last_value);
}
case Operator::LessThanLast:
{
return is_signed ? (static_cast<s32>(value) < static_cast<s32>(last_value)) : (value < last_value);
}
case Operator::LessEqualLast:
{
return is_signed ? (static_cast<s32>(value) <= static_cast<s32>(last_value)) : (value <= last_value);
}
case Operator::Any:
return true;
default:
return false;
}
}
void MemoryScan::Result::UpdateValue(MemoryAccessSize size, bool is_signed)
{
const u32 old_value = value;
switch (size)
{
case MemoryAccessSize::Byte:
{
u8 bvalue = 0;
if (CPU::SafeReadMemoryByte(address, &bvalue)) [[likely]]
value = is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
}
break;
case MemoryAccessSize::HalfWord:
{
u16 bvalue = 0;
if (CPU::SafeReadMemoryHalfWord(address, &bvalue)) [[likely]]
value = is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
}
break;
case MemoryAccessSize::Word:
{
CPU::SafeReadMemoryWord(address, &value);
}
break;
}
value_changed = (value != old_value);
}
MemoryWatchList::MemoryWatchList() = default;
MemoryWatchList::~MemoryWatchList() = default;
const MemoryWatchList::Entry* MemoryWatchList::GetEntryByAddress(u32 address) const
{
for (const Entry& entry : m_entries)
{
if (entry.address == address)
return &entry;
}
return nullptr;
}
bool MemoryWatchList::AddEntry(std::string description, u32 address, MemoryAccessSize size, bool is_signed, bool freeze)
{
if (GetEntryByAddress(address))
return false;
Entry entry;
entry.description = std::move(description);
entry.address = address;
entry.size = size;
entry.is_signed = is_signed;
entry.freeze = false;
UpdateEntryValue(&entry);
entry.changed = false;
entry.freeze = freeze;
m_entries.push_back(std::move(entry));
return true;
}
void MemoryWatchList::RemoveEntry(u32 index)
{
if (index >= m_entries.size())
return;
m_entries.erase(m_entries.begin() + index);
}
bool MemoryWatchList::RemoveEntryByAddress(u32 address)
{
for (auto it = m_entries.begin(); it != m_entries.end(); ++it)
{
if (it->address == address)
{
m_entries.erase(it);
return true;
}
}
return false;
}
void MemoryWatchList::SetEntryDescription(u32 index, std::string description)
{
if (index >= m_entries.size())
return;
Entry& entry = m_entries[index];
entry.description = std::move(description);
}
void MemoryWatchList::SetEntryFreeze(u32 index, bool freeze)
{
if (index >= m_entries.size())
return;
Entry& entry = m_entries[index];
entry.freeze = freeze;
}
void MemoryWatchList::SetEntryValue(u32 index, u32 value)
{
if (index >= m_entries.size())
return;
Entry& entry = m_entries[index];
if (entry.value == value)
return;
SetEntryValue(&entry, value);
}
bool MemoryWatchList::RemoveEntryByDescription(const char* description)
{
bool result = false;
for (auto it = m_entries.begin(); it != m_entries.end();)
{
if (it->description == description)
{
it = m_entries.erase(it);
result = true;
continue;
}
++it;
}
return result;
}
void MemoryWatchList::UpdateValues()
{
for (Entry& entry : m_entries)
UpdateEntryValue(&entry);
}
void MemoryWatchList::SetEntryValue(Entry* entry, u32 value)
{
switch (entry->size)
{
case MemoryAccessSize::Byte:
CPU::SafeWriteMemoryByte(entry->address, Truncate8(value));
break;
case MemoryAccessSize::HalfWord:
CPU::SafeWriteMemoryHalfWord(entry->address, Truncate16(value));
break;
case MemoryAccessSize::Word:
CPU::SafeWriteMemoryWord(entry->address, value);
break;
}
entry->changed = (entry->value != value);
entry->value = value;
}
void MemoryWatchList::UpdateEntryValue(Entry* entry)
{
const u32 old_value = entry->value;
switch (entry->size)
{
case MemoryAccessSize::Byte:
{
u8 bvalue = 0;
if (CPU::SafeReadMemoryByte(entry->address, &bvalue)) [[likely]]
entry->value = entry->is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
}
break;
case MemoryAccessSize::HalfWord:
{
u16 bvalue = 0;
if (CPU::SafeReadMemoryHalfWord(entry->address, &bvalue)) [[likely]]
entry->value = entry->is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
}
break;
case MemoryAccessSize::Word:
{
CPU::SafeReadMemoryWord(entry->address, &entry->value);
}
break;
}
entry->changed = (old_value != entry->value);
if (entry->freeze && entry->changed)
SetEntryValue(entry, old_value);
}

130
src/core/memory_scanner.h Normal file
View File

@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "types.h"
#include <optional>
#include <string>
#include <string_view>
#include <vector>
class MemoryScan
{
public:
enum class Operator
{
Any,
LessThanLast,
LessEqualLast,
GreaterThanLast,
GreaterEqualLast,
NotEqualLast,
EqualLast,
DecreasedBy,
IncreasedBy,
ChangedBy,
Equal,
NotEqual,
LessThan,
LessEqual,
GreaterThan,
GreaterEqual
};
struct Result
{
PhysicalMemoryAddress address;
u32 value;
u32 last_value;
bool value_changed;
bool Filter(Operator op, u32 comp_value, bool is_signed) const;
void UpdateValue(MemoryAccessSize size, bool is_signed);
};
using ResultVector = std::vector<Result>;
MemoryScan();
~MemoryScan();
u32 GetValue() const { return m_value; }
bool GetValueSigned() const { return m_signed; }
MemoryAccessSize GetSize() const { return m_size; }
Operator GetOperator() const { return m_operator; }
PhysicalMemoryAddress GetStartAddress() const { return m_start_address; }
PhysicalMemoryAddress GetEndAddress() const { return m_end_address; }
const ResultVector& GetResults() const { return m_results; }
const Result& GetResult(u32 index) const { return m_results[index]; }
u32 GetResultCount() const { return static_cast<u32>(m_results.size()); }
void SetValue(u32 value) { m_value = value; }
void SetValueSigned(bool s) { m_signed = s; }
void SetSize(MemoryAccessSize size) { m_size = size; }
void SetOperator(Operator op) { m_operator = op; }
void SetStartAddress(PhysicalMemoryAddress addr) { m_start_address = addr; }
void SetEndAddress(PhysicalMemoryAddress addr) { m_end_address = addr; }
void ResetSearch();
void Search();
void SearchAgain();
void UpdateResultsValues();
void SetResultValue(u32 index, u32 value);
private:
void SearchBytes();
void SearchHalfwords();
void SearchWords();
u32 m_value = 0;
MemoryAccessSize m_size = MemoryAccessSize::HalfWord;
Operator m_operator = Operator::Equal;
PhysicalMemoryAddress m_start_address = 0;
PhysicalMemoryAddress m_end_address = 0x200000;
ResultVector m_results;
bool m_signed = false;
};
class MemoryWatchList
{
public:
MemoryWatchList();
~MemoryWatchList();
struct Entry
{
std::string description;
u32 address;
u32 value;
MemoryAccessSize size;
bool is_signed;
bool freeze;
bool changed;
};
using EntryVector = std::vector<Entry>;
const Entry* GetEntryByAddress(u32 address) const;
const EntryVector& GetEntries() const { return m_entries; }
const Entry& GetEntry(u32 index) const { return m_entries[index]; }
u32 GetEntryCount() const { return static_cast<u32>(m_entries.size()); }
bool AddEntry(std::string description, u32 address, MemoryAccessSize size, bool is_signed, bool freeze);
void RemoveEntry(u32 index);
bool RemoveEntryByDescription(const char* description);
bool RemoveEntryByAddress(u32 address);
void SetEntryDescription(u32 index, std::string description);
void SetEntryFreeze(u32 index, bool freeze);
void SetEntryValue(u32 index, u32 value);
void UpdateValues();
private:
static void SetEntryValue(Entry* entry, u32 value);
static void UpdateEntryValue(Entry* entry);
EntryVector m_entries;
};

View File

@ -168,7 +168,6 @@ void Settings::Load(SettingsInterface& si, SettingsInterface& controller_si)
load_devices_from_save_states = si.GetBoolValue("Main", "LoadDevicesFromSaveStates", false);
apply_compatibility_settings = si.GetBoolValue("Main", "ApplyCompatibilitySettings", true);
apply_game_settings = si.GetBoolValue("Main", "ApplyGameSettings", true);
enable_cheats = si.GetBoolValue("Console", "EnableCheats", false);
disable_all_enhancements = si.GetBoolValue("Main", "DisableAllEnhancements", false);
enable_discord_presence = si.GetBoolValue("Main", "EnableDiscordPresence", false);
rewind_enable = si.GetBoolValue("Main", "RewindEnable", false);
@ -514,7 +513,6 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
}
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", load_devices_from_save_states);
si.SetBoolValue("Console", "EnableCheats", enable_cheats);
si.SetBoolValue("Main", "DisableAllEnhancements", disable_all_enhancements);
si.SetBoolValue("Main", "RewindEnable", rewind_enable);
si.SetFloatValue("Main", "RewindFrequency", rewind_save_frequency);
@ -928,7 +926,6 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
g_settings.cpu_overclock_enable = false;
g_settings.cpu_overclock_active = false;
g_settings.enable_8mb_ram = false;
g_settings.enable_cheats = false;
g_settings.gpu_resolution_scale = 1;
g_settings.gpu_multisamples = 1;
g_settings.gpu_per_sample_shading = false;
@ -1041,7 +1038,6 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
(g_settings.fast_forward_speed != 0.0f) ? std::max(g_settings.fast_forward_speed, 1.0f) : 0.0f;
g_settings.turbo_speed = (g_settings.turbo_speed != 0.0f) ? std::max(g_settings.turbo_speed, 1.0f) : 0.0f;
g_settings.rewind_enable = false;
g_settings.enable_cheats = false;
if (g_settings.cpu_overclock_enable && g_settings.GetCPUOverclockPercent() < 100)
{
g_settings.cpu_overclock_enable = false;
@ -2125,6 +2121,7 @@ std::string EmuFolders::GameIcons;
std::string EmuFolders::GameSettings;
std::string EmuFolders::InputProfiles;
std::string EmuFolders::MemoryCards;
std::string EmuFolders::Patches;
std::string EmuFolders::Resources;
std::string EmuFolders::SaveStates;
std::string EmuFolders::Screenshots;
@ -2144,6 +2141,7 @@ void EmuFolders::SetDefaults()
GameSettings = Path::Combine(DataRoot, "gamesettings");
InputProfiles = Path::Combine(DataRoot, "inputprofiles");
MemoryCards = Path::Combine(DataRoot, "memcards");
Patches = Path::Combine(DataRoot, "patches");
SaveStates = Path::Combine(DataRoot, "savestates");
Screenshots = Path::Combine(DataRoot, "screenshots");
Shaders = Path::Combine(DataRoot, "shaders");
@ -2175,6 +2173,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
GameSettings = LoadPathFromSettings(si, DataRoot, "Folders", "GameSettings", "gamesettings");
InputProfiles = LoadPathFromSettings(si, DataRoot, "Folders", "InputProfiles", "inputprofiles");
MemoryCards = LoadPathFromSettings(si, DataRoot, "MemoryCards", "Directory", "memcards");
Patches = LoadPathFromSettings(si, DataRoot, "Folders", "Patches", "patches");
SaveStates = LoadPathFromSettings(si, DataRoot, "Folders", "SaveStates", "savestates");
Screenshots = LoadPathFromSettings(si, DataRoot, "Folders", "Screenshots", "screenshots");
Shaders = LoadPathFromSettings(si, DataRoot, "Folders", "Shaders", "shaders");
@ -2191,6 +2190,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
DEV_LOG("Game Settings Directory: {}", GameSettings);
DEV_LOG("Input Profile Directory: {}", InputProfiles);
DEV_LOG("MemoryCards Directory: {}", MemoryCards);
DEV_LOG("Patches Directory: {}", Patches);
DEV_LOG("Resources Directory: {}", Resources);
DEV_LOG("SaveStates Directory: {}", SaveStates);
DEV_LOG("Screenshots Directory: {}", Screenshots);
@ -2212,6 +2212,7 @@ void EmuFolders::Save(SettingsInterface& si)
si.SetStringValue("Folders", "GameSettings", Path::MakeRelative(GameSettings, DataRoot).c_str());
si.SetStringValue("Folders", "InputProfiles", Path::MakeRelative(InputProfiles, DataRoot).c_str());
si.SetStringValue("MemoryCards", "Directory", Path::MakeRelative(MemoryCards, DataRoot).c_str());
si.SetStringValue("Folders", "Patches", Path::MakeRelative(Patches, DataRoot).c_str());
si.SetStringValue("Folders", "SaveStates", Path::MakeRelative(SaveStates, DataRoot).c_str());
si.SetStringValue("Folders", "Screenshots", Path::MakeRelative(Screenshots, DataRoot).c_str());
si.SetStringValue("Folders", "Shaders", Path::MakeRelative(Shaders, DataRoot).c_str());
@ -2252,6 +2253,7 @@ bool EmuFolders::EnsureFoldersExist()
result = FileSystem::EnsureDirectoryExists(GameSettings.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(InputProfiles.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(MemoryCards.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(Patches.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(SaveStates.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(Screenshots.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(Shaders.c_str(), false) && result;

View File

@ -88,7 +88,6 @@ struct Settings
bool load_devices_from_save_states : 1 = false;
bool apply_compatibility_settings : 1 = true;
bool apply_game_settings : 1 = true;
bool enable_cheats : 1 = false;
bool disable_all_enhancements : 1 = false;
bool enable_discord_presence : 1 = false;
@ -575,6 +574,7 @@ extern std::string GameIcons;
extern std::string GameSettings;
extern std::string InputProfiles;
extern std::string MemoryCards;
extern std::string Patches;
extern std::string Resources;
extern std::string SaveStates;
extern std::string Screenshots;

View File

@ -252,7 +252,7 @@ static std::string s_running_game_serial;
static std::string s_running_game_title;
static std::string s_exe_override;
static const GameDatabase::Entry* s_running_game_entry = nullptr;
static System::GameHash s_running_game_hash;
static GameHash s_running_game_hash;
static System::BootMode s_boot_mode = System::BootMode::None;
static bool s_running_game_custom_title = false;
@ -311,7 +311,6 @@ static Common::Timer s_fps_timer;
static Common::Timer s_frame_timer;
static Threading::ThreadHandle s_cpu_thread_handle;
static std::unique_ptr<CheatList> s_cheat_list;
static std::unique_ptr<MediaCapture> s_media_capture;
// temporary save state, created when loading, used to undo load state
@ -714,7 +713,7 @@ const GameDatabase::Entry* System::GetGameDatabaseEntry()
return s_running_game_entry;
}
System::GameHash System::GetGameHash()
GameHash System::GetGameHash()
{
return s_running_game_hash;
}
@ -922,7 +921,7 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash
return true;
}
System::GameHash System::GetGameHashFromFile(const char* path)
GameHash System::GetGameHashFromFile(const char* path)
{
const std::optional<DynamicHeapArray<u8>> data = FileSystem::ReadBinaryFile(path);
if (!data)
@ -1061,8 +1060,8 @@ bool System::ReadExecutableFromImage(IsoReader& iso, std::string* out_executable
return true;
}
System::GameHash System::GetGameHashFromBuffer(std::string_view exe_name, std::span<const u8> exe_buffer,
const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length)
GameHash System::GetGameHashFromBuffer(std::string_view exe_name, std::span<const u8> exe_buffer,
const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length)
{
XXH64_state_t* state = XXH64_createState();
XXH64_reset(state, 0x4242D00C);
@ -1496,9 +1495,8 @@ void System::ResetSystem()
if (Achievements::ResetHardcoreMode(false))
{
// Make sure a pre-existing cheat file hasn't been loaded when resetting
// after enabling HC mode.
s_cheat_list.reset();
// Make sure a pre-existing cheat file hasn't been loaded when resetting after enabling HC mode.
Cheats::ReloadCheats(true, true, false, true);
ApplySettings(false);
}
@ -1712,6 +1710,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
Error::SetStringFmt(error, "File '{}' is not a valid executable to boot.",
Path::GetFileName(parameters.override_exe));
s_state = State::Shutdown;
Cheats::UnloadAll();
ClearRunningGame();
Host::OnSystemDestroyed();
Host::OnIdleStateChanged();
return false;
@ -1726,6 +1726,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
if (!CheckForSBIFile(disc.get(), error))
{
s_state = State::Shutdown;
Cheats::UnloadAll();
ClearRunningGame();
Host::OnSystemDestroyed();
Host::OnIdleStateChanged();
@ -1763,6 +1764,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
if (cancelled)
{
s_state = State::Shutdown;
Cheats::UnloadAll();
ClearRunningGame();
Host::OnSystemDestroyed();
Host::OnIdleStateChanged();
@ -1776,6 +1778,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
if (!SetBootMode(boot_mode, disc_region, error))
{
s_state = State::Shutdown;
Cheats::UnloadAll();
ClearRunningGame();
Host::OnSystemDestroyed();
Host::OnIdleStateChanged();
@ -1787,6 +1790,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
{
s_boot_mode = System::BootMode::None;
s_state = State::Shutdown;
Cheats::UnloadAll();
ClearRunningGame();
Host::OnSystemDestroyed();
Host::OnIdleStateChanged();
@ -1981,6 +1985,7 @@ void System::DestroySystem()
ClearMemorySaveStates();
Cheats::UnloadAll();
PCDrv::Shutdown();
SIO::Shutdown();
MDEC::Shutdown();
@ -2013,7 +2018,6 @@ void System::DestroySystem()
s_bios_image_info = nullptr;
s_exe_override = {};
s_boot_mode = BootMode::None;
s_cheat_list.reset();
s_state = State::Shutdown;
@ -2085,8 +2089,7 @@ void System::FrameDone()
// TODO: when running ahead, we can skip this (and the flush above)
SPU::GeneratePendingSamples();
if (s_cheat_list)
s_cheat_list->Apply();
Cheats::ApplyFrameEndCodes();
if (Achievements::IsActive())
Achievements::FrameUpdate();
@ -3598,33 +3601,6 @@ void System::DoFrameStep()
PauseSystem(false);
}
void System::DoToggleCheats()
{
if (!System::IsValid())
return;
if (Achievements::IsHardcoreModeActive())
{
Achievements::ConfirmHardcoreModeDisableAsync("Toggling cheats", [](bool approved) { DoToggleCheats(); });
return;
}
CheatList* cl = GetCheatList();
if (!cl)
{
Host::AddKeyedOSDMessage("ToggleCheats", TRANSLATE_STR("OSDMessage", "No cheats are loaded."), 10.0f);
return;
}
cl->SetMasterEnable(!cl->GetMasterEnable());
Host::AddIconOSDMessage(
"ToggleCheats", ICON_FA_EXCLAMATION_TRIANGLE,
cl->GetMasterEnable() ?
TRANSLATE_PLURAL_STR("System", "%n cheat(s) are now active.", "", cl->GetEnabledCodeCount()) :
TRANSLATE_PLURAL_STR("System", "%n cheat(s) are now inactive.", "", cl->GetEnabledCodeCount()),
Host::OSD_QUICK_DURATION);
}
#if 0
// currently not used until EXP1 is implemented
@ -4117,9 +4093,7 @@ void System::UpdateRunningGame(const std::string_view path, CDImage* image, bool
UpdateGameSettingsLayer();
ApplySettings(true);
s_cheat_list.reset();
if (g_settings.enable_cheats)
LoadCheatList();
Cheats::ReloadCheats(true, true, false, true);
if (s_running_game_serial != prev_serial)
UpdateSessionTime(prev_serial);
@ -4248,40 +4222,6 @@ bool System::SwitchMediaSubImage(u32 index)
return true;
}
bool System::HasCheatList()
{
return static_cast<bool>(s_cheat_list);
}
CheatList* System::GetCheatList()
{
return s_cheat_list.get();
}
void System::ApplyCheatCode(const CheatCode& code)
{
Assert(!IsShutdown());
code.Apply();
}
void System::SetCheatList(std::unique_ptr<CheatList> cheats)
{
Assert(!IsShutdown());
s_cheat_list = std::move(cheats);
if (s_cheat_list && s_cheat_list->GetEnabledCodeCount() > 0)
{
Host::AddIconOSDMessage("CheatsLoadWarning", ICON_FA_EXCLAMATION_TRIANGLE,
TRANSLATE_PLURAL_STR("System", "%n cheat(s) are enabled. This may crash games.", "",
s_cheat_list->GetEnabledCodeCount()),
Host::OSD_WARNING_DURATION);
}
else
{
Host::RemoveKeyedOSDMessage("CheatsLoadWarning");
}
}
void System::CheckForSettingsChanges(const Settings& old_settings)
{
if (IsValid() &&
@ -4390,14 +4330,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
Bus::RemapFastmemViews();
}
if (g_settings.enable_cheats != old_settings.enable_cheats)
{
if (g_settings.enable_cheats)
LoadCheatList();
else
SetCheatList(nullptr);
}
SPU::GetOutputStream()->SetOutputVolume(GetAudioOutputVolume());
if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale ||
@ -4688,8 +4620,10 @@ void System::WarnAboutUnsafeSettings()
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Overclock disabled."));
if (g_settings.enable_8mb_ram)
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "8MB RAM disabled."));
if (g_settings.enable_cheats)
if (s_game_settings_interface && s_game_settings_interface->GetBoolValue("Cheats", "EnableCheats", false))
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Cheats disabled."));
if (s_game_settings_interface && s_game_settings_interface->ContainsValue("Patches", "Enable"))
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Patches disabled."));
if (g_settings.gpu_resolution_scale != 1)
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Resolution scale set to 1x."));
if (g_settings.gpu_multisamples != 1)
@ -5585,154 +5519,6 @@ std::string System::GetCheatFileName()
return ret;
}
bool System::LoadCheatList()
{
// Called when booting, needs to test for shutdown.
if (IsShutdown() || !g_settings.enable_cheats)
return false;
const std::string filename(GetCheatFileName());
if (filename.empty() || !FileSystem::FileExists(filename.c_str()))
return false;
std::unique_ptr<CheatList> cl = std::make_unique<CheatList>();
if (!cl->LoadFromFile(filename.c_str(), CheatList::Format::Autodetect))
{
Host::AddIconOSDMessage(
"cheats_loaded", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("System", "Failed to load cheats from '{}'."), Path::GetFileName(filename)));
return false;
}
SetCheatList(std::move(cl));
return true;
}
bool System::LoadCheatListFromDatabase()
{
if (IsShutdown() || s_running_game_serial.empty() || Achievements::IsHardcoreModeActive())
return false;
std::unique_ptr<CheatList> cl = std::make_unique<CheatList>();
if (!cl->LoadFromPackage(s_running_game_serial))
return false;
INFO_LOG("Loaded {} cheats from database.", cl->GetCodeCount());
SetCheatList(std::move(cl));
return true;
}
bool System::SaveCheatList()
{
if (!System::IsValid() || !System::HasCheatList())
return false;
const std::string filename(GetCheatFileName());
if (filename.empty())
return false;
if (!System::GetCheatList()->SaveToPCSXRFile(filename.c_str()))
{
Host::AddIconOSDMessage(
"CheatSaveError", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("System", "Failed to save cheat list to '{}'."), Path::GetFileName(filename)),
Host::OSD_ERROR_DURATION);
}
return true;
}
bool System::DeleteCheatList()
{
if (!System::IsValid())
return false;
const std::string filename(GetCheatFileName());
if (!filename.empty())
{
if (!FileSystem::DeleteFile(filename.c_str()))
return false;
Host::AddIconOSDMessage(
"CheatDelete", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("System", "Deleted cheat list '{}'."), Path::GetFileName(filename)),
Host::OSD_INFO_DURATION);
}
System::SetCheatList(nullptr);
return true;
}
void System::ClearCheatList(bool save_to_file)
{
if (!System::IsValid())
return;
CheatList* cl = System::GetCheatList();
if (!cl)
return;
while (cl->GetCodeCount() > 0)
cl->RemoveCode(cl->GetCodeCount() - 1);
if (save_to_file)
SaveCheatList();
}
void System::SetCheatCodeState(u32 index, bool enabled)
{
if (!System::IsValid() || !System::HasCheatList())
return;
CheatList* cl = System::GetCheatList();
if (index >= cl->GetCodeCount())
return;
CheatCode& cc = cl->GetCode(index);
if (cc.enabled == enabled)
return;
cc.enabled = enabled;
if (!enabled)
cc.ApplyOnDisable();
if (enabled)
{
Host::AddIconOSDMessage(fmt::format("Cheat{}State", index), ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("System", "Cheat '{}' enabled."), cc.description),
Host::OSD_INFO_DURATION);
}
else
{
Host::AddIconOSDMessage(fmt::format("Cheat{}State", index), ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("System", "Cheat '{}' disabled."), cc.description),
Host::OSD_INFO_DURATION);
}
SaveCheatList();
}
void System::ApplyCheatCode(u32 index)
{
if (!System::HasCheatList() || index >= System::GetCheatList()->GetCodeCount())
return;
const CheatCode& cc = System::GetCheatList()->GetCode(index);
if (!cc.enabled)
{
cc.Apply();
Host::AddIconOSDMessage(fmt::format("Cheat{}State", index), ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("System", "Applied cheat '{}'."), cc.description),
Host::OSD_INFO_DURATION);
}
else
{
Host::AddIconOSDMessage(fmt::format("Cheat{}State", index), ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("System", "Cheat '{}' is already enabled."), cc.description),
Host::OSD_INFO_DURATION);
}
}
void System::ToggleWidescreen()
{
g_settings.gpu_widescreen_hack = !g_settings.gpu_widescreen_hack;

View File

@ -23,9 +23,6 @@ enum class GPUVSyncMode : u8;
class Controller;
struct CheatCode;
class CheatList;
class GPUTexture;
class MediaCapture;
@ -107,8 +104,6 @@ enum class BootMode
BootPSF,
};
using GameHash = u64;
extern TickCount g_ticks_per_second;
/// Returns true if the filename is a PlayStation executable we can inject.
@ -315,18 +310,6 @@ std::string GetMediaSubImageTitle(u32 index);
/// Switches to the specified media/disc playlist index.
bool SwitchMediaSubImage(u32 index);
/// Returns true if there is currently a cheat list.
bool HasCheatList();
/// Accesses the current cheat list.
CheatList* GetCheatList();
/// Applies a single cheat code.
void ApplyCheatCode(const CheatCode& code);
/// Sets or clears the provided cheat list, applying every frame.
void SetCheatList(std::unique_ptr<CheatList> cheats);
/// Updates throttler.
void UpdateSpeedLimiterState();
@ -343,7 +326,6 @@ bool IsRewinding();
void SetRewindState(bool enabled);
void DoFrameStep();
void DoToggleCheats();
/// Returns the path to a save state file. Specifying an index of -1 is the "resume" save state.
std::string GetGameSaveStateFileName(std::string_view serial, s32 slot);
@ -405,27 +387,6 @@ bool StartMediaCapture(std::string path = {});
bool StartMediaCapture(std::string path, bool capture_video, bool capture_audio);
void StopMediaCapture();
/// Loads the cheat list for the current game title from the user directory.
bool LoadCheatList();
/// Loads the cheat list for the current game code from the built-in code database.
bool LoadCheatListFromDatabase();
/// Saves the current cheat list to the game title's file.
bool SaveCheatList();
/// Deletes the cheat list, if present.
bool DeleteCheatList();
/// Removes all cheats from the cheat list.
void ClearCheatList(bool save_to_file);
/// Enables/disabled the specified cheat code.
void SetCheatCodeState(u32 index, bool enabled);
/// Immediately applies the specified cheat code.
void ApplyCheatCode(u32 index);
/// Toggle Widescreen Hack and Aspect Ratio
void ToggleWidescreen();

View File

@ -22,6 +22,7 @@ enum class MemoryAccessSize : u32
using TickCount = s32;
using GlobalTicks = u64;
using GameHash = u64;
enum class ConsoleRegion : u8
{

View File

@ -32,9 +32,6 @@ set(SRCS
cheatcodeeditordialog.cpp
cheatcodeeditordialog.h
cheatcodeeditordialog.ui
cheatmanagerwindow.cpp
cheatmanagerwindow.h
cheatmanagerwindow.ui
colorpickerbutton.cpp
colorpickerbutton.h
consolesettingswidget.cpp
@ -79,6 +76,13 @@ set(SRCS
foldersettingswidget.cpp
foldersettingswidget.h
foldersettingswidget.ui
gamecheatsettingswidget.cpp
gamecheatsettingswidget.h
gamecheatsettingswidget.ui
gamepatchdetailswidget.ui
gamepatchsettingswidget.cpp
gamepatchsettingswidget.h
gamepatchsettingswidget.ui
gamelistmodel.cpp
gamelistmodel.h
gamelistrefreshthread.cpp

View File

@ -2,9 +2,17 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "cheatcodeeditordialog.h"
#include "gamecheatsettingswidget.h"
#include "qtutils.h"
#include "core/cheats.h"
#include "common/error.h"
#include <QtWidgets/QMessageBox>
CheatCodeEditorDialog::CheatCodeEditorDialog(const QStringList& group_names, CheatCode* code, QWidget* parent)
CheatCodeEditorDialog::CheatCodeEditorDialog(GameCheatSettingsWidget* parent, Cheats::CodeInfo& code,
const QStringList& group_names)
: QDialog(parent), m_code(code)
{
m_ui.setupUi(this);
@ -24,16 +32,25 @@ void CheatCodeEditorDialog::saveClicked()
return;
}
if (!m_code->SetInstructionsFromString(m_ui.instructions->toPlainText().toStdString()))
std::string new_body = m_ui.instructions->toPlainText().toStdString();
if (new_body.empty())
{
QMessageBox::critical(this, tr("Error"), tr("Instructions are invalid."));
QMessageBox::critical(this, tr("Error"), tr("Instructions cannot be empty."));
return;
}
m_code->description = std::move(new_description);
m_code->type = static_cast<CheatCode::Type>(m_ui.type->currentIndex());
m_code->activation = static_cast<CheatCode::Activation>(m_ui.activation->currentIndex());
m_code->group = m_ui.group->currentText().toStdString();
// m_code.description
m_code.type = static_cast<Cheats::CodeType>(m_ui.type->currentIndex());
m_code.activation = static_cast<Cheats::CodeActivation>(m_ui.activation->currentIndex());
m_code.body = std::move(new_body);
std::string path = m_parent->getPathForSavingCheats();
Error error;
if (!Cheats::SaveCodeToFile(path.c_str(), m_code, &error))
{
QMessageBox::critical(this, tr("Error"),
tr("Failed to save cheat code:\n%1").arg(QString::fromStdString(error.GetDescription())));
}
done(1);
}
@ -45,28 +62,25 @@ void CheatCodeEditorDialog::cancelClicked()
void CheatCodeEditorDialog::setupAdditionalUi(const QStringList& group_names)
{
for (u32 i = 0; i < static_cast<u32>(CheatCode::Type::Count); i++)
{
m_ui.type->addItem(qApp->translate("Cheats", CheatCode::GetTypeDisplayName(static_cast<CheatCode::Type>(i))));
}
for (u32 i = 0; i < static_cast<u32>(Cheats::CodeType::Count); i++)
m_ui.type->addItem(Cheats::GetTypeDisplayName(static_cast<Cheats::CodeType>(i)));
for (u32 i = 0; i < static_cast<u32>(CheatCode::Activation::Count); i++)
{
m_ui.activation->addItem(
qApp->translate("Cheats", CheatCode::GetActivationDisplayName(static_cast<CheatCode::Activation>(i))));
}
for (u32 i = 0; i < static_cast<u32>(Cheats::CodeActivation::Count); i++)
m_ui.activation->addItem(Cheats::GetActivationDisplayName(static_cast<Cheats::CodeActivation>(i)));
m_ui.group->addItem(QStringLiteral("Ungrouped"), QVariant(QString()));
if (!group_names.isEmpty())
m_ui.group->addItems(group_names);
else
m_ui.group->addItem(QStringLiteral("Ungrouped"));
m_ui.group->addItem(QStringLiteral("New..."));
}
void CheatCodeEditorDialog::fillUi()
{
m_ui.description->setText(QString::fromStdString(m_code->description));
m_ui.description->setText(QtUtils::StringViewToQString(m_code.GetNamePart()));
const QString group_qstr(QString::fromStdString(m_code->group));
const QString group_qstr(QtUtils::StringViewToQString(m_code.GetNameParentPart()));
int index = m_ui.group->findText(group_qstr);
if (index >= 0)
{
@ -79,10 +93,10 @@ void CheatCodeEditorDialog::fillUi()
m_ui.group->setCurrentIndex(index);
}
m_ui.type->setCurrentIndex(static_cast<int>(m_code->type));
m_ui.activation->setCurrentIndex(static_cast<int>(m_code->activation));
m_ui.type->setCurrentIndex(static_cast<int>(m_code.type));
m_ui.activation->setCurrentIndex(static_cast<int>(m_code.activation));
m_ui.instructions->setPlainText(QString::fromStdString(m_code->GetInstructionsAsString()));
m_ui.instructions->setPlainText(QString::fromStdString(m_code.body));
}
void CheatCodeEditorDialog::connectUi()

View File

@ -2,15 +2,23 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "core/cheats.h"
#include "ui_cheatcodeeditordialog.h"
#include <QtCore/QStringList>
namespace Cheats {
struct CodeInfo;
}
class GameCheatSettingsWidget;
class CheatCodeEditorDialog : public QDialog
{
Q_OBJECT
public:
CheatCodeEditorDialog(const QStringList& group_names, CheatCode* code, QWidget* parent);
CheatCodeEditorDialog(GameCheatSettingsWidget* parent, Cheats::CodeInfo& code, const QStringList& group_names);
~CheatCodeEditorDialog();
private Q_SLOTS:
@ -22,7 +30,9 @@ private:
void fillUi();
void connectUi();
CheatCode* m_code;
GameCheatSettingsWidget* m_parent;
Cheats::CodeInfo& m_code;
Ui::CheatCodeEditorDialog m_ui;
};

View File

@ -1,581 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "cheatmanagerwindow.h"
#include "cheatcodeeditordialog.h"
#include "mainwindow.h"
#include "qthost.h"
#include "qtutils.h"
#include "core/bus.h"
#include "core/cpu_core.h"
#include "core/host.h"
#include "core/system.h"
#include "common/assert.h"
#include "common/string_util.h"
#include <QtCore/QFileInfo>
#include <QtGui/QColor>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QTreeWidgetItemIterator>
#include <array>
#include <utility>
CheatManagerWindow::CheatManagerWindow() : QWidget()
{
m_ui.setupUi(this);
QtUtils::RestoreWindowGeometry("CheatManagerWindow", this);
connectUi();
updateCheatList();
}
CheatManagerWindow::~CheatManagerWindow() = default;
void CheatManagerWindow::connectUi()
{
connect(m_ui.cheatList, &QTreeWidget::currentItemChanged, this, &CheatManagerWindow::cheatListCurrentItemChanged);
connect(m_ui.cheatList, &QTreeWidget::itemActivated, this, &CheatManagerWindow::cheatListItemActivated);
connect(m_ui.cheatList, &QTreeWidget::itemChanged, this, &CheatManagerWindow::cheatListItemChanged);
connect(m_ui.cheatListNewCategory, &QPushButton::clicked, this, &CheatManagerWindow::newCategoryClicked);
connect(m_ui.cheatListAdd, &QPushButton::clicked, this, &CheatManagerWindow::addCodeClicked);
connect(m_ui.cheatListEdit, &QPushButton::clicked, this, &CheatManagerWindow::editCodeClicked);
connect(m_ui.cheatListRemove, &QPushButton::clicked, this, &CheatManagerWindow::deleteCodeClicked);
connect(m_ui.cheatListActivate, &QPushButton::clicked, this, &CheatManagerWindow::activateCodeClicked);
connect(m_ui.cheatListImport, &QPushButton::clicked, this, &CheatManagerWindow::importClicked);
connect(m_ui.cheatListExport, &QPushButton::clicked, this, &CheatManagerWindow::exportClicked);
connect(m_ui.cheatListClear, &QPushButton::clicked, this, &CheatManagerWindow::clearClicked);
connect(m_ui.cheatListReset, &QPushButton::clicked, this, &CheatManagerWindow::resetClicked);
connect(g_emu_thread, &EmuThread::cheatEnabled, this, &CheatManagerWindow::setCheatCheckState);
connect(g_emu_thread, &EmuThread::runningGameChanged, this, &CheatManagerWindow::updateCheatList);
}
void CheatManagerWindow::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
resizeColumns();
}
void CheatManagerWindow::closeEvent(QCloseEvent* event)
{
QtUtils::SaveWindowGeometry("CheatManagerWindow", this);
QWidget::closeEvent(event);
emit closed();
}
void CheatManagerWindow::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
resizeColumns();
}
void CheatManagerWindow::resizeColumns()
{
QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {-1, 100, 150, 100});
}
QTreeWidgetItem* CheatManagerWindow::getItemForCheatIndex(u32 index) const
{
QTreeWidgetItemIterator iter(m_ui.cheatList);
while (*iter)
{
QTreeWidgetItem* item = *iter;
const QVariant item_data(item->data(0, Qt::UserRole));
if (item_data.isValid() && item_data.toUInt() == index)
return item;
++iter;
}
return nullptr;
}
QTreeWidgetItem* CheatManagerWindow::getItemForCheatGroup(const QString& group_name) const
{
const int count = m_ui.cheatList->topLevelItemCount();
for (int i = 0; i < count; i++)
{
QTreeWidgetItem* item = m_ui.cheatList->topLevelItem(i);
if (item->text(0) == group_name)
return item;
}
return nullptr;
}
QTreeWidgetItem* CheatManagerWindow::createItemForCheatGroup(const QString& group_name) const
{
QTreeWidgetItem* group = new QTreeWidgetItem();
group->setFlags(group->flags() | Qt::ItemIsUserCheckable);
group->setText(0, group_name);
m_ui.cheatList->addTopLevelItem(group);
return group;
}
QStringList CheatManagerWindow::getCheatGroupNames() const
{
QStringList group_names;
const int count = m_ui.cheatList->topLevelItemCount();
for (int i = 0; i < count; i++)
{
QTreeWidgetItem* item = m_ui.cheatList->topLevelItem(i);
group_names.push_back(item->text(0));
}
return group_names;
}
static int getCheatIndexFromItem(QTreeWidgetItem* item)
{
QVariant item_data(item->data(0, Qt::UserRole));
if (!item_data.isValid())
return -1;
return static_cast<int>(item_data.toUInt());
}
int CheatManagerWindow::getSelectedCheatIndex() const
{
QList<QTreeWidgetItem*> sel = m_ui.cheatList->selectedItems();
if (sel.isEmpty())
return -1;
return static_cast<int>(getCheatIndexFromItem(sel.first()));
}
CheatList* CheatManagerWindow::getCheatList() const
{
return System::IsValid() ? System::GetCheatList() : nullptr;
}
void CheatManagerWindow::updateCheatList()
{
QSignalBlocker sb(m_ui.cheatList);
while (m_ui.cheatList->topLevelItemCount() > 0)
delete m_ui.cheatList->takeTopLevelItem(0);
m_ui.cheatList->setEnabled(false);
m_ui.cheatListAdd->setEnabled(false);
m_ui.cheatListNewCategory->setEnabled(false);
m_ui.cheatListEdit->setEnabled(false);
m_ui.cheatListRemove->setEnabled(false);
m_ui.cheatListActivate->setText(tr("Activate"));
m_ui.cheatListActivate->setEnabled(false);
m_ui.cheatListImport->setEnabled(false);
m_ui.cheatListExport->setEnabled(false);
m_ui.cheatListClear->setEnabled(false);
m_ui.cheatListReset->setEnabled(false);
Host::RunOnCPUThread([]() {
if (!System::IsValid())
return;
CheatList* list = System::GetCheatList();
if (!list)
{
System::LoadCheatList();
list = System::GetCheatList();
}
if (!list)
{
System::LoadCheatListFromDatabase();
list = System::GetCheatList();
}
if (!list)
{
System::SetCheatList(std::make_unique<CheatList>());
list = System::GetCheatList();
}
// still racey...
QtHost::RunOnUIThread([list]() {
if (!QtHost::IsSystemValid())
return;
CheatManagerWindow* cm = g_main_window->getCheatManagerWindow();
if (!cm)
return;
QSignalBlocker sb(cm->m_ui.cheatList);
const std::vector<std::string> groups = list->GetCodeGroups();
for (const std::string& group_name : groups)
{
QTreeWidgetItem* group = cm->createItemForCheatGroup(QString::fromStdString(group_name));
const u32 count = list->GetCodeCount();
bool all_enabled = true;
for (u32 i = 0; i < count; i++)
{
const CheatCode& code = list->GetCode(i);
if (code.group != group_name)
continue;
QTreeWidgetItem* item = new QTreeWidgetItem(group);
cm->fillItemForCheatCode(item, i, code);
all_enabled &= code.enabled;
}
group->setCheckState(0, all_enabled ? Qt::Checked : Qt::Unchecked);
group->setExpanded(true);
}
cm->m_ui.cheatList->setEnabled(true);
cm->m_ui.cheatListAdd->setEnabled(true);
cm->m_ui.cheatListNewCategory->setEnabled(true);
cm->m_ui.cheatListImport->setEnabled(true);
cm->m_ui.cheatListClear->setEnabled(true);
cm->m_ui.cheatListReset->setEnabled(true);
cm->m_ui.cheatListExport->setEnabled(cm->m_ui.cheatList->topLevelItemCount() > 0);
});
});
}
void CheatManagerWindow::fillItemForCheatCode(QTreeWidgetItem* item, u32 index, const CheatCode& code)
{
item->setData(0, Qt::UserRole, QVariant(static_cast<uint>(index)));
if (code.IsManuallyActivated())
{
item->setFlags(item->flags() & ~(Qt::ItemIsUserCheckable));
}
else
{
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(0, code.enabled ? Qt::Checked : Qt::Unchecked);
}
item->setText(0, QString::fromStdString(code.description));
item->setText(1, qApp->translate("Cheats", CheatCode::GetTypeDisplayName(code.type)));
item->setText(2, qApp->translate("Cheats", CheatCode::GetActivationDisplayName(code.activation)));
item->setText(3, QString::number(static_cast<uint>(code.instructions.size())));
}
void CheatManagerWindow::saveCheatList()
{
Host::RunOnCPUThread([]() { System::SaveCheatList(); });
}
void CheatManagerWindow::cheatListCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
const int cheat_index = current ? getCheatIndexFromItem(current) : -1;
const bool has_current = (cheat_index >= 0);
m_ui.cheatListEdit->setEnabled(has_current);
m_ui.cheatListRemove->setEnabled(has_current);
m_ui.cheatListActivate->setEnabled(has_current);
if (!has_current)
{
m_ui.cheatListActivate->setText(tr("Activate"));
}
else
{
const bool manual_activation = getCheatList()->GetCode(static_cast<u32>(cheat_index)).IsManuallyActivated();
m_ui.cheatListActivate->setText(manual_activation ? tr("Activate") : tr("Toggle"));
}
}
void CheatManagerWindow::cheatListItemActivated(QTreeWidgetItem* item)
{
if (!item)
return;
const int index = getCheatIndexFromItem(item);
if (index >= 0)
activateCheat(static_cast<u32>(index));
}
void CheatManagerWindow::cheatListItemChanged(QTreeWidgetItem* item, int column)
{
if (!item || column != 0)
return;
CheatList* list = getCheatList();
const int index = getCheatIndexFromItem(item);
if (index < 0)
{
// we're probably a parent/group node
const int child_count = item->childCount();
const Qt::CheckState cs = item->checkState(0);
for (int i = 0; i < child_count; i++)
item->child(i)->setCheckState(0, cs);
return;
}
if (static_cast<u32>(index) >= list->GetCodeCount())
return;
CheatCode& cc = list->GetCode(static_cast<u32>(index));
if (cc.IsManuallyActivated())
return;
const bool new_enabled = (item->checkState(0) == Qt::Checked);
if (cc.enabled == new_enabled)
return;
Host::RunOnCPUThread([index, new_enabled]() {
System::GetCheatList()->SetCodeEnabled(static_cast<u32>(index), new_enabled);
System::SaveCheatList();
});
}
void CheatManagerWindow::activateCheat(u32 index)
{
CheatList* list = getCheatList();
if (index >= list->GetCodeCount())
return;
CheatCode& cc = list->GetCode(index);
if (cc.IsManuallyActivated())
{
g_emu_thread->applyCheat(index);
return;
}
const bool new_enabled = !cc.enabled;
setCheatCheckState(index, new_enabled);
Host::RunOnCPUThread([index, new_enabled]() {
System::GetCheatList()->SetCodeEnabled(index, new_enabled);
System::SaveCheatList();
});
}
void CheatManagerWindow::setCheatCheckState(u32 index, bool checked)
{
QTreeWidgetItem* item = getItemForCheatIndex(index);
if (item)
{
QSignalBlocker sb(m_ui.cheatList);
item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
}
}
void CheatManagerWindow::newCategoryClicked()
{
QString group_name = QInputDialog::getText(this, tr("Add Group"), tr("Group Name:"));
if (group_name.isEmpty())
return;
if (getItemForCheatGroup(group_name) != nullptr)
{
QMessageBox::critical(this, tr("Error"), tr("This group name already exists."));
return;
}
createItemForCheatGroup(group_name);
}
void CheatManagerWindow::addCodeClicked()
{
CheatList* list = getCheatList();
CheatCode new_code;
new_code.group = "Ungrouped";
CheatCodeEditorDialog editor(getCheatGroupNames(), &new_code, this);
if (editor.exec() > 0)
{
const QString group_name_qstr(QString::fromStdString(new_code.group));
QTreeWidgetItem* group_item = getItemForCheatGroup(group_name_qstr);
if (!group_item)
group_item = createItemForCheatGroup(group_name_qstr);
QTreeWidgetItem* item = new QTreeWidgetItem(group_item);
fillItemForCheatCode(item, list->GetCodeCount(), new_code);
group_item->setExpanded(true);
Host::RunOnCPUThread(
[&new_code]() {
System::GetCheatList()->AddCode(std::move(new_code));
System::SaveCheatList();
},
true);
}
}
void CheatManagerWindow::editCodeClicked()
{
int index = getSelectedCheatIndex();
if (index < 0)
return;
CheatList* list = getCheatList();
if (static_cast<u32>(index) >= list->GetCodeCount())
return;
CheatCode new_code = list->GetCode(static_cast<u32>(index));
CheatCodeEditorDialog editor(getCheatGroupNames(), &new_code, this);
if (editor.exec() > 0)
{
QTreeWidgetItem* item = getItemForCheatIndex(static_cast<u32>(index));
if (item)
{
if (new_code.group != list->GetCode(static_cast<u32>(index)).group)
{
item = item->parent()->takeChild(item->parent()->indexOfChild(item));
const QString group_name_qstr(QString::fromStdString(new_code.group));
QTreeWidgetItem* group_item = getItemForCheatGroup(group_name_qstr);
if (!group_item)
group_item = createItemForCheatGroup(group_name_qstr);
group_item->addChild(item);
group_item->setExpanded(true);
}
fillItemForCheatCode(item, static_cast<u32>(index), new_code);
}
else
{
// shouldn't happen...
updateCheatList();
}
Host::RunOnCPUThread(
[index, &new_code]() {
System::GetCheatList()->SetCode(static_cast<u32>(index), std::move(new_code));
System::SaveCheatList();
},
true);
}
}
void CheatManagerWindow::deleteCodeClicked()
{
int index = getSelectedCheatIndex();
if (index < 0)
return;
CheatList* list = getCheatList();
if (static_cast<u32>(index) >= list->GetCodeCount())
return;
if (QMessageBox::question(this, tr("Delete Code"),
tr("Are you sure you wish to delete the selected code? This action is not reversible."),
QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
{
return;
}
Host::RunOnCPUThread(
[index]() {
System::GetCheatList()->RemoveCode(static_cast<u32>(index));
System::SaveCheatList();
},
true);
updateCheatList();
}
void CheatManagerWindow::activateCodeClicked()
{
int index = getSelectedCheatIndex();
if (index < 0)
return;
activateCheat(static_cast<u32>(index));
}
void CheatManagerWindow::importClicked()
{
QMenu menu(this);
connect(menu.addAction(tr("From File...")), &QAction::triggered, this, &CheatManagerWindow::importFromFileTriggered);
connect(menu.addAction(tr("From Text...")), &QAction::triggered, this, &CheatManagerWindow::importFromTextTriggered);
menu.exec(QCursor::pos());
}
void CheatManagerWindow::importFromFileTriggered()
{
const QString filter(tr("PCSXR/Libretro Cheat Files (*.cht *.txt);;All Files (*.*)"));
const QString filename =
QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Import Cheats"), QString(), filter));
if (filename.isEmpty())
return;
CheatList new_cheats;
if (!new_cheats.LoadFromFile(filename.toUtf8().constData(), CheatList::Format::Autodetect))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to parse cheat file. The log may contain more information."));
return;
}
Host::RunOnCPUThread(
[&new_cheats]() {
DebugAssert(System::HasCheatList());
System::GetCheatList()->MergeList(new_cheats);
System::SaveCheatList();
},
true);
updateCheatList();
}
void CheatManagerWindow::importFromTextTriggered()
{
const QString text = QInputDialog::getMultiLineText(this, tr("Import Cheats"), tr("Cheat File Text:"));
if (text.isEmpty())
return;
CheatList new_cheats;
if (!new_cheats.LoadFromString(text.toStdString(), CheatList::Format::Autodetect))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to parse cheat file. The log may contain more information."));
return;
}
Host::RunOnCPUThread(
[&new_cheats]() {
DebugAssert(System::HasCheatList());
System::GetCheatList()->MergeList(new_cheats);
System::SaveCheatList();
},
true);
updateCheatList();
}
void CheatManagerWindow::exportClicked()
{
const QString filter(tr("PCSXR Cheat Files (*.cht);;All Files (*.*)"));
const QString filename =
QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Export Cheats"), QString(), filter));
if (filename.isEmpty())
return;
if (!getCheatList()->SaveToPCSXRFile(filename.toUtf8().constData()))
QMessageBox::critical(this, tr("Error"), tr("Failed to save cheat file. The log may contain more information."));
}
void CheatManagerWindow::clearClicked()
{
if (QMessageBox::question(this, tr("Confirm Clear"),
tr("Are you sure you want to remove all cheats? This is not reversible.")) !=
QMessageBox::Yes)
{
return;
}
Host::RunOnCPUThread([] { System::ClearCheatList(true); }, true);
updateCheatList();
}
void CheatManagerWindow::resetClicked()
{
if (QMessageBox::question(
this, tr("Confirm Reset"),
tr(
"Are you sure you want to reset the cheat list? Any cheats not in the DuckStation database WILL BE LOST.")) !=
QMessageBox::Yes)
{
return;
}
Host::RunOnCPUThread([] { System::DeleteCheatList(); }, true);
updateCheatList();
}

View File

@ -1,74 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "ui_cheatmanagerwindow.h"
#include "core/cheats.h"
#include <QtCore/QTimer>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTableWidget>
#include <QtWidgets/QWidget>
#include <optional>
class CheatManagerWindow : public QWidget
{
Q_OBJECT
public:
CheatManagerWindow();
~CheatManagerWindow();
Q_SIGNALS:
void closed();
protected:
void showEvent(QShowEvent* event);
void closeEvent(QCloseEvent* event);
void resizeEvent(QResizeEvent* event);
void resizeColumns();
private Q_SLOTS:
CheatList* getCheatList() const;
void updateCheatList();
void saveCheatList();
void cheatListCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
void cheatListItemActivated(QTreeWidgetItem* item);
void cheatListItemChanged(QTreeWidgetItem* item, int column);
void activateCheat(u32 index);
void setCheatCheckState(u32 index, bool checked);
void newCategoryClicked();
void addCodeClicked();
void editCodeClicked();
void deleteCodeClicked();
void activateCodeClicked();
void importClicked();
void importFromFileTriggered();
void importFromTextTriggered();
void exportClicked();
void clearClicked();
void resetClicked();
private:
enum : int
{
MAX_DISPLAYED_SCAN_RESULTS = 5000
};
void connectUi();
void fillItemForCheatCode(QTreeWidgetItem* item, u32 index, const CheatCode& code);
QTreeWidgetItem* getItemForCheatIndex(u32 index) const;
QTreeWidgetItem* getItemForCheatGroup(const QString& group_name) const;
QTreeWidgetItem* createItemForCheatGroup(const QString& group_name) const;
QStringList getCheatGroupNames() const;
int getSelectedCheatIndex() const;
Ui::CheatManagerWindow m_ui;
QTimer* m_update_timer = nullptr;
};

View File

@ -1,144 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CheatManagerWindow</class>
<widget class="QWidget" name="CheatManagerWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>817</width>
<height>462</height>
</rect>
</property>
<property name="windowTitle">
<string>Cheat Manager</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="cheatListNewCategory">
<property name="text">
<string>&amp;Add Group...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListAdd">
<property name="text">
<string>&amp;Add Code...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListEdit">
<property name="text">
<string>&amp;Edit Code...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListRemove">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Delete Code</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListActivate">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Activate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListImport">
<property name="text">
<string>Import...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListExport">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Export...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListClear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListReset">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QTreeWidget" name="cheatList">
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Activation</string>
</property>
</column>
<column>
<property name="text">
<string>Instructions</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -9,7 +9,6 @@
<ClCompile Include="audiosettingswidget.cpp" />
<ClCompile Include="autoupdaterdialog.cpp" />
<ClCompile Include="biossettingswidget.cpp" />
<ClCompile Include="cheatmanagerwindow.cpp" />
<ClCompile Include="cheatcodeeditordialog.cpp" />
<ClCompile Include="colorpickerbutton.cpp" />
<ClCompile Include="consolesettingswidget.cpp" />
@ -21,7 +20,9 @@
<ClCompile Include="debuggermodels.cpp" />
<ClCompile Include="debuggerwindow.cpp" />
<ClCompile Include="foldersettingswidget.cpp" />
<ClCompile Include="gamecheatsettingswidget.cpp" />
<ClCompile Include="gamelistmodel.cpp" />
<ClCompile Include="gamepatchsettingswidget.cpp" />
<ClCompile Include="interfacesettingswidget.cpp" />
<ClCompile Include="graphicssettingswidget.cpp" />
<ClCompile Include="hotkeysettingswidget.cpp" />
@ -58,7 +59,6 @@
<QtMoc Include="aboutdialog.h" />
<QtMoc Include="audiosettingswidget.h" />
<QtMoc Include="biossettingswidget.h" />
<QtMoc Include="cheatmanagerwindow.h" />
<QtMoc Include="cheatcodeeditordialog.h" />
<QtMoc Include="coverdownloaddialog.h" />
<QtMoc Include="memorycardsettingswidget.h" />
@ -85,6 +85,8 @@
<QtMoc Include="logwindow.h" />
<QtMoc Include="graphicssettingswidget.h" />
<QtMoc Include="memoryscannerwindow.h" />
<QtMoc Include="gamecheatsettingswidget.h" />
<QtMoc Include="gamepatchsettingswidget.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<QtMoc Include="selectdiscdialog.h" />
@ -139,9 +141,6 @@
<QtUi Include="memorycardeditorwindow.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="cheatmanagerwindow.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="cheatcodeeditordialog.ui">
<FileType>Document</FileType>
</QtUi>
@ -222,7 +221,6 @@
<ClCompile Include="$(IntDir)moc_autoupdaterdialog.cpp" />
<ClCompile Include="$(IntDir)moc_advancedsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_biossettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_cheatmanagerwindow.cpp" />
<ClCompile Include="$(IntDir)moc_cheatcodeeditordialog.cpp" />
<ClCompile Include="$(IntDir)moc_colorpickerbutton.cpp" />
<ClCompile Include="$(IntDir)moc_consolesettingswidget.cpp" />
@ -233,10 +231,12 @@
<ClCompile Include="$(IntDir)moc_displaywidget.cpp" />
<ClCompile Include="$(IntDir)moc_emulationsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_foldersettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamecheatsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistmodel.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistrefreshthread.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistwidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamepatchsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamesummarywidget.cpp" />
<ClCompile Include="$(IntDir)moc_graphicssettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_debuggermodels.cpp" />
@ -342,6 +342,15 @@
<QtUi Include="texturereplacementsettingsdialog.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="gamecheatsettingswidget.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="gamepatchsettingswidget.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="gamepatchdetailswidget.ui">
<FileType>Document</FileType>
</QtUi>
<None Include="translations\duckstation-qt_es-es.ts" />
<None Include="translations\duckstation-qt_tr.ts" />
</ItemGroup>
@ -395,7 +404,6 @@
<PreprocessorDefinitions>QT_NO_EXCEPTIONS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DisableSpecificWarnings>4127;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\minizip\include</AdditionalIncludeDirectories>
<!-- Qt relies on RTTI for assertions in Debug builds. -->
<RuntimeTypeInfo Condition="$(Configuration.Contains(Debug))">true</RuntimeTypeInfo>
<AdditionalOptions Condition="$(Configuration.Contains(Clang)) And $(Configuration.Contains(Debug))">%(AdditionalOptions) /clang:-frtti</AdditionalOptions>

View File

@ -23,7 +23,6 @@
<ClCompile Include="biossettingswidget.cpp" />
<ClCompile Include="memorycardeditorwindow.cpp" />
<ClCompile Include="postprocessingsettingswidget.cpp" />
<ClCompile Include="cheatmanagerwindow.cpp" />
<ClCompile Include="cheatcodeeditordialog.cpp" />
<ClCompile Include="debuggerwindow.cpp" />
<ClCompile Include="debuggermodels.cpp" />
@ -68,9 +67,6 @@
<ClCompile Include="$(IntDir)moc_cheatcodeeditordialog.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)moc_cheatmanagerwindow.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)moc_colorpickerbutton.cpp">
<Filter>moc</Filter>
</ClCompile>
@ -175,6 +171,14 @@
</ClCompile>
<ClCompile Include="vcruntimecheck.cpp" />
<ClCompile Include="qtthemes.cpp" />
<ClCompile Include="gamecheatsettingswidget.cpp" />
<ClCompile Include="gamepatchsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamepatchsettingswidget.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)moc_gamecheatsettingswidget.cpp">
<Filter>moc</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="qtutils.h" />
@ -216,7 +220,6 @@
<QtMoc Include="biossettingswidget.h" />
<QtMoc Include="memorycardeditorwindow.h" />
<QtMoc Include="postprocessingsettingswidget.h" />
<QtMoc Include="cheatmanagerwindow.h" />
<QtMoc Include="cheatcodeeditordialog.h" />
<QtMoc Include="debuggermodels.h" />
<QtMoc Include="debuggerwindow.h" />
@ -237,6 +240,8 @@
<QtMoc Include="graphicssettingswidget.h" />
<QtMoc Include="memoryscannerwindow.h" />
<QtMoc Include="selectdiscdialog.h" />
<QtMoc Include="gamecheatsettingswidget.h" />
<QtMoc Include="gamepatchsettingswidget.h" />
</ItemGroup>
<ItemGroup>
<QtUi Include="consolesettingswidget.ui" />
@ -252,7 +257,6 @@
<QtUi Include="biossettingswidget.ui" />
<QtUi Include="postprocessingchainconfigwidget.ui" />
<QtUi Include="memorycardeditorwindow.ui" />
<QtUi Include="cheatmanagerwindow.ui" />
<QtUi Include="cheatcodeeditordialog.ui" />
<QtUi Include="emulationsettingswidget.ui" />
<QtUi Include="achievementsettingswidget.ui" />
@ -285,6 +289,9 @@
<QtUi Include="controllerbindingwidget_justifier.ui" />
<QtUi Include="selectdiscdialog.ui" />
<QtUi Include="texturereplacementsettingsdialog.ui" />
<QtUi Include="gamepatchsettingswidget.ui" />
<QtUi Include="gamecheatsettingswidget.ui" />
<QtUi Include="gamepatchdetailswidget.ui" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="duckstation-qt.rc" />

View File

@ -0,0 +1,354 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gamecheatsettingswidget.h"
#include "cheatcodeeditordialog.h"
#include "mainwindow.h"
#include "qthost.h"
#include "qtutils.h"
#include "settingswindow.h"
#include "settingwidgetbinder.h"
#include "core/cheats.h"
#include "common/error.h"
#include <QtWidgets/QInputDialog>
GameCheatSettingsWidget::GameCheatSettingsWidget(SettingsWindow* dialog, QWidget* parent) : m_dialog(dialog)
{
m_ui.setupUi(this);
reloadList();
SettingsInterface* sif = m_dialog->getSettingsInterface();
// We don't use the binder here, because they're binary - either enabled, or not in the file.
m_ui.enableCheats->setChecked(sif->GetBoolValue("Cheats", "EnableCheats", false));
m_ui.loadDatabaseCheats->setChecked(sif->GetBoolValue("Cheats", "LoadCheatsFromDatabase", true));
updateListEnabled();
connect(m_ui.enableCheats, &QCheckBox::checkStateChanged, this, &GameCheatSettingsWidget::onEnableCheatsChanged);
connect(m_ui.loadDatabaseCheats, &QCheckBox::checkStateChanged, this,
&GameCheatSettingsWidget::onLoadDatabaseCheatsChanged);
connect(m_ui.cheatList, &QTreeWidget::itemDoubleClicked, this,
&GameCheatSettingsWidget::onCheatListItemDoubleClicked);
connect(m_ui.cheatList, &QTreeWidget::itemChanged, this, &GameCheatSettingsWidget::onCheatListItemChanged);
connect(m_ui.reloadCheats, &QToolButton::clicked, this, &GameCheatSettingsWidget::onReloadClicked);
connect(m_ui.disableAll, &QToolButton::clicked, this, [this]() { setStateForAll(false); });
connect(m_ui.importCheats, &QPushButton::clicked, this, &GameCheatSettingsWidget::onImportClicked);
connect(m_ui.exportCheats, &QPushButton::clicked, this, &GameCheatSettingsWidget::onExportClicked);
}
GameCheatSettingsWidget::~GameCheatSettingsWidget() = default;
std::string GameCheatSettingsWidget::getPathForSavingCheats() const
{
// Check for the path without the hash first. If we have one of those, keep using it.
std::string path = Cheats::GetChtFilename(m_dialog->getGameSerial(), std::nullopt, true);
if (!FileSystem::FileExists(path.c_str()))
path = Cheats::GetChtFilename(m_dialog->getGameSerial(), m_dialog->getGameHash(), true);
return path;
}
QStringList GameCheatSettingsWidget::getGroupNames() const
{
#if 0
#endif
return QStringList();
}
void GameCheatSettingsWidget::onEnableCheatsChanged(Qt::CheckState state)
{
if (state == Qt::Checked)
m_dialog->getSettingsInterface()->SetBoolValue("Cheats", "EnableCheats", true);
else
m_dialog->getSettingsInterface()->DeleteValue("Cheats", "EnableCheats");
saveAndReload(true, true, false, true);
updateListEnabled();
}
void GameCheatSettingsWidget::onLoadDatabaseCheatsChanged(Qt::CheckState state)
{
// Default is enabled.
if (state == Qt::Checked)
m_dialog->getSettingsInterface()->DeleteValue("Cheats", "LoadCheatsFromDatabase");
else
m_dialog->getSettingsInterface()->SetBoolValue("Cheats", "LoadCheatsFromDatabase", false);
saveAndReload(true, true, false, true);
updateListEnabled();
reloadList();
}
void GameCheatSettingsWidget::onCheatListItemDoubleClicked(QTreeWidgetItem* item, int column)
{
const QVariant item_data = item->data(0, Qt::UserRole);
if (!item_data.isValid())
return;
const std::string cheat_name = item_data.toString().toStdString();
Cheats::CodeInfo* code = Cheats::FindCodeInInfoList(m_codes, cheat_name);
if (!code)
return;
CheatCodeEditorDialog dlg(this, *code, getGroupNames());
if (dlg.exec())
reloadList();
}
void GameCheatSettingsWidget::onCheatListItemChanged(QTreeWidgetItem* item, int column)
{
const QVariant item_data = item->data(0, Qt::UserRole);
if (!item_data.isValid())
return;
std::string cheat_name = item_data.toString().toStdString();
const bool current_enabled =
(std::find(m_enabled_codes.begin(), m_enabled_codes.end(), cheat_name) != m_enabled_codes.end());
const bool current_checked = (item->checkState(0) == Qt::Checked);
if (current_enabled == current_checked)
return;
setCheatEnabled(std::move(cheat_name), current_checked, true);
}
void GameCheatSettingsWidget::onReloadClicked()
{
reloadList();
g_emu_thread->reloadPatches(true, false, true, true);
}
bool GameCheatSettingsWidget::shouldLoadFromDatabase() const
{
return m_dialog->getSettingsInterface()->GetBoolValue("Cheats", "LoadCheatsFromDatabase", true);
}
void GameCheatSettingsWidget::updateListEnabled()
{
const bool cheats_enabled = m_dialog->getSettingsInterface()->GetBoolValue("Cheats", "EnableCheats", false);
m_ui.cheatList->setEnabled(cheats_enabled);
m_ui.add->setEnabled(cheats_enabled);
m_ui.remove->setEnabled(cheats_enabled);
m_ui.disableAll->setEnabled(cheats_enabled);
m_ui.reloadCheats->setEnabled(cheats_enabled);
}
void GameCheatSettingsWidget::disableAllCheats()
{
SettingsInterface* sif = m_dialog->getSettingsInterface();
sif->RemoveSection(Cheats::CHEATS_CONFIG_SECTION);
saveAndReload(false, true, false, true);
}
void GameCheatSettingsWidget::saveAndReload(bool reload_files, bool reload_enabled_list, bool verbose,
bool verbose_if_changed)
{
QtHost::SaveGameSettings(m_dialog->getSettingsInterface(), true);
g_emu_thread->reloadPatches(reload_files, reload_enabled_list, verbose, verbose_if_changed);
}
void GameCheatSettingsWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {320, 100, -1});
}
void GameCheatSettingsWidget::setCheatEnabled(std::string name, bool enabled, bool save_and_reload_settings)
{
SettingsInterface* si = m_dialog->getSettingsInterface();
const auto it = std::find(m_enabled_codes.begin(), m_enabled_codes.end(), name);
if (enabled)
{
si->AddToStringList(Cheats::CHEATS_CONFIG_SECTION, Cheats::PATCH_ENABLE_CONFIG_KEY, name.c_str());
if (it == m_enabled_codes.end())
m_enabled_codes.push_back(std::move(name));
}
else
{
si->RemoveFromStringList(Cheats::CHEATS_CONFIG_SECTION, Cheats::PATCH_ENABLE_CONFIG_KEY, name.c_str());
if (it != m_enabled_codes.end())
m_enabled_codes.erase(it);
}
if (save_and_reload_settings)
saveAndReload(false, true, false, true);
}
void GameCheatSettingsWidget::setStateForAll(bool enabled)
{
QSignalBlocker sb(m_ui.cheatList);
setStateRecursively(nullptr, enabled);
saveAndReload(false, true, false, true);
}
void GameCheatSettingsWidget::setStateRecursively(QTreeWidgetItem* parent, bool enabled)
{
const int count = parent ? parent->childCount() : m_ui.cheatList->topLevelItemCount();
for (int i = 0; i < count; i++)
{
QTreeWidgetItem* item = parent ? parent->child(i) : m_ui.cheatList->topLevelItem(i);
const QVariant item_data = item->data(0, Qt::UserRole);
if (item_data.isValid())
{
if ((item->checkState(0) == Qt::Checked) != enabled)
{
item->setCheckState(0, enabled ? Qt::Checked : Qt::Unchecked);
setCheatEnabled(item_data.toString().toStdString(), enabled, false);
}
}
else
{
setStateRecursively(item, enabled);
}
}
}
void GameCheatSettingsWidget::reloadList()
{
// Show all hashes, since the ini is shared.
m_codes = Cheats::GetCodeInfoList(m_dialog->getGameSerial(), std::nullopt, true, shouldLoadFromDatabase());
m_enabled_codes =
m_dialog->getSettingsInterface()->GetStringList(Cheats::CHEATS_CONFIG_SECTION, Cheats::PATCH_ENABLE_CONFIG_KEY);
m_parent_map.clear();
while (m_ui.cheatList->topLevelItemCount() > 0)
delete m_ui.cheatList->takeTopLevelItem(0);
for (const Cheats::CodeInfo& ci : m_codes)
{
const bool enabled = (std::find(m_enabled_codes.begin(), m_enabled_codes.end(), ci.name) != m_enabled_codes.end());
const std::string_view parent_part = ci.GetNameParentPart();
QTreeWidgetItem* parent = getTreeWidgetParent(parent_part);
QTreeWidgetItem* item = new QTreeWidgetItem();
populateTreeWidgetItem(item, ci, enabled);
if (parent)
parent->addChild(item);
else
m_ui.cheatList->addTopLevelItem(item);
}
// Hide root indicator when there's no groups, frees up some whitespace.
m_ui.cheatList->setRootIsDecorated(!m_parent_map.empty());
}
void GameCheatSettingsWidget::onImportClicked()
{
QMenu menu(this);
connect(menu.addAction(tr("From File...")), &QAction::triggered, this,
&GameCheatSettingsWidget::onImportFromFileTriggered);
connect(menu.addAction(tr("From Text...")), &QAction::triggered, this,
&GameCheatSettingsWidget::onImportFromTextTriggered);
menu.exec(QCursor::pos());
}
void GameCheatSettingsWidget::onImportFromFileTriggered()
{
const QString filter(tr("PCSXR/Libretro Cheat Files (*.cht *.txt);;All Files (*.*)"));
const QString filename =
QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Import Cheats"), QString(), filter));
if (filename.isEmpty())
return;
Error error;
const std::optional<std::string> file_contents = FileSystem::ReadFileToString(filename.toStdString().c_str(), &error);
if (!file_contents.has_value())
{
QMessageBox::critical(this, tr("Error"),
tr("Failed to read file:\n%1").arg(QString::fromStdString(error.GetDescription())));
return;
}
importCodes(file_contents.value());
}
void GameCheatSettingsWidget::onImportFromTextTriggered()
{
const QString text = QInputDialog::getMultiLineText(this, tr("Import Cheats"), tr("Cheat File Text:"));
if (text.isEmpty())
return;
importCodes(text.toStdString());
}
void GameCheatSettingsWidget::importCodes(const std::string& file_contents)
{
Error error;
Cheats::CodeInfoList new_codes;
if (!Cheats::ImportCodesFromString(&new_codes, file_contents, Cheats::FileFormat::Unknown, true, &error))
{
QMessageBox::critical(this, tr("Error"),
tr("Failed to parse file:\n%1").arg(QString::fromStdString(error.GetDescription())));
return;
}
Cheats::MergeCheatList(&m_codes, std::move(new_codes));
if (!Cheats::SaveCodesToFile(getPathForSavingCheats().c_str(), m_codes, &error))
{
QMessageBox::critical(this, tr("Error"),
tr("Failed to save file:\n%1").arg(QString::fromStdString(error.GetDescription())));
}
reloadList();
}
void GameCheatSettingsWidget::onExportClicked()
{
const QString filter(tr("PCSXR Cheat Files (*.cht);;All Files (*.*)"));
const QString filename =
QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Export Cheats"), QString(), filter));
if (filename.isEmpty())
return;
Error error;
if (!Cheats::ExportCodesToFile(filename.toStdString(), m_codes, &error))
{
QMessageBox::critical(this, tr("Error"),
tr("Failed to save cheat file:\n%1").arg(QString::fromStdString(error.GetDescription())));
}
}
QTreeWidgetItem* GameCheatSettingsWidget::getTreeWidgetParent(const std::string_view parent)
{
if (parent.empty())
return nullptr;
auto it = m_parent_map.find(parent);
if (it != m_parent_map.end())
return it->second;
std::string_view this_part = parent;
QTreeWidgetItem* parent_to_this = nullptr;
const std::string_view::size_type pos = parent.rfind('\\');
if (pos != std::string::npos && pos != (parent.size() - 1))
{
// go up the chain until we find the real parent, then back down
parent_to_this = getTreeWidgetParent(parent.substr(0, pos));
this_part = parent.substr(pos + 1);
}
QTreeWidgetItem* item = new QTreeWidgetItem();
item->setText(0, QString::fromUtf8(this_part.data(), this_part.length()));
if (parent_to_this)
parent_to_this->addChild(item);
else
m_ui.cheatList->addTopLevelItem(item);
// Must be called after adding.
item->setExpanded(true);
m_parent_map.emplace(parent, item);
return item;
}
void GameCheatSettingsWidget::populateTreeWidgetItem(QTreeWidgetItem* item, const Cheats::CodeInfo& pi, bool enabled)
{
const std::string_view name_part = pi.GetNamePart();
item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren);
item->setCheckState(0, enabled ? Qt::Checked : Qt::Unchecked);
item->setData(0, Qt::UserRole, QString::fromStdString(pi.name));
if (!name_part.empty())
item->setText(0, QtUtils::StringViewToQString(name_part));
}

View File

@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "ui_gamecheatsettingswidget.h"
#include "core/cheats.h"
#include "common/heterogeneous_containers.h"
#include <QtCore/QStringList>
#include <QtWidgets/QWidget>
#include <string>
#include <string_view>
#include <vector>
namespace GameList {
struct Entry;
}
class SettingsWindow;
class GameCheatSettingsWidget : public QWidget
{
Q_OBJECT
public:
GameCheatSettingsWidget(SettingsWindow* dialog, QWidget* parent);
~GameCheatSettingsWidget();
std::string getPathForSavingCheats() const;
QStringList getGroupNames() const;
void disableAllCheats();
protected:
void resizeEvent(QResizeEvent* event) override;
private Q_SLOTS:
void onEnableCheatsChanged(Qt::CheckState state);
void onLoadDatabaseCheatsChanged(Qt::CheckState state);
void onCheatListItemDoubleClicked(QTreeWidgetItem* item, int column);
void onCheatListItemChanged(QTreeWidgetItem* item, int column);
void onReloadClicked();
void updateListEnabled();
void reloadList();
void onImportClicked();
void onImportFromFileTriggered();
void onImportFromTextTriggered();
void onExportClicked();
private:
bool shouldLoadFromDatabase() const;
QTreeWidgetItem* getTreeWidgetParent(const std::string_view parent);
void populateTreeWidgetItem(QTreeWidgetItem* item, const Cheats::CodeInfo& pi, bool enabled);
void setCheatEnabled(std::string name, bool enabled, bool save_and_reload_settings);
void setStateForAll(bool enabled);
void setStateRecursively(QTreeWidgetItem* parent, bool enabled);
void saveAndReload(bool reload_files, bool reload_enabled_list, bool verbose, bool verbose_if_changed);
void importCodes(const std::string& file_contents);
Ui::GameCheatSettingsWidget m_ui;
SettingsWindow* m_dialog;
UnorderedStringMap<QTreeWidgetItem*> m_parent_map;
Cheats::CodeInfoList m_codes;
std::vector<std::string> m_enabled_codes;
};

View File

@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GameCheatSettingsWidget</class>
<widget class="QWidget" name="GameCheatSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>821</width>
<height>401</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Using cheats can have unpredictable effects on games, causing crashes, graphical glitches, and corrupted saves. Cheats can persist through save states even after being disabled, please remember to reset/reboot the game after turning off any codes.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="enableCheats">
<property name="text">
<string>Enable Cheats</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="loadDatabaseCheats">
<property name="text">
<string>Load Database Cheats</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeWidget" name="cheatList">
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::NoSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectItems</enum>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="add">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Add Cheat</string>
</property>
<property name="icon">
<iconset theme="add-line"/>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonStyle::ToolButtonIconOnly</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="remove">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Remove Cheat</string>
</property>
<property name="icon">
<iconset theme="minus-line"/>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonStyle::ToolButtonIconOnly</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="disableAll">
<property name="toolTip">
<string>Disable All Cheats</string>
</property>
<property name="icon">
<iconset theme="chat-off-line"/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="reloadCheats">
<property name="toolTip">
<string>Reload Cheats</string>
</property>
<property name="icon">
<iconset theme="refresh-line"/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="importCheats">
<property name="text">
<string>Import...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="exportCheats">
<property name="text">
<string>Export...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GamePatchDetailsWidget</class>
<widget class="QWidget" name="GamePatchDetailsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>541</width>
<height>112</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="name">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Patch Title</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="enabled">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="description">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Author: &lt;/span&gt;Patch Author&lt;/p&gt;&lt;p&gt;Description would go here&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gamepatchsettingswidget.h"
#include "mainwindow.h"
#include "qthost.h"
#include "qtutils.h"
#include "settingswindow.h"
#include "settingwidgetbinder.h"
#include "core/cheats.h"
#include "common/assert.h"
#include <algorithm>
GamePatchDetailsWidget::GamePatchDetailsWidget(std::string name, const std::string& author,
const std::string& description, bool enabled, SettingsWindow* dialog,
QWidget* parent)
: QWidget(parent), m_dialog(dialog), m_name(name)
{
m_ui.setupUi(this);
m_ui.name->setText(QString::fromStdString(name));
m_ui.description->setText(
tr("<strong>Author: </strong>%1<br>%2")
.arg(author.empty() ? tr("Unknown") : QString::fromStdString(author))
.arg(description.empty() ? tr("No description provided.") : QString::fromStdString(description)));
DebugAssert(dialog->getSettingsInterface());
m_ui.enabled->setChecked(enabled);
connect(m_ui.enabled, &QCheckBox::checkStateChanged, this, &GamePatchDetailsWidget::onEnabledStateChanged);
}
GamePatchDetailsWidget::~GamePatchDetailsWidget() = default;
void GamePatchDetailsWidget::onEnabledStateChanged(int state)
{
SettingsInterface* si = m_dialog->getSettingsInterface();
if (state == Qt::Checked)
si->AddToStringList("Patches", "Enable", m_name.c_str());
else
si->RemoveFromStringList("Patches", "Enable", m_name.c_str());
si->Save();
g_emu_thread->reloadGameSettings();
}
GamePatchSettingsWidget::GamePatchSettingsWidget(SettingsWindow* dialog, QWidget* parent) : m_dialog(dialog)
{
m_ui.setupUi(this);
m_ui.scrollArea->setFrameShape(QFrame::WinPanel);
m_ui.scrollArea->setFrameShadow(QFrame::Sunken);
connect(m_ui.reload, &QPushButton::clicked, this, &GamePatchSettingsWidget::onReloadClicked);
connect(m_ui.disableAllPatches, &QPushButton::clicked, this, &GamePatchSettingsWidget::disableAllPatches);
reloadList();
}
GamePatchSettingsWidget::~GamePatchSettingsWidget() = default;
void GamePatchSettingsWidget::onReloadClicked()
{
reloadList();
// reload it on the emu thread too, so it picks up any changes
g_emu_thread->reloadPatches(true, false, true, true);
}
void GamePatchSettingsWidget::disableAllPatches()
{
SettingsInterface* sif = m_dialog->getSettingsInterface();
sif->RemoveSection(Cheats::PATCHES_CONFIG_SECTION);
QtHost::SaveGameSettings(sif, true);
g_emu_thread->reloadPatches(false, true, false, true);
reloadList();
}
void GamePatchSettingsWidget::reloadList()
{
const std::vector<Cheats::CodeInfo> patches =
Cheats::GetCodeInfoList(m_dialog->getGameSerial(), std::nullopt, false, true);
const std::vector<std::string> enabled_list =
m_dialog->getSettingsInterface()->GetStringList(Cheats::PATCHES_CONFIG_SECTION, Cheats::PATCH_ENABLE_CONFIG_KEY);
delete m_ui.scrollArea->takeWidget();
QWidget* container = new QWidget(m_ui.scrollArea);
QVBoxLayout* layout = new QVBoxLayout(container);
layout->setContentsMargins(0, 0, 0, 0);
if (!patches.empty())
{
bool first = true;
for (const Cheats::CodeInfo& pi : patches)
{
if (!first)
{
QFrame* frame = new QFrame(container);
frame->setFrameShape(QFrame::HLine);
frame->setFrameShadow(QFrame::Sunken);
layout->addWidget(frame);
}
else
{
first = false;
}
const bool enabled = (std::find(enabled_list.begin(), enabled_list.end(), pi.name) != enabled_list.end());
GamePatchDetailsWidget* it =
new GamePatchDetailsWidget(std::move(pi.name), pi.author, pi.description, enabled, m_dialog, container);
layout->addWidget(it);
}
}
else
{
QLabel* label = new QLabel(tr("There are no patches available for this game."), container);
layout->addWidget(label);
}
layout->addStretch(1);
m_ui.scrollArea->setWidget(container);
}

View File

@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "ui_gamepatchdetailswidget.h"
#include "ui_gamepatchsettingswidget.h"
#include <QtWidgets/QWidget>
namespace GameList {
struct Entry;
}
class SettingsWindow;
class GamePatchDetailsWidget : public QWidget
{
Q_OBJECT
public:
GamePatchDetailsWidget(std::string name, const std::string& author, const std::string& description, bool enabled,
SettingsWindow* dialog, QWidget* parent);
~GamePatchDetailsWidget();
private Q_SLOTS:
void onEnabledStateChanged(int state);
private:
Ui::GamePatchDetailsWidget m_ui;
SettingsWindow* m_dialog;
std::string m_name;
};
class GamePatchSettingsWidget : public QWidget
{
Q_OBJECT
public:
GamePatchSettingsWidget(SettingsWindow* dialog, QWidget* parent);
~GamePatchSettingsWidget();
public Q_SLOTS:
void disableAllPatches();
private Q_SLOTS:
void onReloadClicked();
private:
void reloadList();
Ui::GamePatchSettingsWidget m_ui;
SettingsWindow* m_dialog;
};

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GamePatchSettingsWidget</class>
<widget class="QWidget" name="GamePatchSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>766</width>
<height>392</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Activating game patches can cause unpredictable behavior, crashing, soft-locks, or broken saved games. Use patches at your own risk, no support will be provided to users who have enabled game patches.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="disableAllPatches">
<property name="text">
<string>Disable All Patches</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="reload">
<property name="text">
<string>Reload Patches</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -5,7 +5,6 @@
#include "aboutdialog.h"
#include "achievementlogindialog.h"
#include "autoupdaterdialog.h"
#include "cheatmanagerwindow.h"
#include "coverdownloaddialog.h"
#include "debuggerwindow.h"
#include "displaywidget.h"
@ -23,6 +22,7 @@
#include "settingwidgetbinder.h"
#include "core/achievements.h"
#include "core/cheats.h"
#include "core/game_list.h"
#include "core/host.h"
#include "core/memory_card.h"
@ -787,7 +787,6 @@ void MainWindow::destroySubWindows()
{
QtUtils::CloseAndDeleteWindow(m_memory_scanner_window);
QtUtils::CloseAndDeleteWindow(m_debugger_window);
QtUtils::CloseAndDeleteWindow(m_cheat_manager_window);
QtUtils::CloseAndDeleteWindow(m_memory_card_editor_window);
QtUtils::CloseAndDeleteWindow(m_controller_settings_window);
QtUtils::CloseAndDeleteWindow(m_settings_window);
@ -1061,62 +1060,47 @@ void MainWindow::onCheatsActionTriggered()
void MainWindow::onCheatsMenuAboutToShow()
{
m_ui.menuCheats->clear();
connect(m_ui.menuCheats->addAction(tr("Cheat Manager")), &QAction::triggered, this, &MainWindow::openCheatManager);
connect(m_ui.menuCheats->addAction(tr("Select Cheats...")), &QAction::triggered, this,
[this]() { openGamePropertiesForCurrentGame("Cheats"); });
m_ui.menuCheats->addSeparator();
populateCheatsMenu(m_ui.menuCheats);
}
void MainWindow::populateCheatsMenu(QMenu* menu)
{
const bool has_cheat_list = (s_system_valid && System::HasCheatList());
Host::RunOnCPUThread([menu]() {
if (!System::IsValid())
return;
QMenu* enabled_menu = menu->addMenu(tr("&Enabled Cheats"));
enabled_menu->setEnabled(s_system_valid);
QMenu* apply_menu = menu->addMenu(tr("&Apply Cheats"));
apply_menu->setEnabled(s_system_valid);
if (has_cheat_list)
{
CheatList* cl = System::GetCheatList();
for (const std::string& group : cl->GetCodeGroups())
if (!Cheats::AreCheatsEnabled())
{
QMenu* enabled_submenu = nullptr;
QMenu* apply_submenu = nullptr;
for (u32 i = 0; i < cl->GetCodeCount(); i++)
{
CheatCode& cc = cl->GetCode(i);
if (cc.group != group)
continue;
QString desc(QString::fromStdString(cc.description));
if (cc.IsManuallyActivated())
{
if (!apply_submenu)
{
apply_menu->setEnabled(true);
apply_submenu = apply_menu->addMenu(QString::fromStdString(group));
}
QAction* action = apply_submenu->addAction(desc);
connect(action, &QAction::triggered, [i]() { g_emu_thread->applyCheat(i); });
}
else
{
if (!enabled_submenu)
{
enabled_menu->setEnabled(true);
enabled_submenu = enabled_menu->addMenu(QString::fromStdString(group));
}
QAction* action = enabled_submenu->addAction(desc);
action->setCheckable(true);
action->setChecked(cc.enabled);
connect(action, &QAction::toggled, [i](bool enabled) { g_emu_thread->setCheatEnabled(i, enabled); });
}
}
QAction* action = menu->addAction(tr("Cheats are not enabled."));
action->setEnabled(false);
return;
}
}
QStringList names;
Cheats::EnumerateManualCodes([&names](const std::string& name) {
names.append(QString::fromStdString(name));
return true;
});
if (names.empty())
return;
QtHost::RunOnUIThread([menu, names = std::move(names)]() {
QMenu* apply_submenu = menu->addMenu(tr("&Apply Cheat"));
for (const QString& name : names)
{
const QAction* action = apply_submenu->addAction(name);
connect(action, &QAction::triggered, apply_submenu, [action]() {
Host::RunOnCPUThread([name = action->text().toStdString()]() {
if (System::IsValid())
Cheats::ApplyManualCode(name);
});
});
}
});
});
}
const GameList::Entry* MainWindow::resolveDiscSetEntry(const GameList::Entry* entry,
@ -1375,23 +1359,6 @@ void MainWindow::onViewSystemDisplayTriggered()
switchToEmulationView();
}
void MainWindow::onViewGamePropertiesActionTriggered()
{
if (!s_system_valid)
return;
Host::RunOnCPUThread([]() {
const std::string& path = System::GetDiscPath();
const std::string& serial = System::GetGameSerial();
if (path.empty() || serial.empty())
return;
QtHost::RunOnUIThread([path = path, serial = serial]() {
SettingsWindow::openGamePropertiesDialog(path, System::GetGameTitle(), serial, System::GetDiscRegion());
});
});
}
void MainWindow::onGitHubRepositoryActionTriggered()
{
QtUtils::OpenURL(this, "https://github.com/stenzek/duckstation/");
@ -1486,7 +1453,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
if (!entry->IsDiscSet())
{
connect(menu.addAction(tr("Properties...")), &QAction::triggered, [entry]() {
SettingsWindow::openGamePropertiesDialog(entry->path, entry->title, entry->serial, entry->region);
SettingsWindow::openGamePropertiesDialog(entry->path, entry->title, entry->serial, entry->hash, entry->region);
});
connect(menu.addAction(tr("Open Containing Directory...")), &QAction::triggered, [this, entry]() {
@ -1556,7 +1523,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
if (first_disc)
{
SettingsWindow::openGamePropertiesDialog(first_disc->path, first_disc->title, first_disc->serial,
first_disc->region);
first_disc->hash, first_disc->region);
}
});
@ -2081,7 +2048,7 @@ void MainWindow::connectSignals()
connect(m_ui.actionViewGameList, &QAction::triggered, this, &MainWindow::onViewGameListActionTriggered);
connect(m_ui.actionViewGameGrid, &QAction::triggered, this, &MainWindow::onViewGameGridActionTriggered);
connect(m_ui.actionViewSystemDisplay, &QAction::triggered, this, &MainWindow::onViewSystemDisplayTriggered);
connect(m_ui.actionViewGameProperties, &QAction::triggered, this, &MainWindow::onViewGamePropertiesActionTriggered);
connect(m_ui.actionViewGameProperties, &QAction::triggered, this, [this]() { openGamePropertiesForCurrentGame(); });
connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered);
connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered);
connect(m_ui.actionViewThirdPartyNotices, &QAction::triggered, this,
@ -2096,8 +2063,10 @@ void MainWindow::connectSignals()
connect(m_ui.actionCPUDebugger, &QAction::triggered, this, &MainWindow::openCPUDebugger);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableGDBServer, "Debug", "EnableGDBServer", false);
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
connect(m_ui.actionOpenTextureDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenTextureDirectoryTriggered);
connect(m_ui.actionReloadTextureReplacements, &QAction::triggered, g_emu_thread, &EmuThread::reloadTextureReplacements);
connect(m_ui.actionOpenTextureDirectory, &QAction::triggered, this,
&MainWindow::onToolsOpenTextureDirectoryTriggered);
connect(m_ui.actionReloadTextureReplacements, &QAction::triggered, g_emu_thread,
&EmuThread::reloadTextureReplacements);
connect(m_ui.actionMergeDiscSets, &QAction::triggered, m_game_list_widget, &GameListWidget::setMergeDiscSets);
connect(m_ui.actionShowGameIcons, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowGameIcons);
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
@ -2336,6 +2305,25 @@ void MainWindow::doSettings(const char* category /* = nullptr */)
dlg->setCategory(category);
}
void MainWindow::openGamePropertiesForCurrentGame(const char* category /* = nullptr */)
{
if (!s_system_valid)
return;
Host::RunOnCPUThread([category]() {
const std::string& path = System::GetDiscPath();
const std::string& serial = System::GetGameSerial();
if (path.empty() || serial.empty())
return;
QtHost::RunOnUIThread([title = std::string(System::GetGameTitle()), path = std::string(path),
serial = std::string(serial), hash = System::GetGameHash(), region = System::GetDiscRegion(),
category]() {
SettingsWindow::openGamePropertiesDialog(path, title, std::move(serial), hash, region, category);
});
});
}
ControllerSettingsWindow* MainWindow::getControllerSettingsWindow()
{
if (!m_controller_settings_window)
@ -2710,7 +2698,6 @@ void MainWindow::onAchievementsChallengeModeChanged(bool enabled)
{
if (enabled)
{
QtUtils::CloseAndDeleteWindow(m_cheat_manager_window);
QtUtils::CloseAndDeleteWindow(m_debugger_window);
QtUtils::CloseAndDeleteWindow(m_memory_scanner_window);
}
@ -2782,23 +2769,6 @@ void MainWindow::onToolsMemoryScannerTriggered()
QtUtils::ShowOrRaiseWindow(m_memory_scanner_window);
}
void MainWindow::openCheatManager()
{
if (Achievements::IsHardcoreModeActive())
return;
if (!m_cheat_manager_window)
{
m_cheat_manager_window = new CheatManagerWindow();
connect(m_cheat_manager_window, &CheatManagerWindow::closed, this, [this]() {
m_cheat_manager_window->deleteLater();
m_cheat_manager_window = nullptr;
});
}
QtUtils::ShowOrRaiseWindow(m_cheat_manager_window);
}
void MainWindow::openCPUDebugger()
{
if (!m_debugger_window)

View File

@ -29,7 +29,6 @@ class GameListWidget;
class EmuThread;
class AutoUpdaterDialog;
class MemoryCardEditorWindow;
class CheatManagerWindow;
class DebuggerWindow;
class MemoryScannerWindow;
@ -96,9 +95,6 @@ public:
ALWAYS_INLINE QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
ALWAYS_INLINE QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }
/// Accessors for child windows.
CheatManagerWindow* getCheatManagerWindow() const { return m_cheat_manager_window; }
/// Opens the editor for a specific input profile.
void openInputProfileEditor(const std::string_view name);
@ -167,7 +163,6 @@ private Q_SLOTS:
void onViewGameListActionTriggered();
void onViewGameGridActionTriggered();
void onViewSystemDisplayTriggered();
void onViewGamePropertiesActionTriggered();
void onGitHubRepositoryActionTriggered();
void onIssueTrackerActionTriggered();
void onDiscordServerActionTriggered();
@ -189,7 +184,6 @@ private Q_SLOTS:
void onUpdateCheckComplete();
void openCheatManager();
void openCPUDebugger();
protected:
@ -242,6 +236,7 @@ private:
SettingsWindow* getSettingsWindow();
void doSettings(const char* category = nullptr);
void openGamePropertiesForCurrentGame(const char* category = nullptr);
ControllerSettingsWindow* getControllerSettingsWindow();
void doControllerSettings(ControllerSettingsWindow::Category category = ControllerSettingsWindow::Category::Count);
@ -300,7 +295,6 @@ private:
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
MemoryCardEditorWindow* m_memory_card_editor_window = nullptr;
CheatManagerWindow* m_cheat_manager_window = nullptr;
DebuggerWindow* m_debugger_window = nullptr;
MemoryScannerWindow* m_memory_scanner_window = nullptr;

View File

@ -5,7 +5,7 @@
#include "ui_memoryscannerwindow.h"
#include "core/cheats.h"
#include "core/memory_scanner.h"
#include <QtCore/QTimer>
#include <QtWidgets/QComboBox>

View File

@ -1293,28 +1293,34 @@ void EmuThread::changeDiscFromPlaylist(quint32 index)
errorReported(tr("Error"), tr("Failed to switch to subimage %1").arg(index));
}
void EmuThread::setCheatEnabled(quint32 index, bool enabled)
void EmuThread::reloadPatches(bool reload_files, bool reload_enabled_list, bool verbose, bool verbose_if_changed)
{
if (!isOnThread())
{
QMetaObject::invokeMethod(this, "setCheatEnabled", Qt::QueuedConnection, Q_ARG(quint32, index),
Q_ARG(bool, enabled));
QMetaObject::invokeMethod(this, "reloadPatches", Qt::QueuedConnection, Q_ARG(bool, reload_files),
Q_ARG(bool, reload_enabled_list), Q_ARG(bool, verbose), Q_ARG(bool, verbose_if_changed));
return;
}
System::SetCheatCodeState(index, enabled);
emit cheatEnabled(index, enabled);
if (System::IsValid())
{
// If the reloaded list is being enabled, we also need to reload the gameini file.
if (reload_enabled_list)
System::ReloadGameSettings(verbose);
Cheats::ReloadCheats(reload_files, reload_enabled_list, verbose, verbose_if_changed);
}
}
void EmuThread::applyCheat(quint32 index)
void EmuThread::applyCheat(const QString& name)
{
if (!isOnThread())
{
QMetaObject::invokeMethod(this, "applyCheat", Qt::QueuedConnection, Q_ARG(quint32, index));
QMetaObject::invokeMethod(this, "applyCheat", Qt::QueuedConnection, Q_ARG(const QString&, name));
return;
}
System::ApplyCheatCode(index);
if (System::IsValid())
Cheats::ApplyManualCode(name.toStdString());
}
void EmuThread::reloadPostProcessingShaders()

View File

@ -158,6 +158,7 @@ public Q_SLOTS:
void setDefaultSettings(bool system = true, bool controller = true);
void applySettings(bool display_osd_messages = false);
void reloadGameSettings(bool display_osd_messages = false);
void reloadPatches(bool reload_files, bool reload_enabled_list, bool verbose, bool verbose_if_changed);
void updateEmuFolders();
void updateControllerSettings();
void reloadInputSources();
@ -194,8 +195,7 @@ public Q_SLOTS:
void setFullscreen(bool fullscreen, bool allow_render_to_main);
void setSurfaceless(bool surfaceless);
void requestDisplaySize(float scale);
void setCheatEnabled(quint32 index, bool enabled);
void applyCheat(quint32 index);
void applyCheat(const QString& name);
void reloadPostProcessingShaders();
void updatePostProcessingSettings();
void clearInputBindStateFromSource(InputBindingKey key);

View File

@ -33,6 +33,7 @@
<file>icons/black/svg/artboard-2-line.svg</file>
<file>icons/black/svg/cheats-line.svg</file>
<file>icons/black/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/black/svg/chat-off-line.svg</file>
<file>icons/black/svg/chip-2-line.svg</file>
<file>icons/black/svg/chip-line.svg</file>
<file>icons/black/svg/close-line.svg</file>
@ -98,6 +99,7 @@
<file>icons/black/svg/settings-3-line.svg</file>
<file>icons/black/svg/shut-down-line.svg</file>
<file>icons/black/svg/sparkle-fill.svg</file>
<file>icons/black/svg/sparkling-line.svg</file>
<file>icons/black/svg/sun-fill.svg</file>
<file>icons/black/svg/trash-fill.svg</file>
<file>icons/black/svg/trophy-line.svg</file>
@ -263,6 +265,7 @@
<file>icons/white/svg/artboard-2-line.svg</file>
<file>icons/white/svg/cheats-line.svg</file>
<file>icons/white/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/white/svg/chat-off-line.svg</file>
<file>icons/white/svg/chip-2-line.svg</file>
<file>icons/white/svg/chip-line.svg</file>
<file>icons/white/svg/close-line.svg</file>
@ -328,6 +331,7 @@
<file>icons/white/svg/settings-3-line.svg</file>
<file>icons/white/svg/shut-down-line.svg</file>
<file>icons/white/svg/sparkle-fill.svg</file>
<file>icons/white/svg/sparkling-line.svg</file>
<file>icons/white/svg/sun-fill.svg</file>
<file>icons/white/svg/trash-fill.svg</file>
<file>icons/white/svg/trophy-line.svg</file>

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000">
<path d="M2.80777 1.3934L22.6068 21.1924L21.1925 22.6066L17.5846 18.9994L6.45516 19L2.00016 22.5V4C2.00016 3.8307 2.04223 3.67123 2.11649 3.53146L1.39355 2.80762L2.80777 1.3934ZM3.99955 5.4134L4.00016 18.3853L5.76349 17L15.5846 16.9994L3.99955 5.4134ZM21.0002 3C21.5524 3 22.0002 3.44772 22.0002 4V17.785L20.0002 15.785V5L9.21316 4.999L7.21416 3H21.0002Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 447 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000">
<path d="M14 4.4375C15.3462 4.4375 16.4375 3.34619 16.4375 2H17.5625C17.5625 3.34619 18.6538 4.4375 20 4.4375V5.5625C18.6538 5.5625 17.5625 6.65381 17.5625 8H16.4375C16.4375 6.65381 15.3462 5.5625 14 5.5625V4.4375ZM1 11C4.31371 11 7 8.31371 7 5H9C9 8.31371 11.6863 11 15 11V13C11.6863 13 9 15.6863 9 19H7C7 15.6863 4.31371 13 1 13V11ZM4.87601 12C6.18717 12.7276 7.27243 13.8128 8 15.124 8.72757 13.8128 9.81283 12.7276 11.124 12 9.81283 11.2724 8.72757 10.1872 8 8.87601 7.27243 10.1872 6.18717 11.2724 4.87601 12ZM17.25 14C17.25 15.7949 15.7949 17.25 14 17.25V18.75C15.7949 18.75 17.25 20.2051 17.25 22H18.75C18.75 20.2051 20.2051 18.75 22 18.75V17.25C20.2051 17.25 18.75 15.7949 18.75 14H17.25Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 789 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ffffff">
<path d="M2.80777 1.3934L22.6068 21.1924L21.1925 22.6066L17.5846 18.9994L6.45516 19L2.00016 22.5V4C2.00016 3.8307 2.04223 3.67123 2.11649 3.53146L1.39355 2.80762L2.80777 1.3934ZM3.99955 5.4134L4.00016 18.3853L5.76349 17L15.5846 16.9994L3.99955 5.4134ZM21.0002 3C21.5524 3 22.0002 3.44772 22.0002 4V17.785L20.0002 15.785V5L9.21316 4.999L7.21416 3H21.0002Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 447 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ffffff">
<path d="M14 4.4375C15.3462 4.4375 16.4375 3.34619 16.4375 2H17.5625C17.5625 3.34619 18.6538 4.4375 20 4.4375V5.5625C18.6538 5.5625 17.5625 6.65381 17.5625 8H16.4375C16.4375 6.65381 15.3462 5.5625 14 5.5625V4.4375ZM1 11C4.31371 11 7 8.31371 7 5H9C9 8.31371 11.6863 11 15 11V13C11.6863 13 9 15.6863 9 19H7C7 15.6863 4.31371 13 1 13V11ZM4.87601 12C6.18717 12.7276 7.27243 13.8128 8 15.124 8.72757 13.8128 9.81283 12.7276 11.124 12 9.81283 11.2724 8.72757 10.1872 8 8.87601 7.27243 10.1872 6.18717 11.2724 4.87601 12ZM17.25 14C17.25 15.7949 15.7949 17.25 14 17.25V18.75C15.7949 18.75 17.25 20.2051 17.25 22H18.75C18.75 20.2051 20.2051 18.75 22 18.75V17.25C20.2051 17.25 18.75 15.7949 18.75 14H17.25Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 789 B

View File

@ -9,7 +9,9 @@
#include "consolesettingswidget.h"
#include "emulationsettingswidget.h"
#include "foldersettingswidget.h"
#include "gamecheatsettingswidget.h"
#include "gamelistsettingswidget.h"
#include "gamepatchsettingswidget.h"
#include "gamesummarywidget.h"
#include "graphicssettingswidget.h"
#include "interfacesettingswidget.h"
@ -46,9 +48,9 @@ SettingsWindow::SettingsWindow() : QWidget()
connectUi();
}
SettingsWindow::SettingsWindow(const std::string& path, const std::string& serial, DiscRegion region,
SettingsWindow::SettingsWindow(const std::string& path, const std::string& serial, GameHash hash, DiscRegion region,
const GameDatabase::Entry* entry, std::unique_ptr<INISettingsInterface> sif)
: QWidget(), m_sif(std::move(sif)), m_database_entry(entry)
: QWidget(), m_sif(std::move(sif)), m_database_entry(entry), m_serial(serial), m_hash(hash)
{
m_ui.setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@ -108,6 +110,18 @@ void SettingsWindow::addPages()
QStringLiteral("emulation-line"),
tr("<strong>Emulation Settings</strong><hr>These options determine the speed and runahead behavior of the "
"system.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
if (isPerGameSettings())
{
addWidget(m_game_patch_settings_widget = new GamePatchSettingsWidget(this, m_ui.settingsContainer), tr("Patches"),
QStringLiteral("sparkling-line"),
tr("<strong>Patches</strong><hr>This section allows you to select optional patches to apply to the game, "
"which may provide performance, visual, or gameplay improvements."));
addWidget(m_game_cheat_settings_widget = new GameCheatSettingsWidget(this, m_ui.settingsContainer), tr("Cheats"),
QStringLiteral("cheats-line"),
tr("<strong>Cheats</strong><hr>This section allows you to select which cheats you wish to enable."));
}
addWidget(
m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"),
QStringLiteral("memcard-line"),
@ -635,8 +649,9 @@ bool SettingsWindow::hasGameTrait(GameDatabase::Trait trait)
m_sif->GetBoolValue("Main", "ApplyCompatibilitySettings", true));
}
void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std::string& title,
const std::string& serial, DiscRegion region)
SettingsWindow* SettingsWindow::openGamePropertiesDialog(const std::string& path, const std::string& title,
std::string serial, GameHash hash, DiscRegion region,
const char* category /* = nullptr */)
{
const GameDatabase::Entry* dentry = nullptr;
if (!System::IsExeFileName(path) && !System::IsPsfFileName(path))
@ -669,7 +684,9 @@ void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std
dialog->raise();
dialog->activateWindow();
dialog->setFocus();
return;
if (category)
dialog->setCategory(category);
return dialog;
}
}
@ -677,8 +694,11 @@ void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std
if (FileSystem::FileExists(sif->GetFileName().c_str()))
sif->Load();
SettingsWindow* dialog = new SettingsWindow(path, real_serial, region, dentry, std::move(sif));
SettingsWindow* dialog = new SettingsWindow(path, real_serial, hash, region, dentry, std::move(sif));
dialog->show();
if (category)
dialog->setCategory(category);
return dialog;
}
void SettingsWindow::closeGamePropertiesDialogs()

View File

@ -6,12 +6,13 @@
#include "util/ini_settings_interface.h"
#include "common/types.h"
#include "core/types.h"
#include <QtCore/QMap>
#include <QtCore/QString>
#include <QtWidgets/QDialog>
#include <array>
#include <optional>
class QWheelEvent;
@ -25,6 +26,8 @@ struct Entry;
class InterfaceSettingsWidget;
class BIOSSettingsWidget;
class GameListSettingsWidget;
class GamePatchSettingsWidget;
class GameCheatSettingsWidget;
class ConsoleSettingsWidget;
class EmulationSettingsWidget;
class MemoryCardSettingsWidget;
@ -41,12 +44,12 @@ class SettingsWindow final : public QWidget
public:
SettingsWindow();
SettingsWindow(const std::string& path, const std::string& serial, DiscRegion region,
SettingsWindow(const std::string& path, const std::string& serial, GameHash hash, DiscRegion region,
const GameDatabase::Entry* entry, std::unique_ptr<INISettingsInterface> sif);
~SettingsWindow();
static void openGamePropertiesDialog(const std::string& path, const std::string& title, const std::string& serial,
DiscRegion region);
static SettingsWindow* openGamePropertiesDialog(const std::string& path, const std::string& title, std::string serial,
GameHash hash, DiscRegion region, const char* category = nullptr);
static void closeGamePropertiesDialogs();
// Helper for externally setting fields in game settings ini.
@ -56,6 +59,8 @@ public:
ALWAYS_INLINE bool isPerGameSettings() const { return static_cast<bool>(m_sif); }
ALWAYS_INLINE INISettingsInterface* getSettingsInterface() const { return m_sif.get(); }
ALWAYS_INLINE const std::string& getGameSerial() const { return m_serial; }
ALWAYS_INLINE const std::optional<GameHash>& getGameHash() const { return m_hash; }
ALWAYS_INLINE InterfaceSettingsWidget* getInterfaceSettingsWidget() const { return m_interface_settings; }
ALWAYS_INLINE BIOSSettingsWidget* getBIOSSettingsWidget() const { return m_bios_settings; }
@ -93,7 +98,6 @@ public:
bool containsSettingValue(const char* section, const char* key) const;
void removeSettingValue(const char* section, const char* key);
void saveAndReloadGameSettings();
void reloadGameSettingsFromIni();
bool hasGameTrait(GameDatabase::Trait trait);
@ -117,7 +121,7 @@ protected:
private:
enum : u32
{
MAX_SETTINGS_WIDGETS = 12
MAX_SETTINGS_WIDGETS = 13
};
void connectUi();
@ -137,6 +141,8 @@ private:
ConsoleSettingsWidget* m_console_settings = nullptr;
EmulationSettingsWidget* m_emulation_settings = nullptr;
GameListSettingsWidget* m_game_list_settings = nullptr;
GamePatchSettingsWidget* m_game_patch_settings_widget = nullptr;
GameCheatSettingsWidget* m_game_cheat_settings_widget = nullptr;
MemoryCardSettingsWidget* m_memory_card_settings = nullptr;
GraphicsSettingsWidget* m_graphics_settings = nullptr;
PostProcessingSettingsWidget* m_post_processing_settings = nullptr;
@ -150,5 +156,6 @@ private:
QObject* m_current_help_widget = nullptr;
QMap<QObject*, QString> m_widget_help_text_map;
std::string m_game_list_filename;
std::string m_serial;
std::optional<GameHash> m_hash;
};

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
static constexpr ImWchar FA_ICON_RANGE[] = { 0xe06f,0xe06f,0xe086,0xe086,0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf01c,0xf01c,0xf021,0xf021,0xf023,0xf023,0xf025,0xf025,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03d,0xf04a,0xf04c,0xf050,0xf050,0xf05e,0xf05e,0xf062,0xf063,0xf067,0xf067,0xf071,0xf071,0xf075,0xf075,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf085,0xf091,0xf091,0xf0a0,0xf0a0,0xf0ac,0xf0ad,0xf0c5,0xf0c5,0xf0c7,0xf0c9,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0e7,0xf0e7,0xf0eb,0xf0eb,0xf0f1,0xf0f1,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf110,0xf110,0xf119,0xf119,0xf11b,0xf11c,0xf140,0xf140,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf191,0xf192,0xf1ab,0xf1ab,0xf1dd,0xf1de,0xf1e6,0xf1e6,0xf1eb,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf240,0xf240,0xf242,0xf242,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2d0,0xf2d0,0xf2db,0xf2db,0xf2f2,0xf2f2,0xf3fd,0xf3fd,0xf410,0xf410,0xf466,0xf466,0xf4ce,0xf4ce,0xf500,0xf500,0xf51f,0xf51f,0xf538,0xf538,0xf545,0xf545,0xf547,0xf548,0xf57a,0xf57a,0xf5a2,0xf5a2,0xf5aa,0xf5aa,0xf5e7,0xf5e7,0xf65d,0xf65e,0xf6cf,0xf6cf,0xf70c,0xf70c,0xf794,0xf794,0xf7a0,0xf7a0,0xf7c2,0xf7c2,0xf807,0xf807,0xf815,0xf815,0xf818,0xf818,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 };
static constexpr ImWchar FA_ICON_RANGE[] = { 0xe06f,0xe06f,0xe086,0xe086,0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf01c,0xf01c,0xf021,0xf021,0xf023,0xf023,0xf025,0xf025,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03d,0xf04a,0xf04c,0xf050,0xf050,0xf05e,0xf05e,0xf062,0xf063,0xf067,0xf067,0xf071,0xf071,0xf075,0xf075,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf085,0xf091,0xf091,0xf0a0,0xf0a0,0xf0ac,0xf0ad,0xf0c5,0xf0c5,0xf0c7,0xf0c9,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0e7,0xf0e7,0xf0eb,0xf0eb,0xf0f1,0xf0f1,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf110,0xf110,0xf119,0xf119,0xf11b,0xf11c,0xf140,0xf140,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf191,0xf192,0xf1ab,0xf1ab,0xf1dd,0xf1de,0xf1e6,0xf1e6,0xf1eb,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf240,0xf240,0xf242,0xf242,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2d0,0xf2d0,0xf2db,0xf2db,0xf2f2,0xf2f2,0xf3fd,0xf3fd,0xf410,0xf410,0xf462,0xf462,0xf466,0xf466,0xf4ce,0xf4ce,0xf500,0xf500,0xf51f,0xf51f,0xf538,0xf538,0xf545,0xf545,0xf547,0xf548,0xf57a,0xf57a,0xf5a2,0xf5a2,0xf5aa,0xf5aa,0xf5e7,0xf5e7,0xf65d,0xf65e,0xf6cf,0xf6cf,0xf70c,0xf70c,0xf794,0xf794,0xf7a0,0xf7a0,0xf7c2,0xf7c2,0xf807,0xf807,0xf815,0xf815,0xf818,0xf818,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 };
static constexpr ImWchar PF_ICON_RANGE[] = { 0x2196,0x2199,0x219e,0x21a1,0x21b0,0x21b3,0x21ba,0x21c3,0x21c7,0x21ca,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21ed,0x21ee,0x21f7,0x21f8,0x21fa,0x21fb,0x227a,0x227f,0x2284,0x2284,0x2349,0x2349,0x235e,0x235e,0x2360,0x2361,0x2364,0x2366,0x23b2,0x23b4,0x23ce,0x23ce,0x23f4,0x23f7,0x2427,0x243a,0x243c,0x243e,0x2460,0x246b,0x24f5,0x24fd,0x24ff,0x24ff,0x2717,0x2717,0x278a,0x278e,0x27fc,0x27fc,0xe001,0xe001,0xff21,0xff3a,0x1f52b,0x1f52b,0x0,0x0 };