Cheats: Rewrite cheat management system
|
@ -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
|
||||
|
@ -136,7 +138,7 @@ target_precompile_headers(core PRIVATE "pch.h")
|
|||
target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
target_link_libraries(core PUBLIC Threads::Threads common util)
|
||||
target_link_libraries(core PRIVATE xxhash imgui rapidyaml rcheevos cpuinfo::cpuinfo ZLIB::ZLIB Zstd::Zstd)
|
||||
target_link_libraries(core PRIVATE xxhash imgui minizip rapidyaml rcheevos cpuinfo::cpuinfo ZLIB::ZLIB Zstd::Zstd)
|
||||
|
||||
if(CPU_ARCH_X64)
|
||||
target_compile_definitions(core PUBLIC "ENABLE_RECOMPILER=1" "ENABLE_NEWREC=1" "ENABLE_MMAP_FASTMEM=1")
|
||||
|
|
3259
src/core/cheats.cpp
|
@ -7,316 +7,145 @@
|
|||
|
||||
#include "types.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#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,
|
||||
using CodeOption = std::pair<std::string, u32>;
|
||||
using CodeOptionList = std::vector<CodeOption>;
|
||||
|
||||
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;
|
||||
CodeOptionList options;
|
||||
u16 option_range_start = 0;
|
||||
u16 option_range_end = 0;
|
||||
u32 file_offset_start = 0;
|
||||
u32 file_offset_body_start = 0;
|
||||
u32 file_offset_end = 0;
|
||||
CodeType type = CodeType::Gameshark;
|
||||
CodeActivation activation = CodeActivation::EndFrame;
|
||||
bool from_database = false;
|
||||
|
||||
ALWAYS_INLINE bool Valid() const { return !instructions.empty() && !description.empty(); }
|
||||
ALWAYS_INLINE bool IsManuallyActivated() const { return (activation == Activation::Manual); }
|
||||
std::string_view GetNamePart() const;
|
||||
std::string_view GetNameParentPart() const;
|
||||
|
||||
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);
|
||||
bool HasOptionChoices() const { return (!options.empty()); }
|
||||
bool HasOptionRange() const { return (option_range_end > option_range_start); }
|
||||
std::string_view MapOptionValueToName(u32 value) const;
|
||||
std::string_view MapOptionValueToName(const std::string_view value) const;
|
||||
u32 MapOptionNameToValue(const std::string_view opt_name) 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 sort_by_name);
|
||||
|
||||
bool LoadFromFile(const char* filename, Format format);
|
||||
bool LoadFromPCSXRFile(const char* filename);
|
||||
bool LoadFromLibretroFile(const char* filename);
|
||||
/// Returns a list of all unique prefixes/groups for a cheat list.
|
||||
extern std::vector<std::string_view> GetCodeListUniquePrefixes(const CodeInfoList& list, bool include_empty);
|
||||
|
||||
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 const CodeInfo* FindCodeInInfoList(const CodeInfoList& list, const std::string_view name);
|
||||
|
||||
bool SaveToPCSXRFile(const char* filename);
|
||||
/// Searches for a given code by name.
|
||||
extern CodeInfo* FindCodeInInfoList(CodeInfoList& list, const std::string_view name);
|
||||
|
||||
bool LoadFromPackage(const std::string& serial);
|
||||
/// 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);
|
||||
|
||||
void Apply();
|
||||
/// Exports codes to the given file, in DuckStation format.
|
||||
extern bool ExportCodesToFile(std::string path, const CodeInfoList& codes, Error* error);
|
||||
|
||||
void ApplyCode(u32 index);
|
||||
/// Adds, updates, or removes the specified code from the file, rewriting it. If code is null, it will be removed.
|
||||
extern bool UpdateCodeInFile(const char* path, const std::string_view name, 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 setting changes based on patches.
|
||||
extern void ApplySettingOverrides();
|
||||
|
||||
MemoryScan();
|
||||
~MemoryScan();
|
||||
/// Applies all currently-registered frame end cheat codes.
|
||||
extern void ApplyFrameEndCodes();
|
||||
|
||||
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()); }
|
||||
/// Returns true if cheats are enabled in the current game's configuration.
|
||||
extern bool AreCheatsEnabled();
|
||||
|
||||
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; }
|
||||
/// Enumerates the names of all manually-activated codes.
|
||||
extern bool EnumerateManualCodes(std::function<bool(const std::string& name)> callback);
|
||||
|
||||
void ResetSearch();
|
||||
void Search();
|
||||
void SearchAgain();
|
||||
void UpdateResultsValues();
|
||||
/// Invokes/applies the specified manually-activated code.
|
||||
extern bool ApplyManualCode(const std::string_view name);
|
||||
|
||||
void SetResultValue(u32 index, u32 value);
|
||||
// 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;
|
||||
|
||||
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
|
||||
|
|
|
@ -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" />
|
||||
|
@ -170,6 +172,9 @@
|
|||
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
||||
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\minizip\minizip.vcxproj">
|
||||
<Project>{8bda439c-6358-45fb-9994-2ff083babe06}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\rainterface\rainterface.vcxproj" Condition="'$(Platform)'!='ARM64'">
|
||||
<Project>{e4357877-d459-45c7-b8f6-dcbb587bb528}</Project>
|
||||
</ProjectReference>
|
||||
|
@ -206,7 +211,8 @@
|
|||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions Condition="'$(Platform)'=='x64' And $(Configuration.Contains('Debug'))">ZYDIS_DISABLE_ENCODER;ZYDIS_DISABLE_AVX512;ZYDIS_DISABLE_KNC;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='x64' And $(Configuration.Contains('Debug'))">$(SolutionDir)dep\zydis\include;$(SolutionDir)dep\zydis\dependencies\zycore\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='x64' And $(Configuration.Contains('Debug'))">%(AdditionalIncludeDirectories);$(SolutionDir)dep\zydis\include;$(SolutionDir)dep\zydis\dependencies\zycore\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\minizip\include</AdditionalIncludeDirectories>
|
||||
<ObjectFileName>$(IntDir)/%(RelativeDir)/</ObjectFileName>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -197,6 +197,8 @@ enum class SettingsPage
|
|||
Audio,
|
||||
Achievements,
|
||||
Advanced,
|
||||
Patches,
|
||||
Cheats,
|
||||
Count
|
||||
};
|
||||
|
||||
|
@ -276,7 +278,6 @@ static void DoChangeDisc();
|
|||
static void DoRequestExit();
|
||||
static void DoDesktopMode();
|
||||
static void DoToggleFullscreen();
|
||||
static void DoCheatsMenu();
|
||||
static void DoToggleAnalogMode();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -304,6 +305,7 @@ static void DrawControllerSettingsPage();
|
|||
static void DrawHotkeySettingsPage();
|
||||
static void DrawAchievementsSettingsPage();
|
||||
static void DrawAdvancedSettingsPage();
|
||||
static void DrawPatchesOrCheatsSettingsPage(bool cheats);
|
||||
|
||||
static bool IsEditingGameSettings(SettingsInterface* bsi);
|
||||
static SettingsInterface* GetEditingSettingsInterface();
|
||||
|
@ -386,6 +388,7 @@ static void DrawFolderSetting(SettingsInterface* bsi, const char* title, const c
|
|||
|
||||
static void PopulateGraphicsAdapterList();
|
||||
static void PopulateGameListDirectoryCache(SettingsInterface* si);
|
||||
static void PopulatePatchesAndCheatsList(const std::string_view serial);
|
||||
static void PopulatePostProcessingChain(SettingsInterface* si, const char* section);
|
||||
static void BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::Type type, std::string_view section,
|
||||
std::string_view key, std::string_view display_name);
|
||||
|
@ -402,6 +405,11 @@ static std::unique_ptr<GameList::Entry> s_game_settings_entry;
|
|||
static std::vector<std::pair<std::string, bool>> s_game_list_directories_cache;
|
||||
static GPUDevice::AdapterInfoList s_graphics_adapter_list_cache;
|
||||
static std::vector<std::string> s_fullscreen_mode_list_cache;
|
||||
static Cheats::CodeInfoList s_game_patch_list;
|
||||
static std::vector<std::string> s_enabled_game_patch_cache;
|
||||
static Cheats::CodeInfoList s_game_cheats_list;
|
||||
static std::vector<std::string> s_enabled_game_cheat_cache;
|
||||
static std::vector<std::string_view> s_game_cheat_groups;
|
||||
static std::vector<PostProcessingStageInfo> s_postprocessing_stages;
|
||||
static std::vector<const HotkeyInfo*> s_hotkey_list_cache;
|
||||
static std::atomic_bool s_settings_changed{false};
|
||||
|
@ -754,6 +762,11 @@ void FullscreenUI::Shutdown()
|
|||
std::memset(s_controller_macro_expanded, 0, sizeof(s_controller_macro_expanded));
|
||||
s_game_list_sorted_entries = {};
|
||||
s_game_list_directories_cache = {};
|
||||
s_game_patch_list = {};
|
||||
s_enabled_game_patch_cache = {};
|
||||
s_game_cheats_list = {};
|
||||
s_enabled_game_cheat_cache = {};
|
||||
s_game_cheat_groups = {};
|
||||
s_postprocessing_stages = {};
|
||||
s_fullscreen_mode_list_cache = {};
|
||||
s_graphics_adapter_list_cache = {};
|
||||
|
@ -1211,48 +1224,6 @@ void FullscreenUI::DoChangeDisc()
|
|||
DoChangeDiscFromFile();
|
||||
}
|
||||
|
||||
void FullscreenUI::DoCheatsMenu()
|
||||
{
|
||||
CheatList* cl = System::GetCheatList();
|
||||
if (!cl)
|
||||
{
|
||||
if (!System::LoadCheatListFromDatabase() || ((cl = System::GetCheatList()) == nullptr))
|
||||
{
|
||||
Host::AddKeyedOSDMessage("load_cheat_list",
|
||||
fmt::format(FSUI_FSTR("No cheats found for {}."), System::GetGameTitle()), 10.0f);
|
||||
ReturnToPreviousWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiFullscreen::ChoiceDialogOptions options;
|
||||
options.reserve(cl->GetCodeCount());
|
||||
for (u32 i = 0; i < cl->GetCodeCount(); i++)
|
||||
{
|
||||
const CheatCode& cc = cl->GetCode(i);
|
||||
options.emplace_back(cc.description.c_str(), cc.enabled);
|
||||
}
|
||||
|
||||
auto callback = [](s32 index, const std::string& title, bool checked) {
|
||||
if (index < 0)
|
||||
{
|
||||
ReturnToPreviousWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
CheatList* cl = System::GetCheatList();
|
||||
if (!cl)
|
||||
return;
|
||||
|
||||
const CheatCode& cc = cl->GetCode(static_cast<u32>(index));
|
||||
if (cc.activation == CheatCode::Activation::Manual)
|
||||
cl->ApplyCode(static_cast<u32>(index));
|
||||
else
|
||||
System::SetCheatCodeState(static_cast<u32>(index), checked);
|
||||
};
|
||||
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_FROWN, "Cheat List"), true, std::move(options), std::move(callback));
|
||||
}
|
||||
|
||||
void FullscreenUI::DoToggleAnalogMode()
|
||||
{
|
||||
// hacky way to toggle analog mode
|
||||
|
@ -2728,6 +2699,11 @@ void FullscreenUI::SwitchToSettings()
|
|||
{
|
||||
s_game_settings_entry.reset();
|
||||
s_game_settings_interface.reset();
|
||||
s_game_patch_list = {};
|
||||
s_enabled_game_patch_cache = {};
|
||||
s_game_cheats_list = {};
|
||||
s_enabled_game_cheat_cache = {};
|
||||
s_game_cheat_groups = {};
|
||||
|
||||
PopulateGraphicsAdapterList();
|
||||
PopulatePostProcessingChain(GetEditingSettingsInterface(), PostProcessing::Config::DISPLAY_CHAIN_SECTION);
|
||||
|
@ -2741,6 +2717,7 @@ void FullscreenUI::SwitchToGameSettingsForSerial(std::string_view serial)
|
|||
s_game_settings_entry.reset();
|
||||
s_game_settings_interface = std::make_unique<INISettingsInterface>(System::GetGameSettingsPath(serial));
|
||||
s_game_settings_interface->Load();
|
||||
PopulatePatchesAndCheatsList(serial);
|
||||
s_current_main_window = MainWindowType::Settings;
|
||||
s_settings_page = SettingsPage::Summary;
|
||||
QueueResetFocus(FocusResetType::ViewChanged);
|
||||
|
@ -2796,6 +2773,19 @@ void FullscreenUI::PopulateGameListDirectoryCache(SettingsInterface* si)
|
|||
s_game_list_directories_cache.emplace_back(std::move(dir), true);
|
||||
}
|
||||
|
||||
void FullscreenUI::PopulatePatchesAndCheatsList(const std::string_view serial)
|
||||
{
|
||||
s_game_patch_list = Cheats::GetCodeInfoList(serial, std::nullopt, false, true, true);
|
||||
s_game_cheats_list =
|
||||
Cheats::GetCodeInfoList(serial, std::nullopt, true,
|
||||
s_game_settings_interface->GetBoolValue("Cheats", "LoadCheatsFromDatabase", true), true);
|
||||
s_game_cheat_groups = Cheats::GetCodeListUniquePrefixes(s_game_cheats_list, true);
|
||||
s_enabled_game_patch_cache =
|
||||
s_game_settings_interface->GetStringList(Cheats::PATCHES_CONFIG_SECTION, Cheats::PATCH_ENABLE_CONFIG_KEY);
|
||||
s_enabled_game_cheat_cache =
|
||||
s_game_settings_interface->GetStringList(Cheats::CHEATS_CONFIG_SECTION, Cheats::PATCH_ENABLE_CONFIG_KEY);
|
||||
}
|
||||
|
||||
void FullscreenUI::DoCopyGameSettings()
|
||||
{
|
||||
if (!s_game_settings_interface)
|
||||
|
@ -2839,32 +2829,34 @@ void FullscreenUI::DrawSettingsWindow()
|
|||
{
|
||||
static constexpr float ITEM_WIDTH = 25.0f;
|
||||
|
||||
static constexpr const char* global_icons[] = {
|
||||
ICON_FA_TV, ICON_FA_DICE_D20, ICON_FA_COGS, ICON_PF_MICROCHIP,
|
||||
ICON_PF_PICTURE, ICON_FA_MAGIC, ICON_PF_SOUND, ICON_PF_GAMEPAD_ALT,
|
||||
ICON_PF_KEYBOARD_ALT, ICON_PF_MEMORY_CARD, ICON_FA_TROPHY, ICON_FA_EXCLAMATION_TRIANGLE};
|
||||
static constexpr const char* per_game_icons[] = {ICON_FA_PARAGRAPH, ICON_FA_HDD, ICON_FA_COGS,
|
||||
ICON_PF_PICTURE, ICON_PF_SOUND, ICON_PF_GAMEPAD_ALT,
|
||||
ICON_PF_MEMORY_CARD, ICON_FA_TROPHY, ICON_FA_EXCLAMATION_TRIANGLE};
|
||||
static constexpr SettingsPage global_pages[] = {
|
||||
static constexpr const SettingsPage global_pages[] = {
|
||||
SettingsPage::Interface, SettingsPage::Console, SettingsPage::Emulation, SettingsPage::BIOS,
|
||||
SettingsPage::Display, SettingsPage::PostProcessing, SettingsPage::Audio, SettingsPage::Controller,
|
||||
SettingsPage::Hotkey, SettingsPage::MemoryCards, SettingsPage::Achievements, SettingsPage::Advanced};
|
||||
static constexpr SettingsPage per_game_pages[] = {
|
||||
SettingsPage::Summary, SettingsPage::Console, SettingsPage::Emulation,
|
||||
SettingsPage::Display, SettingsPage::Audio, SettingsPage::Controller,
|
||||
static constexpr const SettingsPage per_game_pages[] = {
|
||||
SettingsPage::Summary, SettingsPage::Console, SettingsPage::Emulation, SettingsPage::Patches,
|
||||
SettingsPage::Cheats, SettingsPage::Display, SettingsPage::Audio, SettingsPage::Controller,
|
||||
SettingsPage::MemoryCards, SettingsPage::Achievements, SettingsPage::Advanced};
|
||||
static constexpr std::array<const char*, static_cast<u32>(SettingsPage::Count)> titles = {
|
||||
{FSUI_NSTR("Summary"), FSUI_NSTR("Interface Settings"), FSUI_NSTR("Console Settings"),
|
||||
FSUI_NSTR("Emulation Settings"), FSUI_NSTR("BIOS Settings"), FSUI_NSTR("Controller Settings"),
|
||||
FSUI_NSTR("Hotkey Settings"), FSUI_NSTR("Memory Card Settings"), FSUI_NSTR("Graphics Settings"),
|
||||
FSUI_NSTR("Post-Processing Settings"), FSUI_NSTR("Audio Settings"), FSUI_NSTR("Achievements Settings"),
|
||||
FSUI_NSTR("Advanced Settings")}};
|
||||
static constexpr std::array<std::pair<const char*, const char*>, static_cast<u32>(SettingsPage::Count)> titles = {
|
||||
{{FSUI_NSTR("Summary"), ICON_FA_PARAGRAPH},
|
||||
{FSUI_NSTR("Interface Settings"), ICON_FA_TV},
|
||||
{FSUI_NSTR("Console Settings"), ICON_FA_DICE_D20},
|
||||
{FSUI_NSTR("Emulation Settings"), ICON_FA_COGS},
|
||||
{FSUI_NSTR("BIOS Settings"), ICON_PF_MICROCHIP},
|
||||
{FSUI_NSTR("Controller Settings"), ICON_PF_GAMEPAD_ALT},
|
||||
{FSUI_NSTR("Hotkey Settings"), ICON_PF_KEYBOARD_ALT},
|
||||
{FSUI_NSTR("Memory Card Settings"), ICON_PF_MEMORY_CARD},
|
||||
{FSUI_NSTR("Graphics Settings"), ICON_PF_PICTURE},
|
||||
{FSUI_NSTR("Post-Processing Settings"), ICON_FA_MAGIC},
|
||||
{FSUI_NSTR("Audio Settings"), ICON_PF_SOUND},
|
||||
{FSUI_NSTR("Achievements Settings"), ICON_FA_TROPHY},
|
||||
{FSUI_NSTR("Advanced Settings"), ICON_FA_EXCLAMATION_TRIANGLE},
|
||||
{FSUI_NSTR("Patches"), ICON_FA_BAND_AID},
|
||||
{FSUI_NSTR("Cheats"), ICON_FA_FLASK}}};
|
||||
|
||||
const bool game_settings = IsEditingGameSettings(GetEditingSettingsInterface());
|
||||
const u32 count =
|
||||
game_settings ? static_cast<u32>(std::size(per_game_pages)) : static_cast<u32>(std::size(global_pages));
|
||||
const char* const* icons = game_settings ? per_game_icons : global_icons;
|
||||
const SettingsPage* pages = game_settings ? per_game_pages : global_pages;
|
||||
u32 index = 0;
|
||||
for (u32 i = 0; i < count; i++)
|
||||
|
@ -2903,13 +2895,14 @@ void FullscreenUI::DrawSettingsWindow()
|
|||
if (s_game_settings_entry)
|
||||
NavTitle(s_game_settings_entry->title.c_str());
|
||||
else
|
||||
NavTitle(Host::TranslateToCString(TR_CONTEXT, titles[static_cast<u32>(pages[index])]));
|
||||
NavTitle(Host::TranslateToCString(TR_CONTEXT, titles[static_cast<u32>(pages[index])].first));
|
||||
|
||||
RightAlignNavButtons(count, ITEM_WIDTH, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
if (NavButton(icons[i], i == index, true, ITEM_WIDTH, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY))
|
||||
if (NavButton(titles[static_cast<u32>(pages[i])].second, i == index, true, ITEM_WIDTH,
|
||||
LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY))
|
||||
{
|
||||
s_settings_page = pages[i];
|
||||
QueueResetFocus(FocusResetType::Other);
|
||||
|
@ -2994,6 +2987,14 @@ void FullscreenUI::DrawSettingsWindow()
|
|||
DrawAdvancedSettingsPage();
|
||||
break;
|
||||
|
||||
case SettingsPage::Patches:
|
||||
DrawPatchesOrCheatsSettingsPage(false);
|
||||
break;
|
||||
|
||||
case SettingsPage::Cheats:
|
||||
DrawPatchesOrCheatsSettingsPage(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -5220,6 +5221,186 @@ void FullscreenUI::DrawAdvancedSettingsPage()
|
|||
EndMenuButtons();
|
||||
}
|
||||
|
||||
void FullscreenUI::DrawPatchesOrCheatsSettingsPage(bool cheats)
|
||||
{
|
||||
SettingsInterface* bsi = GetEditingSettingsInterface();
|
||||
|
||||
const Cheats::CodeInfoList& code_list = cheats ? s_game_cheats_list : s_game_patch_list;
|
||||
std::vector<std::string>& enable_list = cheats ? s_enabled_game_cheat_cache : s_enabled_game_patch_cache;
|
||||
const char* section = cheats ? Cheats::CHEATS_CONFIG_SECTION : Cheats::PATCHES_CONFIG_SECTION;
|
||||
|
||||
BeginMenuButtons();
|
||||
|
||||
static constexpr auto draw_code = [](SettingsInterface* bsi, const char* section, const Cheats::CodeInfo& ci,
|
||||
std::vector<std::string>& enable_list, bool cheats) {
|
||||
const auto enable_it = std::find(enable_list.begin(), enable_list.end(), ci.name);
|
||||
|
||||
SmallString title;
|
||||
if (!cheats)
|
||||
title = std::string_view(ci.name);
|
||||
else
|
||||
title = ci.GetNamePart();
|
||||
|
||||
// TODO: Handle ranges.
|
||||
bool state = (enable_it != enable_list.end());
|
||||
|
||||
if (ci.HasOptionChoices())
|
||||
{
|
||||
TinyString visible_value(FSUI_VSTR("Disabled"));
|
||||
bool has_option = false;
|
||||
if (state)
|
||||
{
|
||||
// Need to map the value to an option.
|
||||
visible_value = ci.MapOptionValueToName(bsi->GetTinyStringValue(section, ci.name.c_str()));
|
||||
has_option = true;
|
||||
}
|
||||
|
||||
if (MenuButtonWithValue(title.c_str(), ci.description.c_str(), visible_value))
|
||||
{
|
||||
ImGuiFullscreen::ChoiceDialogOptions options;
|
||||
options.reserve(ci.options.size() + 1);
|
||||
options.emplace_back(FSUI_VSTR("Disabled"), !has_option);
|
||||
|
||||
for (const Cheats::CodeOption& opt : ci.options)
|
||||
options.emplace_back(opt.first, has_option && (visible_value.view() == opt.first));
|
||||
|
||||
OpenChoiceDialog(ci.name, false, std::move(options),
|
||||
[cheat_name = ci.name, cheats, section](s32 index, const std::string& title, bool checked) {
|
||||
if (index >= 0)
|
||||
{
|
||||
const Cheats::CodeInfo* ci =
|
||||
Cheats::FindCodeInInfoList(cheats ? s_game_cheats_list : s_game_patch_list, cheat_name);
|
||||
if (ci)
|
||||
{
|
||||
SettingsInterface* bsi = GetEditingSettingsInterface();
|
||||
std::vector<std::string>& enable_list =
|
||||
cheats ? s_enabled_game_cheat_cache : s_enabled_game_patch_cache;
|
||||
const auto it = std::find(enable_list.begin(), enable_list.end(), ci->name);
|
||||
if (index == 0)
|
||||
{
|
||||
bsi->RemoveFromStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci->name.c_str());
|
||||
if (it != enable_list.end())
|
||||
enable_list.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
bsi->AddToStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci->name.c_str());
|
||||
bsi->SetUIntValue(section, ci->name.c_str(), ci->MapOptionNameToValue(title));
|
||||
if (it == enable_list.end())
|
||||
enable_list.push_back(std::move(cheat_name));
|
||||
}
|
||||
|
||||
SetSettingsChanged(bsi);
|
||||
}
|
||||
}
|
||||
|
||||
CloseChoiceDialog();
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool changed = ToggleButton(title.c_str(), ci.description.c_str(), &state);
|
||||
if (changed)
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
bsi->AddToStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci.name.c_str());
|
||||
enable_list.push_back(ci.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
bsi->RemoveFromStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci.name.c_str());
|
||||
enable_list.erase(enable_it);
|
||||
}
|
||||
|
||||
SetSettingsChanged(bsi);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (cheats)
|
||||
{
|
||||
ActiveButton(
|
||||
FSUI_CSTR(
|
||||
"WARNING: Activating cheats can cause unpredictable behavior, crashing, soft-locks, or broken saved games."),
|
||||
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
|
||||
MenuHeading(FSUI_CSTR("Settings"));
|
||||
|
||||
bool enable_cheats = bsi->GetBoolValue("Cheats", "EnableCheats", false);
|
||||
if (ToggleButton(FSUI_CSTR("Enable Cheats"), FSUI_CSTR("Enables the cheats that are selected below."),
|
||||
&enable_cheats))
|
||||
{
|
||||
if (enable_cheats)
|
||||
bsi->SetBoolValue("Cheats", "EnableCheats", true);
|
||||
else
|
||||
bsi->DeleteValue("Cheats", "EnableCheats");
|
||||
SetSettingsChanged(bsi);
|
||||
}
|
||||
|
||||
bool load_database_cheats = bsi->GetBoolValue("Cheats", "LoadCheatsFromDatabase", true);
|
||||
if (ToggleButton(FSUI_CSTR("Load Database Cheats"),
|
||||
FSUI_CSTR("Enables loading of cheats for this game from DuckStation's database."),
|
||||
&load_database_cheats))
|
||||
{
|
||||
if (load_database_cheats)
|
||||
bsi->DeleteValue("Cheats", "LoadCheatsFromDatabase");
|
||||
else
|
||||
bsi->SetBoolValue("Cheats", "LoadCheatsFromDatabase", false);
|
||||
SetSettingsChanged(bsi);
|
||||
if (s_game_settings_entry)
|
||||
PopulatePatchesAndCheatsList(s_game_settings_entry->serial);
|
||||
}
|
||||
|
||||
if (code_list.empty())
|
||||
{
|
||||
ActiveButton(FSUI_CSTR("No cheats are available for this game."), false, false,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const std::string_view& group : s_game_cheat_groups)
|
||||
{
|
||||
if (group.empty())
|
||||
MenuHeading(FSUI_CSTR("Ungrouped"));
|
||||
else
|
||||
MenuHeading(SmallString(group).c_str());
|
||||
|
||||
for (const Cheats::CodeInfo& ci : code_list)
|
||||
{
|
||||
if (ci.GetNameParentPart() != group)
|
||||
continue;
|
||||
|
||||
draw_code(bsi, section, ci, enable_list, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveButton(
|
||||
FSUI_CSTR("WARNING: Activating game patches can cause unpredictable behavior, crashing, soft-locks, or broken "
|
||||
"saved games."),
|
||||
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
|
||||
if (code_list.empty())
|
||||
{
|
||||
ActiveButton(FSUI_CSTR("No patches are available for this game."), false, false,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuHeading(FSUI_CSTR("Game Patches"));
|
||||
|
||||
for (const Cheats::CodeInfo& ci : code_list)
|
||||
draw_code(bsi, section, ci, enable_list, false);
|
||||
}
|
||||
}
|
||||
|
||||
EndMenuButtons();
|
||||
}
|
||||
|
||||
void FullscreenUI::DrawPauseMenu()
|
||||
{
|
||||
SmallString buffer;
|
||||
|
@ -5327,7 +5508,7 @@ void FullscreenUI::DrawPauseMenu()
|
|||
ImVec2(10.0f, 10.0f), ImGuiWindowFlags_NoBackground))
|
||||
{
|
||||
static constexpr u32 submenu_item_count[] = {
|
||||
12, // None
|
||||
11, // None
|
||||
4, // Exit
|
||||
3, // Achievements
|
||||
};
|
||||
|
@ -5365,13 +5546,6 @@ void FullscreenUI::DrawPauseMenu()
|
|||
s_current_main_window = MainWindowType::None;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (ActiveButton(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Toggle Analog"), false))
|
||||
{
|
||||
ClosePauseMenu();
|
||||
|
|
|
@ -220,7 +220,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)
|
||||
|
|
|
@ -82,7 +82,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);
|
||||
|
@ -193,7 +193,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));
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
|
@ -518,7 +517,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);
|
||||
|
@ -938,7 +936,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;
|
||||
|
@ -1051,7 +1048,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;
|
||||
|
@ -2135,6 +2131,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;
|
||||
|
@ -2154,6 +2151,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");
|
||||
|
@ -2185,6 +2183,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");
|
||||
|
@ -2201,6 +2200,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);
|
||||
|
@ -2222,6 +2222,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());
|
||||
|
@ -2262,6 +2263,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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -578,6 +577,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;
|
||||
|
|
|
@ -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);
|
||||
|
@ -1288,6 +1287,9 @@ void System::LoadSettings(bool display_osd_messages)
|
|||
entry->ApplySettings(g_settings, display_osd_messages);
|
||||
}
|
||||
|
||||
// patch overrides take precedence over compat settings
|
||||
Cheats::ApplySettingOverrides();
|
||||
|
||||
g_settings.FixIncompatibleSettings(display_osd_messages);
|
||||
}
|
||||
|
||||
|
@ -1483,6 +1485,9 @@ bool System::UpdateGameSettingsLayer()
|
|||
Host::Internal::SetInputSettingsLayer(input_interface.get(), lock);
|
||||
s_input_settings_interface = std::move(input_interface);
|
||||
s_input_profile_name = std::move(input_profile_name);
|
||||
|
||||
Cheats::ReloadCheats(false, true, false, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1496,9 +1501,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 +1716,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 +1732,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 +1770,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
|||
if (cancelled)
|
||||
{
|
||||
s_state = State::Shutdown;
|
||||
Cheats::UnloadAll();
|
||||
ClearRunningGame();
|
||||
Host::OnSystemDestroyed();
|
||||
Host::OnIdleStateChanged();
|
||||
|
@ -1776,6 +1784,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 +1796,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 +1991,7 @@ void System::DestroySystem()
|
|||
|
||||
ClearMemorySaveStates();
|
||||
|
||||
Cheats::UnloadAll();
|
||||
PCDrv::Shutdown();
|
||||
SIO::Shutdown();
|
||||
MDEC::Shutdown();
|
||||
|
@ -2013,7 +2024,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 +2095,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 +3607,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
|
||||
|
||||
|
@ -4114,12 +4096,11 @@ void System::UpdateRunningGame(const std::string_view path, CDImage* image, bool
|
|||
|
||||
Achievements::GameChanged(s_running_game_path, image);
|
||||
|
||||
// game layer reloads cheats, but only the active list, we need new files
|
||||
Cheats::ReloadCheats(true, false, false, true);
|
||||
UpdateGameSettingsLayer();
|
||||
ApplySettings(true);
|
||||
|
||||
s_cheat_list.reset();
|
||||
if (g_settings.enable_cheats)
|
||||
LoadCheatList();
|
||||
ApplySettings(true);
|
||||
|
||||
if (s_running_game_serial != prev_serial)
|
||||
UpdateSessionTime(prev_serial);
|
||||
|
@ -4248,40 +4229,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 +4337,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 +4627,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 +5526,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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ enum class MemoryAccessSize : u32
|
|||
|
||||
using TickCount = s32;
|
||||
using GlobalTicks = u64;
|
||||
using GameHash = u64;
|
||||
|
||||
enum class ConsoleRegion : u8
|
||||
{
|
||||
|
|
|
@ -29,12 +29,6 @@ set(SRCS
|
|||
biossettingswidget.cpp
|
||||
biossettingswidget.h
|
||||
biossettingswidget.ui
|
||||
cheatcodeeditordialog.cpp
|
||||
cheatcodeeditordialog.h
|
||||
cheatcodeeditordialog.ui
|
||||
cheatmanagerwindow.cpp
|
||||
cheatmanagerwindow.h
|
||||
cheatmanagerwindow.ui
|
||||
colorpickerbutton.cpp
|
||||
colorpickerbutton.h
|
||||
consolesettingswidget.cpp
|
||||
|
@ -79,6 +73,15 @@ set(SRCS
|
|||
foldersettingswidget.cpp
|
||||
foldersettingswidget.h
|
||||
foldersettingswidget.ui
|
||||
gamecheatcodechoiceeditordialog.ui
|
||||
gamecheatcodeeditordialog.ui
|
||||
gamecheatsettingswidget.cpp
|
||||
gamecheatsettingswidget.h
|
||||
gamecheatsettingswidget.ui
|
||||
gamepatchdetailswidget.ui
|
||||
gamepatchsettingswidget.cpp
|
||||
gamepatchsettingswidget.h
|
||||
gamepatchsettingswidget.ui
|
||||
gamelistmodel.cpp
|
||||
gamelistmodel.h
|
||||
gamelistrefreshthread.cpp
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "cheatcodeeditordialog.h"
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
CheatCodeEditorDialog::CheatCodeEditorDialog(const QStringList& group_names, CheatCode* code, QWidget* parent)
|
||||
: QDialog(parent), m_code(code)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
setupAdditionalUi(group_names);
|
||||
fillUi();
|
||||
connectUi();
|
||||
}
|
||||
|
||||
CheatCodeEditorDialog::~CheatCodeEditorDialog() = default;
|
||||
|
||||
void CheatCodeEditorDialog::saveClicked()
|
||||
{
|
||||
std::string new_description = m_ui.description->text().toStdString();
|
||||
if (new_description.empty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Description cannot be empty."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_code->SetInstructionsFromString(m_ui.instructions->toPlainText().toStdString()))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Instructions are invalid."));
|
||||
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();
|
||||
|
||||
done(1);
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::cancelClicked()
|
||||
{
|
||||
done(0);
|
||||
}
|
||||
|
||||
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>(CheatCode::Activation::Count); i++)
|
||||
{
|
||||
m_ui.activation->addItem(
|
||||
qApp->translate("Cheats", CheatCode::GetActivationDisplayName(static_cast<CheatCode::Activation>(i))));
|
||||
}
|
||||
|
||||
if (!group_names.isEmpty())
|
||||
m_ui.group->addItems(group_names);
|
||||
else
|
||||
m_ui.group->addItem(QStringLiteral("Ungrouped"));
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::fillUi()
|
||||
{
|
||||
m_ui.description->setText(QString::fromStdString(m_code->description));
|
||||
|
||||
const QString group_qstr(QString::fromStdString(m_code->group));
|
||||
int index = m_ui.group->findText(group_qstr);
|
||||
if (index >= 0)
|
||||
{
|
||||
m_ui.group->setCurrentIndex(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
index = m_ui.group->count();
|
||||
m_ui.group->addItem(group_qstr);
|
||||
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.instructions->setPlainText(QString::fromStdString(m_code->GetInstructionsAsString()));
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::connectUi()
|
||||
{
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &CheatCodeEditorDialog::saveClicked);
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &CheatCodeEditorDialog::cancelClicked);
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
#include "core/cheats.h"
|
||||
#include "ui_cheatcodeeditordialog.h"
|
||||
|
||||
class CheatCodeEditorDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CheatCodeEditorDialog(const QStringList& group_names, CheatCode* code, QWidget* parent);
|
||||
~CheatCodeEditorDialog();
|
||||
|
||||
private Q_SLOTS:
|
||||
void saveClicked();
|
||||
void cancelClicked();
|
||||
|
||||
private:
|
||||
void setupAdditionalUi(const QStringList& group_names);
|
||||
void fillUi();
|
||||
void connectUi();
|
||||
|
||||
CheatCode* m_code;
|
||||
|
||||
Ui::CheatCodeEditorDialog m_ui;
|
||||
};
|
|
@ -1,74 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CheatCodeEditorDialog</class>
|
||||
<widget class="QDialog" name="CheatCodeEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>491</width>
|
||||
<height>284</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Cheat Code Editor</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Description:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="description"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Group:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="group"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="type"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Activation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="activation"/>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QPlainTextEdit" name="instructions"/>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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>&Add Group...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cheatListAdd">
|
||||
<property name="text">
|
||||
<string>&Add Code...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cheatListEdit">
|
||||
<property name="text">
|
||||
<string>&Edit Code...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cheatListRemove">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&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>
|
|
@ -9,8 +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" />
|
||||
<ClCompile Include="controllerbindingwidgets.cpp" />
|
||||
|
@ -21,7 +19,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,8 +58,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" />
|
||||
<QtMoc Include="memorycardeditorwindow.h" />
|
||||
|
@ -85,6 +83,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,10 +139,7 @@
|
|||
<QtUi Include="memorycardeditorwindow.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="cheatmanagerwindow.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="cheatcodeeditordialog.ui">
|
||||
<QtUi Include="gamecheatcodeeditordialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="inputbindingdialog.ui">
|
||||
|
@ -222,8 +219,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" />
|
||||
<ClCompile Include="$(IntDir)moc_controllerbindingwidgets.cpp" />
|
||||
|
@ -233,10 +228,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 +339,18 @@
|
|||
<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>
|
||||
<QtUi Include="gamecheatcodechoiceeditordialog.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>
|
||||
|
|
|
@ -23,8 +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" />
|
||||
<ClCompile Include="memoryviewwidget.cpp" />
|
||||
|
@ -65,12 +63,6 @@
|
|||
<ClCompile Include="$(IntDir)moc_biossettingswidget.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
<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 +167,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,8 +216,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" />
|
||||
<QtMoc Include="memoryviewwidget.h" />
|
||||
|
@ -237,6 +235,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,8 +252,7 @@
|
|||
<QtUi Include="biossettingswidget.ui" />
|
||||
<QtUi Include="postprocessingchainconfigwidget.ui" />
|
||||
<QtUi Include="memorycardeditorwindow.ui" />
|
||||
<QtUi Include="cheatmanagerwindow.ui" />
|
||||
<QtUi Include="cheatcodeeditordialog.ui" />
|
||||
<QtUi Include="gamecheatcodeeditordialog.ui" />
|
||||
<QtUi Include="emulationsettingswidget.ui" />
|
||||
<QtUi Include="achievementsettingswidget.ui" />
|
||||
<QtUi Include="achievementlogindialog.ui" />
|
||||
|
@ -285,6 +284,10 @@
|
|||
<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" />
|
||||
<QtUi Include="gamecheatcodechoiceeditordialog.ui" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="duckstation-qt.rc" />
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GameCheatCodeChoiceEditorDialog</class>
|
||||
<widget class="QWidget" name="GameCheatCodeChoiceEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>231</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Cheat Choice Editor</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="optionList">
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Value</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="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,142 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GameCheatCodeEditorDialog</class>
|
||||
<widget class="QDialog" name="GameCheatCodeEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>491</width>
|
||||
<height>318</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Cheat Code Editor</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="descriptionLabel">
|
||||
<property name="text">
|
||||
<string>Description:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPlainTextEdit" name="description">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="groupLabel">
|
||||
<property name="text">
|
||||
<string>Group:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="group"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="typeLabel">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="type"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="activationLabel">
|
||||
<property name="text">
|
||||
<string>Activation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="activation"/>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QPlainTextEdit" name="instructions"/>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Options:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="optionsType">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Choice</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Range</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="rangeMin">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="rangeMax">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="editChoice">
|
||||
<property name="text">
|
||||
<string>Edit Choices...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,948 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "gamecheatsettingswidget.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 "common/string_util.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <QtCore/QSignalBlocker>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QStyledItemDelegate>
|
||||
|
||||
namespace {
|
||||
class CheatListOptionDelegate : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
CheatListOptionDelegate(GameCheatSettingsWidget* parent, QTreeWidget* treeview);
|
||||
|
||||
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
|
||||
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
|
||||
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||
|
||||
private:
|
||||
std::string getCodeNameForRow(const QModelIndex& index) const;
|
||||
const Cheats::CodeInfo* getCodeInfoForRow(const QModelIndex& index) const;
|
||||
|
||||
GameCheatSettingsWidget* m_parent;
|
||||
QTreeWidget* m_treeview;
|
||||
};
|
||||
}; // namespace
|
||||
|
||||
CheatListOptionDelegate::CheatListOptionDelegate(GameCheatSettingsWidget* parent, QTreeWidget* treeview)
|
||||
: QStyledItemDelegate(parent), m_parent(parent), m_treeview(treeview)
|
||||
{
|
||||
}
|
||||
|
||||
std::string CheatListOptionDelegate::getCodeNameForRow(const QModelIndex& index) const
|
||||
{
|
||||
return index.siblingAtColumn(0).data(Qt::UserRole).toString().toStdString();
|
||||
}
|
||||
|
||||
const Cheats::CodeInfo* CheatListOptionDelegate::getCodeInfoForRow(const QModelIndex& index) const
|
||||
{
|
||||
return m_parent->getCodeInfo(getCodeNameForRow(index));
|
||||
}
|
||||
|
||||
QWidget* CheatListOptionDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) const
|
||||
{
|
||||
// only edit the value, don't want the title becoming editable
|
||||
if (index.column() != 1)
|
||||
return nullptr;
|
||||
|
||||
const QVariant data = index.data(Qt::UserRole);
|
||||
if (data.isNull())
|
||||
return nullptr;
|
||||
|
||||
// if it's a uint, it's a range, otherwise string => combobox
|
||||
if (data.typeId() == QMetaType::QString)
|
||||
return new QComboBox(parent);
|
||||
else if (data.typeId() == QMetaType::UInt)
|
||||
return new QSpinBox(parent);
|
||||
else
|
||||
return QStyledItemDelegate::createEditor(parent, option, index);
|
||||
}
|
||||
|
||||
void CheatListOptionDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
|
||||
{
|
||||
if (index.column() != 1)
|
||||
return;
|
||||
|
||||
if (QComboBox* cb = qobject_cast<QComboBox*>(editor))
|
||||
{
|
||||
const Cheats::CodeInfo* ci = getCodeInfoForRow(index);
|
||||
if (ci)
|
||||
{
|
||||
int current_index = 0;
|
||||
const QString selected_name = index.data(Qt::UserRole).toString();
|
||||
for (const Cheats::CodeOption& opt : ci->options)
|
||||
{
|
||||
const QString name = QString::fromStdString(opt.first);
|
||||
cb->addItem(name, QVariant(static_cast<uint>(opt.second)));
|
||||
if (name == selected_name)
|
||||
cb->setCurrentIndex(current_index);
|
||||
current_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (QSpinBox* sb = qobject_cast<QSpinBox*>(editor))
|
||||
{
|
||||
const Cheats::CodeInfo* ci = getCodeInfoForRow(index);
|
||||
if (ci)
|
||||
{
|
||||
sb->setMinimum(ci->option_range_start);
|
||||
sb->setMaximum(ci->option_range_end);
|
||||
sb->setValue(index.data(Qt::UserRole).toUInt());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return QStyledItemDelegate::setEditorData(editor, index);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatListOptionDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
|
||||
{
|
||||
if (index.column() != 1)
|
||||
return;
|
||||
|
||||
if (QComboBox* cb = qobject_cast<QComboBox*>(editor))
|
||||
{
|
||||
const QString value = cb->currentText();
|
||||
const Cheats::CodeInfo* ci = getCodeInfoForRow(index);
|
||||
if (ci)
|
||||
{
|
||||
m_parent->setCodeOption(ci->name, ci->MapOptionNameToValue(value.toStdString()));
|
||||
model->setData(index, value, Qt::UserRole);
|
||||
}
|
||||
}
|
||||
else if (QSpinBox* sb = qobject_cast<QSpinBox*>(editor))
|
||||
{
|
||||
const u32 value = static_cast<u32>(sb->value());
|
||||
m_parent->setCodeOption(getCodeNameForRow(index), value);
|
||||
model->setData(index, static_cast<uint>(value), Qt::UserRole);
|
||||
}
|
||||
else
|
||||
{
|
||||
return QStyledItemDelegate::setModelData(editor, model, index);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatListOptionDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) const
|
||||
{
|
||||
if (index.column() == 0)
|
||||
{
|
||||
// skip for editable rows
|
||||
if (index.flags() & Qt::ItemIsEditable)
|
||||
return QStyledItemDelegate::paint(painter, option, index);
|
||||
|
||||
// expand the width to full for those without options
|
||||
QStyleOptionViewItem option_copy(option);
|
||||
option_copy.rect.setWidth(option_copy.rect.width() + m_treeview->columnWidth(1));
|
||||
return QStyledItemDelegate::paint(painter, option_copy, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(index.flags() & Qt::ItemIsEditable))
|
||||
{
|
||||
// disable painting this column, so we can expand it (see above)
|
||||
return;
|
||||
}
|
||||
|
||||
// just draw the number or label as a string
|
||||
const QVariant data = index.data(Qt::UserRole);
|
||||
painter->drawText(option.rect, 0, data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
GameCheatSettingsWidget::GameCheatSettingsWidget(SettingsWindow* dialog, QWidget* parent) : m_dialog(dialog)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
m_ui.cheatList->setItemDelegate(new CheatListOptionDelegate(this, m_ui.cheatList));
|
||||
|
||||
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));
|
||||
|
||||
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::customContextMenuRequested, this,
|
||||
&GameCheatSettingsWidget::onCheatListContextMenuRequested);
|
||||
connect(m_ui.cheatList, &QTreeWidget::itemChanged, this, &GameCheatSettingsWidget::onCheatListItemChanged);
|
||||
connect(m_ui.add, &QToolButton::clicked, this, &GameCheatSettingsWidget::newCode);
|
||||
connect(m_ui.remove, &QToolButton::clicked, this, &GameCheatSettingsWidget::onRemoveCodeClicked);
|
||||
connect(m_ui.disableAll, &QToolButton::clicked, this, &GameCheatSettingsWidget::disableAllCheats);
|
||||
connect(m_ui.reloadCheats, &QToolButton::clicked, this, &GameCheatSettingsWidget::onReloadClicked);
|
||||
connect(m_ui.importCheats, &QPushButton::clicked, this, &GameCheatSettingsWidget::onImportClicked);
|
||||
connect(m_ui.exportCheats, &QPushButton::clicked, this, &GameCheatSettingsWidget::onExportClicked);
|
||||
}
|
||||
|
||||
GameCheatSettingsWidget::~GameCheatSettingsWidget() = default;
|
||||
|
||||
const Cheats::CodeInfo* GameCheatSettingsWidget::getCodeInfo(const std::string_view name) const
|
||||
{
|
||||
return Cheats::FindCodeInInfoList(m_codes, name);
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::setCodeOption(const std::string_view name, u32 value)
|
||||
{
|
||||
const Cheats::CodeInfo* info = getCodeInfo(name);
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
m_dialog->getSettingsInterface()->SetUIntValue("Cheats", info->name.c_str(), value);
|
||||
m_dialog->saveAndReloadGameSettings();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
std::vector<std::string_view> unique_prefixes = Cheats::GetCodeListUniquePrefixes(m_codes, false);
|
||||
|
||||
QStringList ret;
|
||||
if (!unique_prefixes.empty())
|
||||
{
|
||||
ret.reserve(unique_prefixes.size());
|
||||
for (const std::string_view& prefix : unique_prefixes)
|
||||
ret.push_back(QtUtils::StringViewToQString(prefix));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool GameCheatSettingsWidget::hasCodeWithName(const std::string_view name) const
|
||||
{
|
||||
return (Cheats::FindCodeInInfoList(m_codes, name) != nullptr);
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::onEnableCheatsChanged(Qt::CheckState state)
|
||||
{
|
||||
if (state == Qt::Checked)
|
||||
m_dialog->getSettingsInterface()->SetBoolValue("Cheats", "EnableCheats", true);
|
||||
else
|
||||
m_dialog->getSettingsInterface()->DeleteValue("Cheats", "EnableCheats");
|
||||
m_dialog->saveAndReloadGameSettings();
|
||||
}
|
||||
|
||||
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);
|
||||
m_dialog->saveAndReloadGameSettings();
|
||||
reloadList();
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::onCheatListItemDoubleClicked(QTreeWidgetItem* item, int column)
|
||||
{
|
||||
const QVariant item_data = item->data(0, Qt::UserRole);
|
||||
if (!item_data.isValid())
|
||||
return;
|
||||
|
||||
editCode(item_data.toString().toStdString());
|
||||
}
|
||||
|
||||
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::onCheatListContextMenuRequested(const QPoint& pos)
|
||||
{
|
||||
Cheats::CodeInfo* selected = getSelectedCode();
|
||||
const std::string selected_code = selected ? selected->name : std::string();
|
||||
|
||||
QMenu context_menu(m_ui.cheatList);
|
||||
|
||||
QAction* add = context_menu.addAction(QIcon::fromTheme("add-line"), tr("Add Cheat..."));
|
||||
connect(add, &QAction::triggered, this, &GameCheatSettingsWidget::newCode);
|
||||
QAction* edit = context_menu.addAction(QIcon::fromTheme("mag-line"), tr("Edit Cheat..."));
|
||||
edit->setEnabled(selected != nullptr);
|
||||
connect(edit, &QAction::triggered, this, [this, &selected_code]() { editCode(selected_code); });
|
||||
QAction* remove = context_menu.addAction(QIcon::fromTheme("minus-line"), tr("Remove Cheat"));
|
||||
remove->setEnabled(selected != nullptr);
|
||||
connect(remove, &QAction::triggered, this, [this, &selected_code]() { removeCode(selected_code, true); });
|
||||
context_menu.addSeparator();
|
||||
|
||||
QAction* disable_all = context_menu.addAction(QIcon::fromTheme("chat-off-line"), tr("Disable All Cheats"));
|
||||
connect(disable_all, &QAction::triggered, this, &GameCheatSettingsWidget::disableAllCheats);
|
||||
|
||||
QAction* reload = context_menu.addAction(QIcon::fromTheme("refresh-line"), tr("Reload Cheats"));
|
||||
connect(reload, &QAction::triggered, this, &GameCheatSettingsWidget::onReloadClicked);
|
||||
|
||||
context_menu.exec(m_ui.cheatList->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::onRemoveCodeClicked()
|
||||
{
|
||||
Cheats::CodeInfo* selected = getSelectedCode();
|
||||
if (!selected)
|
||||
return;
|
||||
|
||||
removeCode(selected->name, true);
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::onReloadClicked()
|
||||
{
|
||||
reloadList();
|
||||
g_emu_thread->reloadCheats(true, false, true, true);
|
||||
}
|
||||
|
||||
bool GameCheatSettingsWidget::shouldLoadFromDatabase() const
|
||||
{
|
||||
return m_dialog->getSettingsInterface()->GetBoolValue("Cheats", "LoadCheatsFromDatabase", true);
|
||||
}
|
||||
|
||||
Cheats::CodeInfo* GameCheatSettingsWidget::getSelectedCode()
|
||||
{
|
||||
const QList<QTreeWidgetItem*> selected = m_ui.cheatList->selectedItems();
|
||||
if (selected.size() != 1)
|
||||
return nullptr;
|
||||
|
||||
const QVariant item_data = selected[0]->data(0, Qt::UserRole);
|
||||
if (!item_data.isValid())
|
||||
return nullptr;
|
||||
|
||||
return Cheats::FindCodeInInfoList(m_codes, item_data.toString().toStdString());
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::disableAllCheats()
|
||||
{
|
||||
setStateForAll(false);
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {-1, 150});
|
||||
}
|
||||
|
||||
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)
|
||||
m_dialog->saveAndReloadGameSettings();
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::setStateForAll(bool enabled)
|
||||
{
|
||||
QSignalBlocker sb(m_ui.cheatList);
|
||||
setStateRecursively(nullptr, enabled);
|
||||
m_dialog->saveAndReloadGameSettings();
|
||||
}
|
||||
|
||||
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(), true);
|
||||
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::newCode()
|
||||
{
|
||||
Cheats::CodeInfo new_code;
|
||||
CheatCodeEditorDialog dlg(this, &new_code, getGroupNames());
|
||||
if (!dlg.exec())
|
||||
{
|
||||
// cancelled
|
||||
return;
|
||||
}
|
||||
|
||||
// no need to reload cheats yet, it's not active. just refresh the list
|
||||
reloadList();
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::editCode(const std::string_view code_name)
|
||||
{
|
||||
Cheats::CodeInfo* code = Cheats::FindCodeInInfoList(m_codes, code_name);
|
||||
if (!code)
|
||||
return;
|
||||
|
||||
CheatCodeEditorDialog dlg(this, code, getGroupNames());
|
||||
if (!dlg.exec())
|
||||
{
|
||||
// no changes
|
||||
return;
|
||||
}
|
||||
|
||||
reloadList();
|
||||
g_emu_thread->reloadCheats(true, true, false, true);
|
||||
}
|
||||
|
||||
void GameCheatSettingsWidget::removeCode(const std::string_view code_name, bool confirm)
|
||||
{
|
||||
Cheats::CodeInfo* code = Cheats::FindCodeInInfoList(m_codes, code_name);
|
||||
if (!code)
|
||||
return;
|
||||
|
||||
if (code->from_database)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("This code is from the built-in cheat database, and cannot be removed. To hide this code, "
|
||||
"uncheck the \"Load Database Cheats\" option."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (QMessageBox::question(this, tr("Confirm Removal"),
|
||||
tr("You are removing the code named '%1'. You cannot undo this action, are you sure you "
|
||||
"wish to delete this code?")) != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Error error;
|
||||
if (!Cheats::UpdateCodeInFile(getPathForSavingCheats().c_str(), code->name, nullptr, &error))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Failed to save file:\n%1").arg(QString::fromStdString(error.GetDescription())));
|
||||
return;
|
||||
}
|
||||
|
||||
reloadList();
|
||||
g_emu_thread->reloadCheats(true, true, false, true);
|
||||
}
|
||||
|
||||
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 (!pi.description.empty())
|
||||
item->setToolTip(0, QString::fromStdString(pi.description));
|
||||
if (!name_part.empty())
|
||||
item->setText(0, QtUtils::StringViewToQString(name_part));
|
||||
|
||||
if (pi.HasOptionChoices())
|
||||
{
|
||||
// need to resolve the value back to a name
|
||||
const std::string_view option_name =
|
||||
pi.MapOptionValueToName(m_dialog->getSettingsInterface()->GetTinyStringValue("Cheats", pi.name.c_str()));
|
||||
item->setData(1, Qt::UserRole, QtUtils::StringViewToQString(option_name));
|
||||
item->setFlags(item->flags() | Qt::ItemIsEditable);
|
||||
}
|
||||
else if (pi.HasOptionRange())
|
||||
{
|
||||
const u32 value = m_dialog->getSettingsInterface()->GetUIntValue("Cheats", pi.name.c_str(), pi.option_range_start);
|
||||
item->setData(1, Qt::UserRole, static_cast<uint>(value));
|
||||
item->setFlags(item->flags() | Qt::ItemIsEditable);
|
||||
}
|
||||
}
|
||||
|
||||
CheatCodeEditorDialog::CheatCodeEditorDialog(GameCheatSettingsWidget* parent, Cheats::CodeInfo* code,
|
||||
const QStringList& group_names)
|
||||
: QDialog(parent), m_parent(parent), m_code(code)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
setupAdditionalUi(group_names);
|
||||
fillUi();
|
||||
|
||||
connect(m_ui.group, &QComboBox::currentIndexChanged, this, &CheatCodeEditorDialog::onGroupSelectedIndexChanged);
|
||||
connect(m_ui.optionsType, &QComboBox::currentIndexChanged, this, &CheatCodeEditorDialog::onOptionTypeChanged);
|
||||
connect(m_ui.rangeMin, &QSpinBox::valueChanged, this, &CheatCodeEditorDialog::onRangeMinChanged);
|
||||
connect(m_ui.rangeMax, &QSpinBox::valueChanged, this, &CheatCodeEditorDialog::onRangeMaxChanged);
|
||||
connect(m_ui.editChoice, &QPushButton::clicked, this, &CheatCodeEditorDialog::onEditChoiceClicked);
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &CheatCodeEditorDialog::saveClicked);
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &CheatCodeEditorDialog::cancelClicked);
|
||||
}
|
||||
|
||||
CheatCodeEditorDialog::~CheatCodeEditorDialog() = default;
|
||||
|
||||
void CheatCodeEditorDialog::onGroupSelectedIndexChanged(int index)
|
||||
{
|
||||
if (index != (m_ui.group->count() - 1))
|
||||
return;
|
||||
|
||||
// new item...
|
||||
const QString text = QInputDialog::getText(
|
||||
this, tr("Enter Group Name"), tr("Enter name for the code group. Using backslashes (\\) will create sub-trees."));
|
||||
|
||||
// don't want this re-triggering
|
||||
QSignalBlocker sb(m_ui.group);
|
||||
|
||||
if (text.isEmpty())
|
||||
{
|
||||
// cancelled...
|
||||
m_ui.group->setCurrentIndex(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const int existing_index = m_ui.group->findText(text);
|
||||
if (existing_index >= 0)
|
||||
{
|
||||
m_ui.group->setCurrentIndex(existing_index);
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui.group->insertItem(index, text);
|
||||
m_ui.group->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::saveClicked()
|
||||
{
|
||||
std::string new_name = m_ui.name->text().toStdString();
|
||||
if (new_name.empty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Name cannot be empty."));
|
||||
return;
|
||||
}
|
||||
|
||||
std::string new_body = m_ui.instructions->toPlainText().toStdString();
|
||||
if (new_body.empty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Instructions cannot be empty."));
|
||||
return;
|
||||
}
|
||||
|
||||
// name actually includes the prefix
|
||||
if (const int index = m_ui.group->currentIndex(); index != 0)
|
||||
{
|
||||
const std::string prefix = m_ui.group->currentText().toStdString();
|
||||
if (!prefix.empty())
|
||||
new_name = fmt::format("{}\\{}", prefix, new_name);
|
||||
}
|
||||
|
||||
// if the name has changed, then we need to make sure it hasn't already been used
|
||||
if (new_name != m_code->name && m_parent->hasCodeWithName(new_name))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("A code with the name '%1' already exists.").arg(QString::fromStdString(new_name)));
|
||||
return;
|
||||
}
|
||||
|
||||
std::string old_name = std::move(m_code->name);
|
||||
|
||||
// cheats coming from the database need to be copied into the user's file
|
||||
if (m_code->from_database)
|
||||
{
|
||||
m_code->from_database = false;
|
||||
old_name.clear();
|
||||
}
|
||||
|
||||
m_code->name = std::move(new_name);
|
||||
m_code->description = m_ui.description->toPlainText().replace('\n', ' ').toStdString();
|
||||
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);
|
||||
|
||||
m_code->option_range_start = 0;
|
||||
m_code->option_range_end = 0;
|
||||
m_code->options = {};
|
||||
if (m_ui.optionsType->currentIndex() == 1)
|
||||
{
|
||||
// choices
|
||||
m_code->options = std::move(m_new_options);
|
||||
}
|
||||
else if (m_ui.optionsType->currentIndex() == 2)
|
||||
{
|
||||
// range
|
||||
m_code->option_range_start = static_cast<u16>(m_ui.rangeMin->value());
|
||||
m_code->option_range_end = static_cast<u16>(m_ui.rangeMax->value());
|
||||
}
|
||||
|
||||
std::string path = m_parent->getPathForSavingCheats();
|
||||
Error error;
|
||||
if (!Cheats::UpdateCodeInFile(path.c_str(), old_name, m_code, &error))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Failed to save cheat code:\n%1").arg(QString::fromStdString(error.GetDescription())));
|
||||
}
|
||||
|
||||
done(1);
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::cancelClicked()
|
||||
{
|
||||
done(0);
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::onOptionTypeChanged(int index)
|
||||
{
|
||||
m_ui.editChoice->setVisible(index == 1);
|
||||
m_ui.rangeMin->setVisible(index == 2);
|
||||
m_ui.rangeMax->setVisible(index == 2);
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::onRangeMinChanged(int value)
|
||||
{
|
||||
m_ui.rangeMax->setValue(std::max(m_ui.rangeMax->value(), value));
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::onRangeMaxChanged(int value)
|
||||
{
|
||||
m_ui.rangeMin->setValue(std::min(m_ui.rangeMin->value(), value));
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::onEditChoiceClicked()
|
||||
{
|
||||
GameCheatCodeChoiceEditorDialog dlg(this, m_new_options);
|
||||
if (dlg.exec())
|
||||
m_new_options = dlg.getNewOptions();
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::setupAdditionalUi(const QStringList& group_names)
|
||||
{
|
||||
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>(Cheats::CodeActivation::Count); i++)
|
||||
m_ui.activation->addItem(Cheats::GetActivationDisplayName(static_cast<Cheats::CodeActivation>(i)));
|
||||
|
||||
m_ui.group->addItem(tr("Ungrouped"));
|
||||
|
||||
if (!group_names.isEmpty())
|
||||
m_ui.group->addItems(group_names);
|
||||
|
||||
m_ui.group->addItem(tr("New..."));
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::fillUi()
|
||||
{
|
||||
m_ui.name->setText(QtUtils::StringViewToQString(m_code->GetNamePart()));
|
||||
m_ui.description->setPlainText(QString::fromStdString(m_code->description));
|
||||
|
||||
const std::string_view group = m_code->GetNameParentPart();
|
||||
if (group.empty())
|
||||
{
|
||||
// ungrouped is always first
|
||||
m_ui.group->setCurrentIndex(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString group_qstr(QtUtils::StringViewToQString(group));
|
||||
int index = m_ui.group->findText(group_qstr);
|
||||
if (index < 0)
|
||||
{
|
||||
// shouldn't happen...
|
||||
index = m_ui.group->count() - 1;
|
||||
m_ui.group->insertItem(index, group_qstr);
|
||||
}
|
||||
|
||||
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.instructions->setPlainText(QString::fromStdString(m_code->body));
|
||||
|
||||
m_ui.rangeMin->setValue(static_cast<int>(m_code->option_range_start));
|
||||
m_ui.rangeMax->setValue(static_cast<int>(m_code->option_range_end));
|
||||
m_new_options = m_code->options;
|
||||
|
||||
m_ui.optionsType->setCurrentIndex(m_code->HasOptionRange() ? 2 : (m_code->HasOptionChoices() ? 1 : 0));
|
||||
onOptionTypeChanged(m_ui.optionsType->currentIndex());
|
||||
}
|
||||
|
||||
GameCheatCodeChoiceEditorDialog::GameCheatCodeChoiceEditorDialog(QWidget* parent, const Cheats::CodeOptionList& options)
|
||||
: QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
connect(m_ui.add, &QToolButton::clicked, this, &GameCheatCodeChoiceEditorDialog::onAddClicked);
|
||||
connect(m_ui.remove, &QToolButton::clicked, this, &GameCheatCodeChoiceEditorDialog::onRemoveClicked);
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &GameCheatCodeChoiceEditorDialog::onSaveClicked);
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &CheatCodeEditorDialog::reject);
|
||||
|
||||
m_ui.optionList->setRootIsDecorated(false);
|
||||
for (const Cheats::CodeOption& opt : options)
|
||||
{
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem();
|
||||
item->setFlags(item->flags() | Qt::ItemIsEditable);
|
||||
item->setText(0, QString::fromStdString(opt.first));
|
||||
item->setText(1, QString::number(opt.second));
|
||||
m_ui.optionList->addTopLevelItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
GameCheatCodeChoiceEditorDialog::~GameCheatCodeChoiceEditorDialog() = default;
|
||||
|
||||
void GameCheatCodeChoiceEditorDialog::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QDialog::resizeEvent(event);
|
||||
QtUtils::ResizeColumnsForTreeView(m_ui.optionList, {-1, 150});
|
||||
}
|
||||
|
||||
void GameCheatCodeChoiceEditorDialog::onAddClicked()
|
||||
{
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem();
|
||||
item->setFlags(item->flags() | Qt::ItemIsEditable);
|
||||
item->setText(0, QStringLiteral("Option %1").arg(m_ui.optionList->topLevelItemCount()));
|
||||
item->setText(1, QStringLiteral("0"));
|
||||
m_ui.optionList->addTopLevelItem(item);
|
||||
}
|
||||
|
||||
void GameCheatCodeChoiceEditorDialog::onRemoveClicked()
|
||||
{
|
||||
const QList<QTreeWidgetItem*> items = m_ui.optionList->selectedItems();
|
||||
for (QTreeWidgetItem* item : items)
|
||||
{
|
||||
const int index = m_ui.optionList->indexOfTopLevelItem(item);
|
||||
if (index >= 0)
|
||||
delete m_ui.optionList->takeTopLevelItem(index);
|
||||
}
|
||||
}
|
||||
|
||||
void GameCheatCodeChoiceEditorDialog::onSaveClicked()
|
||||
{
|
||||
// validate the data
|
||||
const int count = m_ui.optionList->topLevelItemCount();
|
||||
if (count == 0)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("At least one option must be defined."));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
const QTreeWidgetItem* it = m_ui.optionList->topLevelItem(i);
|
||||
const QString this_name = it->text(0);
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
if (i == j)
|
||||
continue;
|
||||
|
||||
if (m_ui.optionList->topLevelItem(j)->text(0) == this_name)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("The option '%1' is defined twice.").arg(this_name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// should be a parseable number
|
||||
const QString this_value = it->text(1);
|
||||
if (bool ok; this_value.toUInt(&ok), !ok)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("The option '%1' does not have a valid value. It must be a number.").arg(this_name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
Cheats::CodeOptionList GameCheatCodeChoiceEditorDialog::getNewOptions() const
|
||||
{
|
||||
Cheats::CodeOptionList ret;
|
||||
|
||||
const int count = m_ui.optionList->topLevelItemCount();
|
||||
ret.reserve(static_cast<size_t>(count));
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
const QTreeWidgetItem* it = m_ui.optionList->topLevelItem(i);
|
||||
ret.emplace_back(it->text(0).toStdString(), it->text(1).toUInt());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui_gamecheatcodechoiceeditordialog.h"
|
||||
#include "ui_gamecheatcodeeditordialog.h"
|
||||
#include "ui_gamecheatsettingswidget.h"
|
||||
|
||||
#include "core/cheats.h"
|
||||
|
||||
#include "common/heterogeneous_containers.h"
|
||||
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtWidgets/QDialog>
|
||||
#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() override;
|
||||
|
||||
const Cheats::CodeInfo* getCodeInfo(const std::string_view name) const;
|
||||
void setCodeOption(const std::string_view name, u32 value);
|
||||
std::string getPathForSavingCheats() const;
|
||||
QStringList getGroupNames() const;
|
||||
bool hasCodeWithName(const std::string_view name) 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 onCheatListContextMenuRequested(const QPoint& pos);
|
||||
void onRemoveCodeClicked();
|
||||
void onReloadClicked();
|
||||
void onImportClicked();
|
||||
void onImportFromFileTriggered();
|
||||
void onImportFromTextTriggered();
|
||||
void onExportClicked();
|
||||
void reloadList();
|
||||
|
||||
private:
|
||||
bool shouldLoadFromDatabase() const;
|
||||
|
||||
Cheats::CodeInfo* getSelectedCode();
|
||||
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 importCodes(const std::string& file_contents);
|
||||
void newCode();
|
||||
void editCode(const std::string_view code_name);
|
||||
void removeCode(const std::string_view code_name, bool confirm);
|
||||
|
||||
Ui::GameCheatSettingsWidget m_ui;
|
||||
SettingsWindow* m_dialog;
|
||||
|
||||
UnorderedStringMap<QTreeWidgetItem*> m_parent_map;
|
||||
Cheats::CodeInfoList m_codes;
|
||||
std::vector<std::string> m_enabled_codes;
|
||||
};
|
||||
|
||||
class CheatCodeEditorDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CheatCodeEditorDialog(GameCheatSettingsWidget* parent, Cheats::CodeInfo* code, const QStringList& group_names);
|
||||
~CheatCodeEditorDialog() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onGroupSelectedIndexChanged(int index);
|
||||
void saveClicked();
|
||||
void cancelClicked();
|
||||
|
||||
void onOptionTypeChanged(int index);
|
||||
void onRangeMinChanged(int value);
|
||||
void onRangeMaxChanged(int value);
|
||||
void onEditChoiceClicked();
|
||||
|
||||
private:
|
||||
void setupAdditionalUi(const QStringList& group_names);
|
||||
void fillUi();
|
||||
|
||||
GameCheatSettingsWidget* m_parent;
|
||||
Ui::GameCheatCodeEditorDialog m_ui;
|
||||
|
||||
Cheats::CodeInfo* m_code;
|
||||
Cheats::CodeOptionList m_new_options;
|
||||
};
|
||||
|
||||
class GameCheatCodeChoiceEditorDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameCheatCodeChoiceEditorDialog(QWidget* parent, const Cheats::CodeOptionList& options);
|
||||
~GameCheatCodeChoiceEditorDialog() override;
|
||||
|
||||
Cheats::CodeOptionList getNewOptions() const;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onAddClicked();
|
||||
void onRemoveClicked();
|
||||
void onSaveClicked();
|
||||
|
||||
private:
|
||||
Ui::GameCheatCodeChoiceEditorDialog m_ui;
|
||||
};
|
|
@ -0,0 +1,175 @@
|
|||
<?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>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,0,0,0">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableCheats">
|
||||
<property name="text">
|
||||
<string>Enable Cheats</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="cheatList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditTrigger::AllEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SelectionMode::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Value</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="loadDatabaseCheats">
|
||||
<property name="text">
|
||||
<string>Load Database Cheats</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="importCheats">
|
||||
<property name="text">
|
||||
<string>Import...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="import-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="exportCheats">
|
||||
<property name="text">
|
||||
<string>Export...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="export-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -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><html><head/><body><p><span style=" font-weight:700;">Author: </span>Patch Author</p><p>Description would go here</p></body></html></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>
|
|
@ -0,0 +1,125 @@
|
|||
// 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->reloadCheats(true, false, true, true);
|
||||
}
|
||||
|
||||
void GamePatchSettingsWidget::disableAllPatches()
|
||||
{
|
||||
SettingsInterface* sif = m_dialog->getSettingsInterface();
|
||||
sif->RemoveSection(Cheats::PATCHES_CONFIG_SECTION);
|
||||
m_dialog->saveAndReloadGameSettings();
|
||||
reloadList();
|
||||
}
|
||||
|
||||
void GamePatchSettingsWidget::reloadList()
|
||||
{
|
||||
const std::vector<Cheats::CodeInfo> patches =
|
||||
Cheats::GetCodeInfoList(m_dialog->getGameSerial(), std::nullopt, false, true, 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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -0,0 +1,71 @@
|
|||
<?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="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>
|
|
@ -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);
|
||||
|
@ -998,125 +997,55 @@ void MainWindow::populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* actio
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateCheatActionsVisibility()
|
||||
{
|
||||
// If the cheat system is disabled, put an action to enable it in place of the menu under System.
|
||||
const bool cheats_enabled = Host::GetBoolSettingValue("Console", "EnableCheats", false);
|
||||
m_ui.actionCheats->setVisible(!cheats_enabled);
|
||||
m_ui.menuCheats->menuAction()->setVisible(cheats_enabled);
|
||||
}
|
||||
|
||||
void MainWindow::onCheatsActionTriggered()
|
||||
{
|
||||
const bool cheats_enabled = Host::GetBoolSettingValue("Console", "EnableCheats", false);
|
||||
if (cheats_enabled)
|
||||
{
|
||||
m_ui.menuCheats->exec(QCursor::pos());
|
||||
return;
|
||||
}
|
||||
|
||||
SystemLock lock(pauseAndLockSystem());
|
||||
QMessageBox mb(this);
|
||||
mb.setWindowTitle(tr("Enable Cheats"));
|
||||
mb.setText(
|
||||
tr("Using cheats can have unpredictable effects on games, causing crashes, graphical glitches, and corrupted "
|
||||
"saves. By using the cheat manager, you agree that it is an unsupported configuration, and we will not "
|
||||
"provide you with any assistance when games break.\n\nCheats persist through save states even after being "
|
||||
"disabled, please remember to reset/reboot the game after turning off any codes.\n\nAre you sure you want "
|
||||
"to continue?"));
|
||||
mb.setIcon(QMessageBox::Warning);
|
||||
QPushButton* global = mb.addButton(tr("Enable For All Games"), QMessageBox::DestructiveRole);
|
||||
QPushButton* game = mb.addButton(tr("Enable For This Game"), QMessageBox::AcceptRole);
|
||||
game->setEnabled(s_system_valid && !s_current_game_serial.isEmpty());
|
||||
QPushButton* cancel = mb.addButton(tr("Cancel"), QMessageBox::RejectRole);
|
||||
mb.setDefaultButton(cancel);
|
||||
mb.setEscapeButton(cancel);
|
||||
mb.exec();
|
||||
|
||||
if (mb.clickedButton() == global)
|
||||
{
|
||||
// enable globally
|
||||
Host::SetBaseBoolSettingValue("Console", "EnableCheats", true);
|
||||
Host::CommitBaseSettingChanges();
|
||||
g_emu_thread->applySettings(false);
|
||||
}
|
||||
else if (mb.clickedButton() == game)
|
||||
{
|
||||
if (!SettingsWindow::setGameSettingsBoolForSerial(s_current_game_serial.toStdString(), "Console", "EnableCheats",
|
||||
true))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to enable cheats for %1.").arg(s_current_game_serial));
|
||||
return;
|
||||
}
|
||||
|
||||
g_emu_thread->reloadGameSettings(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// do nothing
|
||||
return;
|
||||
}
|
||||
m_ui.menuCheats->exec(QCursor::pos());
|
||||
}
|
||||
|
||||
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 +1304,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 +1398,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 +1468,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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1709,7 +1621,6 @@ void MainWindow::setupAdditionalUi()
|
|||
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->isShowingGridCoverTitles());
|
||||
|
||||
updateDebugMenuVisibility();
|
||||
updateCheatActionsVisibility();
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(CPUExecutionMode::Count); i++)
|
||||
{
|
||||
|
@ -1795,37 +1706,38 @@ void MainWindow::setupAdditionalUi()
|
|||
|
||||
void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode)
|
||||
{
|
||||
m_ui.actionStartFile->setDisabled(starting || running);
|
||||
m_ui.actionStartDisc->setDisabled(starting || running);
|
||||
m_ui.actionStartBios->setDisabled(starting || running);
|
||||
m_ui.actionResumeLastState->setDisabled(starting || running || cheevos_challenge_mode);
|
||||
m_ui.actionStartFullscreenUI->setDisabled(starting || running);
|
||||
m_ui.actionStartFullscreenUI2->setDisabled(starting || running);
|
||||
const bool starting_or_running = (starting || running);
|
||||
const bool starting_or_not_running = (starting || !running);
|
||||
m_ui.actionStartFile->setDisabled(starting_or_running);
|
||||
m_ui.actionStartDisc->setDisabled(starting_or_running);
|
||||
m_ui.actionStartBios->setDisabled(starting_or_running);
|
||||
m_ui.actionResumeLastState->setDisabled(starting_or_running || cheevos_challenge_mode);
|
||||
m_ui.actionStartFullscreenUI->setDisabled(starting_or_running);
|
||||
m_ui.actionStartFullscreenUI2->setDisabled(starting_or_running);
|
||||
|
||||
m_ui.actionPowerOff->setDisabled(starting || !running);
|
||||
m_ui.actionPowerOffWithoutSaving->setDisabled(starting || !running);
|
||||
m_ui.actionReset->setDisabled(starting || !running);
|
||||
m_ui.actionPause->setDisabled(starting || !running);
|
||||
m_ui.actionChangeDisc->setDisabled(starting || !running);
|
||||
m_ui.actionCheats->setDisabled(cheevos_challenge_mode);
|
||||
m_ui.actionCheatsToolbar->setDisabled(cheevos_challenge_mode);
|
||||
m_ui.actionScreenshot->setDisabled(starting || !running);
|
||||
m_ui.menuChangeDisc->setDisabled(starting || !running);
|
||||
m_ui.menuCheats->setDisabled(cheevos_challenge_mode);
|
||||
m_ui.actionPowerOff->setDisabled(starting_or_not_running);
|
||||
m_ui.actionPowerOffWithoutSaving->setDisabled(starting_or_not_running);
|
||||
m_ui.actionReset->setDisabled(starting_or_not_running);
|
||||
m_ui.actionPause->setDisabled(starting_or_not_running);
|
||||
m_ui.actionChangeDisc->setDisabled(starting_or_not_running);
|
||||
m_ui.actionCheatsToolbar->setDisabled(starting_or_not_running || cheevos_challenge_mode);
|
||||
m_ui.actionScreenshot->setDisabled(starting_or_not_running);
|
||||
m_ui.menuChangeDisc->setDisabled(starting_or_not_running);
|
||||
m_ui.menuCheats->setDisabled(starting_or_not_running || cheevos_challenge_mode);
|
||||
m_ui.actionCPUDebugger->setDisabled(cheevos_challenge_mode);
|
||||
m_ui.actionMemoryScanner->setDisabled(cheevos_challenge_mode);
|
||||
m_ui.actionReloadTextureReplacements->setDisabled(starting || !running);
|
||||
m_ui.actionDumpRAM->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionDumpVRAM->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionDumpSPURAM->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionReloadTextureReplacements->setDisabled(starting_or_not_running);
|
||||
m_ui.actionDumpRAM->setDisabled(starting_or_not_running || cheevos_challenge_mode);
|
||||
m_ui.actionDumpVRAM->setDisabled(starting_or_not_running || cheevos_challenge_mode);
|
||||
m_ui.actionDumpSPURAM->setDisabled(starting_or_not_running || cheevos_challenge_mode);
|
||||
|
||||
m_ui.actionSaveState->setDisabled(starting || !running);
|
||||
m_ui.menuSaveState->setDisabled(starting || !running);
|
||||
m_ui.menuWindowSize->setDisabled(starting || !running);
|
||||
m_ui.actionSaveState->setDisabled(starting_or_not_running);
|
||||
m_ui.menuSaveState->setDisabled(starting_or_not_running);
|
||||
m_ui.menuWindowSize->setDisabled(starting_or_not_running);
|
||||
|
||||
m_ui.actionViewGameProperties->setDisabled(starting || !running);
|
||||
m_ui.actionViewGameProperties->setDisabled(starting_or_not_running);
|
||||
|
||||
if (starting || running)
|
||||
if (starting_or_running)
|
||||
{
|
||||
if (!m_ui.toolBar->actions().contains(m_ui.actionPowerOff))
|
||||
{
|
||||
|
@ -2037,7 +1949,6 @@ void MainWindow::connectSignals()
|
|||
connect(m_ui.menuLoadState, &QMenu::aboutToShow, this, &MainWindow::onLoadStateMenuAboutToShow);
|
||||
connect(m_ui.menuSaveState, &QMenu::aboutToShow, this, &MainWindow::onSaveStateMenuAboutToShow);
|
||||
connect(m_ui.menuCheats, &QMenu::aboutToShow, this, &MainWindow::onCheatsMenuAboutToShow);
|
||||
connect(m_ui.actionCheats, &QAction::triggered, this, &MainWindow::onCheatsActionTriggered);
|
||||
connect(m_ui.actionCheatsToolbar, &QAction::triggered, this, &MainWindow::onCheatsActionTriggered);
|
||||
connect(m_ui.actionStartFullscreenUI, &QAction::triggered, this, &MainWindow::onStartFullscreenUITriggered);
|
||||
connect(m_ui.actionStartFullscreenUI2, &QAction::triggered, this, &MainWindow::onStartFullscreenUITriggered);
|
||||
|
@ -2081,7 +1992,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,
|
||||
|
@ -2095,8 +2006,10 @@ void MainWindow::connectSignals()
|
|||
connect(m_ui.actionMediaCapture, &QAction::toggled, this, &MainWindow::onToolsMediaCaptureToggled);
|
||||
connect(m_ui.actionCPUDebugger, &QAction::triggered, this, &MainWindow::openCPUDebugger);
|
||||
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);
|
||||
|
@ -2335,6 +2248,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)
|
||||
|
@ -2628,7 +2560,6 @@ void MainWindow::checkForSettingChanges()
|
|||
{
|
||||
LogWindow::updateSettings();
|
||||
updateWindowState();
|
||||
updateCheatActionsVisibility();
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> MainWindow::getWindowInfo()
|
||||
|
@ -2709,7 +2640,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);
|
||||
}
|
||||
|
@ -2781,23 +2711,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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -171,7 +167,6 @@ private Q_SLOTS:
|
|||
void onViewGameListActionTriggered();
|
||||
void onViewGameGridActionTriggered();
|
||||
void onViewSystemDisplayTriggered();
|
||||
void onViewGamePropertiesActionTriggered();
|
||||
void onGitHubRepositoryActionTriggered();
|
||||
void onIssueTrackerActionTriggered();
|
||||
void onDiscordServerActionTriggered();
|
||||
|
@ -193,7 +188,6 @@ private Q_SLOTS:
|
|||
|
||||
void onUpdateCheckComplete();
|
||||
|
||||
void openCheatManager();
|
||||
void openCPUDebugger();
|
||||
|
||||
protected:
|
||||
|
@ -220,7 +214,6 @@ private:
|
|||
void updateStatusBarWidgetVisibility();
|
||||
void updateWindowTitle();
|
||||
void updateWindowState(bool force_visible = false);
|
||||
void updateCheatActionsVisibility();
|
||||
|
||||
void setProgressBar(int current, int total);
|
||||
void clearProgressBar();
|
||||
|
@ -245,6 +238,7 @@ private:
|
|||
void updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen);
|
||||
|
||||
void doSettings(const char* category = nullptr);
|
||||
void openGamePropertiesForCurrentGame(const char* category = nullptr);
|
||||
void doControllerSettings(ControllerSettingsWindow::Category category = ControllerSettingsWindow::Category::Count);
|
||||
|
||||
void updateDebugMenuCPUExecutionMode();
|
||||
|
@ -301,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;
|
||||
|
||||
|
|
|
@ -87,7 +87,6 @@
|
|||
<addaction name="actionPause"/>
|
||||
<addaction name="menuChangeDisc"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCheats"/>
|
||||
<addaction name="menuCheats"/>
|
||||
<addaction name="actionScreenshot"/>
|
||||
<addaction name="separator"/>
|
||||
|
@ -481,14 +480,6 @@
|
|||
<string>Change Disc...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCheats">
|
||||
<property name="icon">
|
||||
<iconset theme="cheats-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Cheats...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCheatsToolbar">
|
||||
<property name="icon">
|
||||
<iconset theme="cheats-line"/>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "memoryscannerwindow.h"
|
||||
#include "cheatcodeeditordialog.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#include "ui_memoryscannerwindow.h"
|
||||
|
||||
#include "core/cheats.h"
|
||||
#include "core/memory_scanner.h"
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtWidgets/QComboBox>
|
||||
|
|
|
@ -1292,28 +1292,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::reloadCheats(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()
|
||||
|
|
|
@ -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 reloadCheats(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);
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
<file>icons/black/svg/arrow-left-right-line.svg</file>
|
||||
<file>icons/black/svg/arrow-up-line.svg</file>
|
||||
<file>icons/black/svg/artboard-2-line.svg</file>
|
||||
<file>icons/black/svg/chat-off-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/chip-2-line.svg</file>
|
||||
|
@ -55,6 +56,7 @@
|
|||
<file>icons/black/svg/download-2-line.svg</file>
|
||||
<file>icons/black/svg/eject-line.svg</file>
|
||||
<file>icons/black/svg/emulation-line.svg</file>
|
||||
<file>icons/black/svg/export-line.svg</file>
|
||||
<file>icons/black/svg/file-add-line.svg</file>
|
||||
<file>icons/black/svg/file-line.svg</file>
|
||||
<file>icons/black/svg/file-list-line.svg</file>
|
||||
|
@ -74,6 +76,7 @@
|
|||
<file>icons/black/svg/global-line.svg</file>
|
||||
<file>icons/black/svg/guncon-line.svg</file>
|
||||
<file>icons/black/svg/image-fill.svg</file>
|
||||
<file>icons/black/svg/import-line.svg</file>
|
||||
<file>icons/black/svg/information-line.svg</file>
|
||||
<file>icons/black/svg/joystick-line.svg</file>
|
||||
<file>icons/black/svg/keyboard-line.svg</file>
|
||||
|
@ -98,6 +101,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>
|
||||
|
@ -249,6 +253,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>
|
||||
|
@ -271,6 +276,7 @@
|
|||
<file>icons/white/svg/download-2-line.svg</file>
|
||||
<file>icons/white/svg/eject-line.svg</file>
|
||||
<file>icons/white/svg/emulation-line.svg</file>
|
||||
<file>icons/white/svg/export-line.svg</file>
|
||||
<file>icons/white/svg/file-add-line.svg</file>
|
||||
<file>icons/white/svg/file-line.svg</file>
|
||||
<file>icons/white/svg/file-list-line.svg</file>
|
||||
|
@ -290,6 +296,7 @@
|
|||
<file>icons/white/svg/global-line.svg</file>
|
||||
<file>icons/white/svg/guncon-line.svg</file>
|
||||
<file>icons/white/svg/image-fill.svg</file>
|
||||
<file>icons/white/svg/import-line.svg</file>
|
||||
<file>icons/white/svg/information-line.svg</file>
|
||||
<file>icons/white/svg/joystick-line.svg</file>
|
||||
<file>icons/white/svg/keyboard-line.svg</file>
|
||||
|
@ -314,6 +321,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>
|
||||
|
|
|
@ -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 |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000"><path d="M22 4C22 3.44772 21.5523 3 21 3H3C2.44772 3 2 3.44772 2 4V20C2 20.5523 2.44772 21 3 21H21C21.5523 21 22 20.5523 22 20V4ZM4 15H7.41604C8.1876 16.7659 9.94968 18 12 18C14.0503 18 15.8124 16.7659 16.584 15H20V19H4V15ZM4 5H20V13H15C15 14.6569 13.6569 16 12 16C10.3431 16 9 14.6569 9 13H4V5ZM16 11H13V14H11V11H8L12 6.5L16 11Z"></path></svg>
|
After Width: | Height: | Size: 419 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000"><path d="M22 4C22 3.44772 21.5523 3 21 3H3C2.44772 3 2 3.44772 2 4V20C2 20.5523 2.44772 21 3 21H21C21.5523 21 22 20.5523 22 20V4ZM4 15H7.41604C8.1876 16.7659 9.94968 18 12 18C14.0503 18 15.8124 16.7659 16.584 15H20V19H4V15ZM4 5H20V13H15C15 14.6569 13.6569 16 12 16C10.3431 16 9 14.6569 9 13H4V5ZM16 9H13V6H11V9H8L12 13.5L16 9Z"></path></svg>
|
After Width: | Height: | Size: 416 B |
|
@ -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 |
|
@ -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 |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ffffff"><path d="M22 4C22 3.44772 21.5523 3 21 3H3C2.44772 3 2 3.44772 2 4V20C2 20.5523 2.44772 21 3 21H21C21.5523 21 22 20.5523 22 20V4ZM4 15H7.41604C8.1876 16.7659 9.94968 18 12 18C14.0503 18 15.8124 16.7659 16.584 15H20V19H4V15ZM4 5H20V13H15C15 14.6569 13.6569 16 12 16C10.3431 16 9 14.6569 9 13H4V5ZM16 11H13V14H11V11H8L12 6.5L16 11Z"></path></svg>
|
After Width: | Height: | Size: 419 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ffffff"><path d="M22 4C22 3.44772 21.5523 3 21 3H3C2.44772 3 2 3.44772 2 4V20C2 20.5523 2.44772 21 3 21H21C21.5523 21 22 20.5523 22 20V4ZM4 15H7.41604C8.1876 16.7659 9.94968 18 12 18C14.0503 18 15.8124 16.7659 16.584 15H20V19H4V15ZM4 5H20V13H15C15 14.6569 13.6569 16 12 16C10.3431 16 9 14.6569 9 13H4V5ZM16 9H13V6H11V9H8L12 13.5L16 9Z"></path></svg>
|
After Width: | Height: | Size: 416 B |
|
@ -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 |
|
@ -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,23 @@ 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. 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."));
|
||||
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. "
|
||||
"<strong>Using cheats can have unpredictable effects on games, causing crashes, graphical glitches, "
|
||||
"and corrupted saves.</strong> Cheats also persist through save states even after being disabled, "
|
||||
"please remember to reset/reboot the game after turning off any codes."));
|
||||
}
|
||||
|
||||
addWidget(
|
||||
m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"),
|
||||
QStringLiteral("memcard-line"),
|
||||
|
@ -635,8 +654,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 +689,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 +699,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()
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,0xf0ac,0xf0ad,0xf0c3,0xf0c3,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,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2717,0x2717,0x278a,0x278e,0x27fc,0x27fc,0xe001,0xe001,0xff21,0xff3a,0x1f52b,0x1f52b,0x0,0x0 };
|
||||
|
||||
|
|